thumbgate 1.1.0 → 1.3.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/README.md +4 -4
- 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 +48 -16
- package/adapters/README.md +1 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/mcp/server-stdio.js +11 -8
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +20 -11
- package/config/github-about.json +1 -1
- package/config/model-tiers.json +11 -0
- package/package.json +22 -11
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-codex-bridge/.mcp.json +1 -1
- 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/cursor-marketplace/README.md +2 -2
- package/plugins/cursor-marketplace/commands/capture-feedback.md +2 -2
- package/plugins/cursor-marketplace/rules/feedback-capture.mdc +3 -3
- package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +3 -2
- package/plugins/opencode-profile/INSTALL.md +1 -1
- package/public/compare.html +302 -0
- package/public/guide.html +4 -4
- package/public/index.html +77 -38
- package/public/learn/ai-agent-persistent-memory.html +1 -0
- package/public/lessons.html +325 -17
- package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
- package/scripts/ai-search-visibility.js +142 -0
- package/scripts/audit-trail.js +6 -0
- package/scripts/capture-railway-diagnostics.sh +97 -0
- package/scripts/changeset-check.js +372 -0
- package/scripts/check-congruence.js +8 -5
- package/scripts/claude-feedback-sync.js +320 -0
- package/scripts/cli-telemetry.js +4 -1
- package/scripts/computer-use-firewall.js +45 -15
- package/scripts/contextfs.js +32 -23
- package/scripts/dashboard.js +84 -0
- package/scripts/docker-sandbox-planner.js +208 -0
- package/scripts/feedback-loop.js +16 -0
- package/scripts/github-about.js +56 -0
- package/scripts/intervention-policy.js +696 -0
- package/scripts/local-model-profile.js +18 -2
- package/scripts/model-tier-router.js +10 -1
- package/scripts/operational-integrity.js +361 -32
- package/scripts/prove-adapters.js +1 -0
- package/scripts/prove-automation.js +2 -2
- package/scripts/prove-packaged-runtime.js +260 -0
- package/scripts/prove-runtime.js +13 -0
- package/scripts/published-cli.js +10 -1
- package/scripts/rate-limiter.js +3 -3
- package/scripts/statusline-links.js +238 -0
- package/scripts/statusline-local-stats.js +2 -0
- package/scripts/statusline.sh +200 -10
- package/scripts/sync-github-about.js +7 -4
- package/scripts/tool-registry.js +2 -2
- package/scripts/workflow-sentinel.js +197 -39
- package/skills/thumbgate/SKILL.md +1 -1
- package/src/api/server.js +12 -1
package/scripts/dashboard.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { aggregateFailureDiagnostics } = require('./failure-diagnostics');
|
|
7
|
+
const { AUDIT_LOG_FILENAME } = require('./audit-trail');
|
|
7
8
|
const { getBillingSummary, loadFunnelLedger, loadResolvedRevenueEvents } = require('./billing');
|
|
8
9
|
const { getTelemetryAnalytics, loadTelemetryEvents } = require('./telemetry-analytics');
|
|
9
10
|
const { getAutoGatesPath } = require('./auto-promote-gates');
|
|
@@ -19,6 +20,7 @@ const { routeProfile } = require('./profile-router');
|
|
|
19
20
|
const { getSettingsStatus } = require('./settings-hierarchy');
|
|
20
21
|
const { summarizeWorkflowRuns } = require('./workflow-runs');
|
|
21
22
|
const { searchLessons } = require('./lesson-search');
|
|
23
|
+
const { getInterventionPolicySummary } = require('./intervention-policy');
|
|
22
24
|
|
|
23
25
|
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
24
26
|
const DEFAULT_GATES_PATH = path.join(PROJECT_ROOT, 'config', 'gates', 'default.json');
|
|
@@ -60,6 +62,15 @@ function pickFirstText(...values) {
|
|
|
60
62
|
return null;
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
function toLocalDayKey(value) {
|
|
66
|
+
const ts = value instanceof Date ? value : new Date(value);
|
|
67
|
+
if (Number.isNaN(ts.getTime())) return null;
|
|
68
|
+
const year = ts.getFullYear();
|
|
69
|
+
const month = String(ts.getMonth() + 1).padStart(2, '0');
|
|
70
|
+
const day = String(ts.getDate()).padStart(2, '0');
|
|
71
|
+
return `${year}-${month}-${day}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
63
74
|
// ---------------------------------------------------------------------------
|
|
64
75
|
// Approval rate + trend
|
|
65
76
|
// ---------------------------------------------------------------------------
|
|
@@ -143,6 +154,58 @@ function computeGateStats() {
|
|
|
143
154
|
};
|
|
144
155
|
}
|
|
145
156
|
|
|
157
|
+
function computeGateAuditSeries(feedbackDir, options = {}) {
|
|
158
|
+
const auditLogPath = path.join(feedbackDir, AUDIT_LOG_FILENAME);
|
|
159
|
+
const entries = readJSONL(auditLogPath).filter((entry) => entry && entry.timestamp);
|
|
160
|
+
const dayCount = Number.isInteger(options.dayCount) ? options.dayCount : 14;
|
|
161
|
+
const today = new Date();
|
|
162
|
+
today.setHours(0, 0, 0, 0);
|
|
163
|
+
const countsByDay = new Map();
|
|
164
|
+
|
|
165
|
+
for (const entry of entries) {
|
|
166
|
+
if (!['allow', 'deny', 'warn'].includes(entry.decision)) continue;
|
|
167
|
+
const dayKey = toLocalDayKey(entry.timestamp);
|
|
168
|
+
if (!dayKey) continue;
|
|
169
|
+
if (!countsByDay.has(dayKey)) {
|
|
170
|
+
countsByDay.set(dayKey, { allow: 0, deny: 0, warn: 0 });
|
|
171
|
+
}
|
|
172
|
+
countsByDay.get(dayKey)[entry.decision] += 1;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const days = [];
|
|
176
|
+
const totals = { allow: 0, deny: 0, warn: 0, intercepted: 0, total: 0 };
|
|
177
|
+
|
|
178
|
+
for (let offset = dayCount - 1; offset >= 0; offset -= 1) {
|
|
179
|
+
const day = new Date(today);
|
|
180
|
+
day.setDate(today.getDate() - offset);
|
|
181
|
+
const dayKey = toLocalDayKey(day);
|
|
182
|
+
const record = countsByDay.get(dayKey) || { allow: 0, deny: 0, warn: 0 };
|
|
183
|
+
const intercepted = record.deny + record.warn;
|
|
184
|
+
const total = intercepted + record.allow;
|
|
185
|
+
const summary = {
|
|
186
|
+
dayKey,
|
|
187
|
+
allow: record.allow,
|
|
188
|
+
deny: record.deny,
|
|
189
|
+
warn: record.warn,
|
|
190
|
+
intercepted,
|
|
191
|
+
total,
|
|
192
|
+
};
|
|
193
|
+
totals.allow += record.allow;
|
|
194
|
+
totals.deny += record.deny;
|
|
195
|
+
totals.warn += record.warn;
|
|
196
|
+
totals.intercepted += intercepted;
|
|
197
|
+
totals.total += total;
|
|
198
|
+
days.push(summary);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
dayCount,
|
|
203
|
+
days,
|
|
204
|
+
totals,
|
|
205
|
+
activeDays: days.filter((day) => day.total > 0).length,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
146
209
|
function listActiveGates() {
|
|
147
210
|
try {
|
|
148
211
|
const config = loadGatesConfig();
|
|
@@ -710,6 +773,7 @@ function generateDashboard(feedbackDir, options = {}) {
|
|
|
710
773
|
const prevention = computePreventionImpact(feedbackDir, gateStats);
|
|
711
774
|
const trend = computeSessionTrend(entries, 10);
|
|
712
775
|
const health = computeSystemHealth(feedbackDir, gateStats);
|
|
776
|
+
const gateAudit = computeGateAuditSeries(feedbackDir);
|
|
713
777
|
const diagnostics = aggregateFailureDiagnostics([...entries, ...diagnosticEntries]);
|
|
714
778
|
const secretGuard = computeSecretGuardStats(diagnosticEntries);
|
|
715
779
|
const gates = listActiveGates();
|
|
@@ -722,6 +786,7 @@ function generateDashboard(feedbackDir, options = {}) {
|
|
|
722
786
|
const delegation = summarizeDelegation(feedbackDir);
|
|
723
787
|
const readiness = generateAgentReadinessReport({ projectRoot: PROJECT_ROOT });
|
|
724
788
|
const harness = computeHarnessOverview(feedbackDir, entries);
|
|
789
|
+
const interventionPolicy = getInterventionPolicySummary(feedbackDir);
|
|
725
790
|
const settingsStatus = getSettingsStatus({ projectRoot: PROJECT_ROOT });
|
|
726
791
|
settingsStatus.routingPreview = {
|
|
727
792
|
dashboardTool: routeProfile({
|
|
@@ -782,6 +847,7 @@ function generateDashboard(feedbackDir, options = {}) {
|
|
|
782
847
|
prevention,
|
|
783
848
|
trend,
|
|
784
849
|
health,
|
|
850
|
+
gateAudit,
|
|
785
851
|
diagnostics,
|
|
786
852
|
delegation,
|
|
787
853
|
secretGuard,
|
|
@@ -790,6 +856,7 @@ function generateDashboard(feedbackDir, options = {}) {
|
|
|
790
856
|
observability,
|
|
791
857
|
instrumentation,
|
|
792
858
|
readiness,
|
|
859
|
+
interventionPolicy,
|
|
793
860
|
settingsStatus,
|
|
794
861
|
team,
|
|
795
862
|
templateLibrary,
|
|
@@ -809,6 +876,7 @@ function printDashboard(data) {
|
|
|
809
876
|
prevention,
|
|
810
877
|
trend,
|
|
811
878
|
health,
|
|
879
|
+
gateAudit,
|
|
812
880
|
diagnostics,
|
|
813
881
|
delegation,
|
|
814
882
|
secretGuard,
|
|
@@ -817,6 +885,7 @@ function printDashboard(data) {
|
|
|
817
885
|
observability,
|
|
818
886
|
instrumentation,
|
|
819
887
|
readiness,
|
|
888
|
+
interventionPolicy,
|
|
820
889
|
settingsStatus,
|
|
821
890
|
team,
|
|
822
891
|
templateLibrary,
|
|
@@ -862,6 +931,20 @@ function printDashboard(data) {
|
|
|
862
931
|
console.log(` Top Next Fix : ${harness.topRecommendations[0].type} (${harness.topRecommendations[0].count} lessons)`);
|
|
863
932
|
}
|
|
864
933
|
|
|
934
|
+
console.log('');
|
|
935
|
+
console.log('🧠 Learned Policy');
|
|
936
|
+
console.log(` Enabled : ${interventionPolicy.enabled ? 'yes' : 'no'}`);
|
|
937
|
+
console.log(` Examples : ${interventionPolicy.exampleCount}`);
|
|
938
|
+
console.log(` Train Accuracy : ${Math.round((interventionPolicy.metrics.trainingAccuracy || 0) * 100)}%`);
|
|
939
|
+
console.log(` Holdout Accuracy : ${Math.round((interventionPolicy.metrics.holdoutAccuracy || 0) * 100)}%`);
|
|
940
|
+
console.log(` Recent Pressure : ${Math.round((interventionPolicy.nonAllowRate || 0) * 100)}% non-allow`);
|
|
941
|
+
if (interventionPolicy.updatedAt) {
|
|
942
|
+
console.log(` Updated : ${interventionPolicy.updatedAt}`);
|
|
943
|
+
}
|
|
944
|
+
if (interventionPolicy.topTokens && interventionPolicy.topTokens.deny && interventionPolicy.topTokens.deny[0]) {
|
|
945
|
+
console.log(` Top Deny Signal : ${interventionPolicy.topTokens.deny[0].token}`);
|
|
946
|
+
}
|
|
947
|
+
|
|
865
948
|
console.log('');
|
|
866
949
|
console.log('🎯 North Star');
|
|
867
950
|
console.log(` Weekly Proof Runs: ${analytics.northStar.weeklyActiveProofBackedWorkflowRuns}`);
|
|
@@ -1043,6 +1126,7 @@ module.exports = {
|
|
|
1043
1126
|
computeSystemHealth,
|
|
1044
1127
|
computeEfficiencyMetrics,
|
|
1045
1128
|
computeHarnessOverview,
|
|
1129
|
+
getInterventionPolicySummary,
|
|
1046
1130
|
computeAnalyticsSummary,
|
|
1047
1131
|
computeSecretGuardStats,
|
|
1048
1132
|
computeObservabilityStats,
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const { classifyCommand } = require('./operational-integrity');
|
|
7
|
+
|
|
8
|
+
const HIGH_RISK_ACTION_TYPES = new Set([
|
|
9
|
+
'shell.exec',
|
|
10
|
+
'file.delete',
|
|
11
|
+
'upload',
|
|
12
|
+
'message.send',
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
function normalizeText(value) {
|
|
16
|
+
if (value === undefined || value === null) return '';
|
|
17
|
+
return String(value).trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeStringArray(values = []) {
|
|
21
|
+
if (!Array.isArray(values)) return [];
|
|
22
|
+
return Array.from(new Set(
|
|
23
|
+
values
|
|
24
|
+
.map((value) => normalizeText(value))
|
|
25
|
+
.filter(Boolean),
|
|
26
|
+
));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeRiskBand(value) {
|
|
30
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
31
|
+
if (['very_high', 'high', 'medium', 'low'].includes(normalized)) {
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
if (normalized === 'critical') return 'very_high';
|
|
35
|
+
return 'low';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function quoteShellArg(value) {
|
|
39
|
+
return `'${String(value).replaceAll('\'', String.raw`'\''`)}'`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildNetworkPolicy(input = {}) {
|
|
43
|
+
const allowedHosts = normalizeStringArray(input.allowedHosts || input.egressAllowlist);
|
|
44
|
+
if (input.requiresNetwork !== true) {
|
|
45
|
+
return {
|
|
46
|
+
mode: 'deny_all',
|
|
47
|
+
allowedHosts: [],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
mode: allowedHosts.length > 0 ? 'allow_list' : 'egress_enabled',
|
|
52
|
+
allowedHosts,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildLaunchers(workspacePath) {
|
|
57
|
+
const suffix = workspacePath ? ` shell ${quoteShellArg(workspacePath)}` : ' shell';
|
|
58
|
+
return {
|
|
59
|
+
standalone: `sbx run${suffix}`,
|
|
60
|
+
dockerDesktop: `docker sandbox run${suffix}`,
|
|
61
|
+
followUp: workspacePath
|
|
62
|
+
? [
|
|
63
|
+
'sbx list',
|
|
64
|
+
'docker sandbox ls',
|
|
65
|
+
]
|
|
66
|
+
: [],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function buildSummary(shouldSandbox, recommendation) {
|
|
71
|
+
if (!shouldSandbox) {
|
|
72
|
+
return 'Current action can stay on the normal local execution path.';
|
|
73
|
+
}
|
|
74
|
+
if (recommendation === 'required') {
|
|
75
|
+
return 'Route this action into Docker Sandboxes before retrying so the run happens inside a disposable microVM instead of on the host.';
|
|
76
|
+
}
|
|
77
|
+
return 'Prefer Docker Sandboxes for this action to reduce host blast radius while keeping local autonomy.';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function buildWhy({
|
|
81
|
+
recommendation,
|
|
82
|
+
command,
|
|
83
|
+
riskBand,
|
|
84
|
+
actionType,
|
|
85
|
+
affectedFiles,
|
|
86
|
+
}) {
|
|
87
|
+
const lines = [];
|
|
88
|
+
if (recommendation === 'required') {
|
|
89
|
+
lines.push('The predicted action is destructive or release-sensitive enough to justify host isolation.');
|
|
90
|
+
} else if (recommendation === 'recommended') {
|
|
91
|
+
lines.push('The predicted action is high-risk enough that isolated execution meaningfully reduces host blast radius.');
|
|
92
|
+
} else {
|
|
93
|
+
lines.push('The current action does not need a dedicated Docker sandbox boundary.');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (command && /\brm\s+-rf\b/i.test(command)) {
|
|
97
|
+
lines.push('Recursive delete commands are safer when the filesystem boundary lives inside a disposable microVM.');
|
|
98
|
+
}
|
|
99
|
+
if (command && /\bgit\s+push\b.*(?:--force|-f)\b/i.test(command)) {
|
|
100
|
+
lines.push('Force-push flows should run in an isolated lane so host credentials and unrelated state stay out of scope.');
|
|
101
|
+
}
|
|
102
|
+
if (command && /\b(?:gh\s+pr\s+(?:create|merge)|npm\s+publish|yarn\s+publish|pnpm\s+publish)\b/i.test(command)) {
|
|
103
|
+
lines.push('PR, merge, and publish flows are governance-sensitive and benefit from a disposable execution boundary.');
|
|
104
|
+
}
|
|
105
|
+
if (HIGH_RISK_ACTION_TYPES.has(actionType)) {
|
|
106
|
+
lines.push(`Action type ${actionType} is in the high-risk set for local execution.`);
|
|
107
|
+
}
|
|
108
|
+
if (riskBand === 'very_high' || riskBand === 'high') {
|
|
109
|
+
lines.push(`Risk band ${riskBand} predicts elevated blast radius on the local host.`);
|
|
110
|
+
}
|
|
111
|
+
if (affectedFiles.length >= 4) {
|
|
112
|
+
lines.push(`The change touches ${affectedFiles.length} files, so host isolation improves recovery if the run goes sideways.`);
|
|
113
|
+
}
|
|
114
|
+
return lines;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function buildDockerSandboxPlan(input = {}) {
|
|
118
|
+
const toolName = normalizeText(input.toolName);
|
|
119
|
+
const actionType = normalizeText(input.actionType)
|
|
120
|
+
|| (toolName === 'Bash' ? 'shell.exec' : '');
|
|
121
|
+
const command = normalizeText(input.command);
|
|
122
|
+
const repoPath = normalizeText(input.repoPath);
|
|
123
|
+
const workspacePath = repoPath ? path.resolve(repoPath) : null;
|
|
124
|
+
const affectedFiles = normalizeStringArray(input.affectedFiles || input.changedFiles || input.files);
|
|
125
|
+
const riskBand = normalizeRiskBand(input.riskBand || input.band);
|
|
126
|
+
const riskScore = Number.isFinite(Number(input.riskScore))
|
|
127
|
+
? Number(Number(input.riskScore).toFixed(4))
|
|
128
|
+
: null;
|
|
129
|
+
const commandInfo = classifyCommand(command);
|
|
130
|
+
const destructiveCommand = /\brm\s+-rf\b/i.test(command)
|
|
131
|
+
|| /\bgit\s+push\b.*(?:--force|-f)\b/i.test(command)
|
|
132
|
+
|| /\bgh\s+pr\s+merge\b.*--admin\b/i.test(command);
|
|
133
|
+
const governedCommand = Boolean(
|
|
134
|
+
commandInfo.isPrCreate
|
|
135
|
+
|| commandInfo.isPrMerge
|
|
136
|
+
|| commandInfo.isPublish
|
|
137
|
+
|| commandInfo.isReleaseCreate
|
|
138
|
+
|| commandInfo.isTagCreate
|
|
139
|
+
);
|
|
140
|
+
const highRiskAction = HIGH_RISK_ACTION_TYPES.has(actionType)
|
|
141
|
+
|| destructiveCommand
|
|
142
|
+
|| governedCommand
|
|
143
|
+
|| riskBand === 'high'
|
|
144
|
+
|| riskBand === 'very_high';
|
|
145
|
+
|
|
146
|
+
let recommendation = 'not_needed';
|
|
147
|
+
if (destructiveCommand || commandInfo.isPublish || commandInfo.isReleaseCreate || actionType === 'upload' || actionType === 'message.send') {
|
|
148
|
+
recommendation = 'required';
|
|
149
|
+
} else if (highRiskAction || affectedFiles.length >= 4) {
|
|
150
|
+
recommendation = 'recommended';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const shouldSandbox = recommendation !== 'not_needed';
|
|
154
|
+
const networkPolicy = buildNetworkPolicy({
|
|
155
|
+
requiresNetwork: input.requiresNetwork === true || governedCommand || commandInfo.isPublish || actionType === 'upload' || actionType === 'message.send',
|
|
156
|
+
allowedHosts: input.allowedHosts,
|
|
157
|
+
egressAllowlist: input.egressAllowlist,
|
|
158
|
+
});
|
|
159
|
+
const launchers = buildLaunchers(workspacePath);
|
|
160
|
+
const summary = buildSummary(shouldSandbox, recommendation);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
plannerVersion: 'docker-sandbox-plan-v1',
|
|
164
|
+
shouldSandbox,
|
|
165
|
+
recommendation,
|
|
166
|
+
summary,
|
|
167
|
+
sandboxKind: shouldSandbox ? 'docker_microvm' : 'host',
|
|
168
|
+
workspacePath,
|
|
169
|
+
actionType: actionType || null,
|
|
170
|
+
riskBand,
|
|
171
|
+
riskScore,
|
|
172
|
+
command: command || null,
|
|
173
|
+
affectedFiles,
|
|
174
|
+
networkPolicy,
|
|
175
|
+
launchers,
|
|
176
|
+
claims: shouldSandbox ? {
|
|
177
|
+
isolationBoundary: 'microvm',
|
|
178
|
+
hostAccess: 'bounded_outside_host',
|
|
179
|
+
dockerDaemon: 'private_inside_sandbox',
|
|
180
|
+
workspaceStrategy: workspacePath ? 'directory_sync' : 'ephemeral',
|
|
181
|
+
} : null,
|
|
182
|
+
why: buildWhy({
|
|
183
|
+
recommendation,
|
|
184
|
+
command,
|
|
185
|
+
riskBand,
|
|
186
|
+
actionType,
|
|
187
|
+
affectedFiles,
|
|
188
|
+
}),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = {
|
|
193
|
+
HIGH_RISK_ACTION_TYPES,
|
|
194
|
+
buildDockerSandboxPlan,
|
|
195
|
+
buildLaunchers,
|
|
196
|
+
buildNetworkPolicy,
|
|
197
|
+
buildSummary,
|
|
198
|
+
normalizeRiskBand,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
if (require.main === module) {
|
|
202
|
+
const plan = buildDockerSandboxPlan({
|
|
203
|
+
toolName: process.argv[2] || 'Bash',
|
|
204
|
+
command: process.argv.slice(3).join(' '),
|
|
205
|
+
repoPath: process.cwd(),
|
|
206
|
+
});
|
|
207
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
208
|
+
}
|
package/scripts/feedback-loop.js
CHANGED
|
@@ -377,6 +377,10 @@ function appendDiagnosticRecord(params = {}) {
|
|
|
377
377
|
timestamp: params.timestamp || new Date().toISOString(),
|
|
378
378
|
};
|
|
379
379
|
appendJSONL(DIAGNOSTIC_LOG_PATH, record);
|
|
380
|
+
try {
|
|
381
|
+
const { trainAndPersistInterventionPolicy } = require('./intervention-policy');
|
|
382
|
+
trainAndPersistInterventionPolicy(getFeedbackPaths().FEEDBACK_DIR);
|
|
383
|
+
} catch { /* non-critical */ }
|
|
380
384
|
return record;
|
|
381
385
|
}
|
|
382
386
|
|
|
@@ -1090,6 +1094,10 @@ function captureFeedback(params) {
|
|
|
1090
1094
|
const riskScorer = getRiskScorerModule();
|
|
1091
1095
|
if (riskScorer) riskScorer.trainAndPersistRiskModel(FEEDBACK_DIR);
|
|
1092
1096
|
} catch { /* non-critical */ }
|
|
1097
|
+
try {
|
|
1098
|
+
const { trainAndPersistInterventionPolicy } = require('./intervention-policy');
|
|
1099
|
+
trainAndPersistInterventionPolicy(FEEDBACK_DIR);
|
|
1100
|
+
} catch { /* non-critical */ }
|
|
1093
1101
|
updateStatuslineWithLesson({
|
|
1094
1102
|
accepted: false,
|
|
1095
1103
|
signal,
|
|
@@ -1127,6 +1135,10 @@ function captureFeedback(params) {
|
|
|
1127
1135
|
const riskScorer = getRiskScorerModule();
|
|
1128
1136
|
if (riskScorer) riskScorer.trainAndPersistRiskModel(FEEDBACK_DIR);
|
|
1129
1137
|
} catch { /* non-critical */ }
|
|
1138
|
+
try {
|
|
1139
|
+
const { trainAndPersistInterventionPolicy } = require('./intervention-policy');
|
|
1140
|
+
trainAndPersistInterventionPolicy(FEEDBACK_DIR);
|
|
1141
|
+
} catch { /* non-critical */ }
|
|
1130
1142
|
return {
|
|
1131
1143
|
accepted: false,
|
|
1132
1144
|
signalLogged: true,
|
|
@@ -1285,6 +1297,10 @@ function captureFeedback(params) {
|
|
|
1285
1297
|
const riskScorer = getRiskScorerModule();
|
|
1286
1298
|
if (riskScorer) riskScorer.trainAndPersistRiskModel(FEEDBACK_DIR);
|
|
1287
1299
|
} catch { /* non-critical */ }
|
|
1300
|
+
try {
|
|
1301
|
+
const { trainAndPersistInterventionPolicy } = require('./intervention-policy');
|
|
1302
|
+
trainAndPersistInterventionPolicy(FEEDBACK_DIR);
|
|
1303
|
+
} catch { /* non-critical */ }
|
|
1288
1304
|
try {
|
|
1289
1305
|
const toolName = feedbackEvent.toolName || feedbackEvent.tool_name || 'unknown';
|
|
1290
1306
|
const toolInput = feedbackEvent.context || feedbackEvent.input || '';
|
package/scripts/github-about.js
CHANGED
|
@@ -12,6 +12,8 @@ const ROOT = path.join(__dirname, '..');
|
|
|
12
12
|
const CONFIG_RELATIVE_PATH = path.join('config', 'github-about.json');
|
|
13
13
|
const LEGACY_REPOSITORY_URL = 'https://github.com/IgorGanapolsky/thumbgate';
|
|
14
14
|
const GITHUB_API_BASE_URL = 'https://api.github.com';
|
|
15
|
+
const DEFAULT_VERIFY_ATTEMPTS = 5;
|
|
16
|
+
const DEFAULT_VERIFY_DELAY_MS = 2000;
|
|
15
17
|
|
|
16
18
|
function readText(root, relativePath) {
|
|
17
19
|
return fs.readFileSync(path.join(root, relativePath), 'utf8');
|
|
@@ -296,6 +298,57 @@ async function fetchLiveGitHubAbout(options = {}) {
|
|
|
296
298
|
};
|
|
297
299
|
}
|
|
298
300
|
|
|
301
|
+
function normalizePositiveInteger(value, fallback) {
|
|
302
|
+
const parsed = Number.parseInt(value, 10);
|
|
303
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function sleep(delayMs) {
|
|
307
|
+
return new Promise((resolve) => {
|
|
308
|
+
setTimeout(resolve, delayMs);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function verifyLiveGitHubAbout(options = {}) {
|
|
313
|
+
const root = options.root || ROOT;
|
|
314
|
+
const expected = options.expected || loadGitHubAboutConfig(root);
|
|
315
|
+
const repo = normalizeText(options.repo) || expected.repo;
|
|
316
|
+
const label = options.label || `Live GitHub About (${repo})`;
|
|
317
|
+
const attempts = normalizePositiveInteger(options.attempts, DEFAULT_VERIFY_ATTEMPTS);
|
|
318
|
+
const delayMs = normalizePositiveInteger(options.delayMs, DEFAULT_VERIFY_DELAY_MS);
|
|
319
|
+
const fetcher = typeof options.fetcher === 'function' ? options.fetcher : fetchLiveGitHubAbout;
|
|
320
|
+
const sleeper = typeof options.sleep === 'function' ? options.sleep : sleep;
|
|
321
|
+
let actual = null;
|
|
322
|
+
let errors = [];
|
|
323
|
+
|
|
324
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
325
|
+
actual = await fetcher({
|
|
326
|
+
root,
|
|
327
|
+
repo,
|
|
328
|
+
token: options.token,
|
|
329
|
+
});
|
|
330
|
+
errors = compareGitHubAbout(expected, actual, label);
|
|
331
|
+
if (errors.length === 0) {
|
|
332
|
+
return {
|
|
333
|
+
ok: true,
|
|
334
|
+
actual,
|
|
335
|
+
attemptsUsed: attempt,
|
|
336
|
+
errors: [],
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (attempt < attempts) {
|
|
340
|
+
await sleeper(delayMs * attempt);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
ok: false,
|
|
346
|
+
actual,
|
|
347
|
+
attemptsUsed: attempts,
|
|
348
|
+
errors,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
299
352
|
async function updateLiveGitHubAbout(options = {}) {
|
|
300
353
|
const about = loadGitHubAboutConfig(options.root || ROOT);
|
|
301
354
|
const repo = normalizeText(options.repo) || about.repo;
|
|
@@ -334,6 +387,8 @@ async function updateLiveGitHubAbout(options = {}) {
|
|
|
334
387
|
}
|
|
335
388
|
|
|
336
389
|
module.exports = {
|
|
390
|
+
DEFAULT_VERIFY_ATTEMPTS,
|
|
391
|
+
DEFAULT_VERIFY_DELAY_MS,
|
|
337
392
|
LEGACY_REPOSITORY_URL,
|
|
338
393
|
buildCanonicalRepoUrls,
|
|
339
394
|
collectLocalGitHubAboutErrors,
|
|
@@ -347,4 +402,5 @@ module.exports = {
|
|
|
347
402
|
normalizeTopics,
|
|
348
403
|
normalizeUrl,
|
|
349
404
|
updateLiveGitHubAbout,
|
|
405
|
+
verifyLiveGitHubAbout,
|
|
350
406
|
};
|