thumbgate 1.1.0 → 1.2.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 +16 -5
- 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 +10 -7
- package/adapters/opencode/opencode.json +1 -1
- package/config/github-about.json +1 -1
- package/package.json +20 -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/opencode-profile/INSTALL.md +1 -1
- package/public/compare.html +302 -0
- package/public/index.html +36 -10
- package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
- package/scripts/ai-search-visibility.js +142 -0
- package/scripts/changeset-check.js +372 -0
- package/scripts/check-congruence.js +7 -4
- package/scripts/computer-use-firewall.js +45 -15
- package/scripts/docker-sandbox-planner.js +208 -0
- package/scripts/github-about.js +56 -0
- package/scripts/operational-integrity.js +7 -1
- package/scripts/published-cli.js +10 -1
- package/scripts/statusline-links.js +238 -0
- package/scripts/statusline.sh +39 -4
- package/scripts/sync-github-about.js +7 -4
- package/scripts/workflow-sentinel.js +83 -35
- package/src/api/server.js +12 -1
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
normalizePosix,
|
|
15
15
|
resolveRepoRoot,
|
|
16
16
|
} = require('./operational-integrity');
|
|
17
|
+
const { buildDockerSandboxPlan } = require('./docker-sandbox-planner');
|
|
17
18
|
const { evaluatePretool } = require('./hybrid-feedback-context');
|
|
18
19
|
|
|
19
20
|
const GOVERNANCE_STATE_PATH = path.join(process.env.HOME || '/tmp', '.thumbgate', 'governance-state.json');
|
|
@@ -523,12 +524,58 @@ function buildEvidence({
|
|
|
523
524
|
return evidence;
|
|
524
525
|
}
|
|
525
526
|
|
|
527
|
+
function addIntegrityRemediations(push, integrity) {
|
|
528
|
+
if (!integrity || !Array.isArray(integrity.blockers)) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const blockerCodes = new Set(integrity.blockers.map((blocker) => blocker.code));
|
|
533
|
+
const remediationSpecs = [
|
|
534
|
+
{
|
|
535
|
+
codes: ['missing_branch_governance'],
|
|
536
|
+
id: 'set_branch_governance',
|
|
537
|
+
title: 'Declare branch governance',
|
|
538
|
+
action: 'Call set_branch_governance with branchName, baseBranch, and PR/release expectations.',
|
|
539
|
+
why: 'Release, merge, and PR workflows need explicit branch state.',
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
codes: ['merge_requires_pr_context'],
|
|
543
|
+
id: 'attach_pr_context',
|
|
544
|
+
title: 'Attach PR context',
|
|
545
|
+
action: 'Update branch governance with prNumber or prUrl before merging.',
|
|
546
|
+
why: 'Merge actions should be tied to one explicit review surface.',
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
codes: ['missing_release_version', 'release_version_mismatch'],
|
|
550
|
+
id: 'align_release_version',
|
|
551
|
+
title: 'Align release version',
|
|
552
|
+
action: 'Set branch governance releaseVersion and verify it matches package.json before publish.',
|
|
553
|
+
why: 'Release metadata should match the artifact being published.',
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
codes: ['publish_requires_base_branch', 'publish_requires_mainline_head'],
|
|
557
|
+
id: 'switch_to_mainline',
|
|
558
|
+
title: 'Run publish from mainline',
|
|
559
|
+
action: `Move the action onto ${integrity.baseBranch || DEFAULT_BASE_BRANCH} after the merge commit exists.`,
|
|
560
|
+
why: 'Publish and tag flows should execute from the protected mainline branch.',
|
|
561
|
+
},
|
|
562
|
+
];
|
|
563
|
+
|
|
564
|
+
for (const remediation of remediationSpecs) {
|
|
565
|
+
if (!remediation.codes.some((code) => blockerCodes.has(code))) {
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
push(remediation.id, remediation.title, remediation.action, remediation.why);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
526
572
|
function buildRemediations({
|
|
527
573
|
integrity,
|
|
528
574
|
taskScopeViolation,
|
|
529
575
|
protectedSurface,
|
|
530
576
|
blastRadius,
|
|
531
577
|
memoryGuard,
|
|
578
|
+
executionSurface,
|
|
532
579
|
}) {
|
|
533
580
|
const remediations = [];
|
|
534
581
|
const seen = new Set();
|
|
@@ -555,41 +602,7 @@ function buildRemediations({
|
|
|
555
602
|
'Protected policy files need an explicit time-bounded approval.'
|
|
556
603
|
);
|
|
557
604
|
}
|
|
558
|
-
|
|
559
|
-
const blockerCodes = new Set(integrity.blockers.map((blocker) => blocker.code));
|
|
560
|
-
if (blockerCodes.has('missing_branch_governance')) {
|
|
561
|
-
push(
|
|
562
|
-
'set_branch_governance',
|
|
563
|
-
'Declare branch governance',
|
|
564
|
-
'Call set_branch_governance with branchName, baseBranch, and PR/release expectations.',
|
|
565
|
-
'Release, merge, and PR workflows need explicit branch state.'
|
|
566
|
-
);
|
|
567
|
-
}
|
|
568
|
-
if (blockerCodes.has('merge_requires_pr_context')) {
|
|
569
|
-
push(
|
|
570
|
-
'attach_pr_context',
|
|
571
|
-
'Attach PR context',
|
|
572
|
-
'Update branch governance with prNumber or prUrl before merging.',
|
|
573
|
-
'Merge actions should be tied to one explicit review surface.'
|
|
574
|
-
);
|
|
575
|
-
}
|
|
576
|
-
if (blockerCodes.has('missing_release_version') || blockerCodes.has('release_version_mismatch')) {
|
|
577
|
-
push(
|
|
578
|
-
'align_release_version',
|
|
579
|
-
'Align release version',
|
|
580
|
-
'Set branch governance releaseVersion and verify it matches package.json before publish.',
|
|
581
|
-
'Release metadata should match the artifact being published.'
|
|
582
|
-
);
|
|
583
|
-
}
|
|
584
|
-
if (blockerCodes.has('publish_requires_base_branch') || blockerCodes.has('publish_requires_mainline_head')) {
|
|
585
|
-
push(
|
|
586
|
-
'switch_to_mainline',
|
|
587
|
-
'Run publish from mainline',
|
|
588
|
-
`Move the action onto ${integrity.baseBranch || DEFAULT_BASE_BRANCH} after the merge commit exists.`,
|
|
589
|
-
'Publish and tag flows should execute from the protected mainline branch.'
|
|
590
|
-
);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
605
|
+
addIntegrityRemediations(push, integrity);
|
|
593
606
|
if (memoryGuard && memoryGuard.mode && memoryGuard.mode !== 'allow') {
|
|
594
607
|
push(
|
|
595
608
|
'retrieve_lessons',
|
|
@@ -606,6 +619,14 @@ function buildRemediations({
|
|
|
606
619
|
'Smaller blast radii are easier to verify and recover.'
|
|
607
620
|
);
|
|
608
621
|
}
|
|
622
|
+
if (executionSurface?.shouldSandbox) {
|
|
623
|
+
push(
|
|
624
|
+
'route_to_docker_sandbox',
|
|
625
|
+
'Route through Docker Sandboxes',
|
|
626
|
+
`Launch the repo in Docker Sandboxes before retrying. Standalone: ${executionSurface.launchers.standalone}. Docker Desktop: ${executionSurface.launchers.dockerDesktop}.`,
|
|
627
|
+
'Isolated execution limits host damage when a high-risk local action goes wrong.'
|
|
628
|
+
);
|
|
629
|
+
}
|
|
609
630
|
|
|
610
631
|
return remediations;
|
|
611
632
|
}
|
|
@@ -615,6 +636,9 @@ function buildReasoning(report) {
|
|
|
615
636
|
`Workflow sentinel risk ${report.band} (${report.riskScore}) for ${report.toolName}.`,
|
|
616
637
|
`Blast radius: ${report.blastRadius.summary}.`,
|
|
617
638
|
];
|
|
639
|
+
if (report.executionSurface?.shouldSandbox) {
|
|
640
|
+
lines.push(`Execution surface: ${report.executionSurface.summary}`);
|
|
641
|
+
}
|
|
618
642
|
for (const driver of report.drivers.slice(0, 4)) {
|
|
619
643
|
lines.push(`Driver ${driver.key} (+${driver.weight}): ${driver.reason}`);
|
|
620
644
|
}
|
|
@@ -624,6 +648,16 @@ function buildReasoning(report) {
|
|
|
624
648
|
return lines;
|
|
625
649
|
}
|
|
626
650
|
|
|
651
|
+
function getSentinelActionType(toolName) {
|
|
652
|
+
if (toolName === 'Bash') {
|
|
653
|
+
return 'shell.exec';
|
|
654
|
+
}
|
|
655
|
+
if (EDIT_LIKE_TOOLS.has(toolName)) {
|
|
656
|
+
return 'file.write';
|
|
657
|
+
}
|
|
658
|
+
return '';
|
|
659
|
+
}
|
|
660
|
+
|
|
627
661
|
function chooseDecision({ riskScore, integrity, memoryGuard, blastRadius, command }) {
|
|
628
662
|
const hasOperationalBlockers = Boolean(integrity && Array.isArray(integrity.blockers) && integrity.blockers.length > 0);
|
|
629
663
|
const destructiveBypass = /\bgit\s+push\b.*(?:--force|-f)\b/i.test(command) || /\bgh\s+pr\s+merge\b.*--admin\b/i.test(command);
|
|
@@ -713,6 +747,18 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
713
747
|
taskScopeViolation,
|
|
714
748
|
protectedSurface: protectedSurfaceForRisk,
|
|
715
749
|
});
|
|
750
|
+
const executionSurface = buildDockerSandboxPlan({
|
|
751
|
+
toolName,
|
|
752
|
+
actionType: getSentinelActionType(toolName),
|
|
753
|
+
command: toolInput.command,
|
|
754
|
+
repoPath,
|
|
755
|
+
affectedFiles,
|
|
756
|
+
riskBand: risk.band,
|
|
757
|
+
riskScore: risk.score,
|
|
758
|
+
requiresNetwork: Boolean(
|
|
759
|
+
/\b(?:curl|wget|gh\s+pr|git\s+push|npm\s+publish|yarn\s+publish|pnpm\s+publish)\b/i.test(toolInput.command || '')
|
|
760
|
+
),
|
|
761
|
+
});
|
|
716
762
|
const decision = chooseDecision({
|
|
717
763
|
riskScore: risk.score,
|
|
718
764
|
integrity,
|
|
@@ -736,6 +782,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
736
782
|
protectedSurface: protectedSurfaceForRisk,
|
|
737
783
|
blastRadius,
|
|
738
784
|
memoryGuard,
|
|
785
|
+
executionSurface,
|
|
739
786
|
});
|
|
740
787
|
const summary = decision === 'allow'
|
|
741
788
|
? 'No predictive workflow blockers detected.'
|
|
@@ -753,6 +800,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
753
800
|
blastRadius,
|
|
754
801
|
evidence,
|
|
755
802
|
remediations,
|
|
803
|
+
executionSurface,
|
|
756
804
|
memoryGuard,
|
|
757
805
|
taskScopeViolation,
|
|
758
806
|
operationalIntegrity: {
|
package/src/api/server.js
CHANGED
|
@@ -169,6 +169,7 @@ const PRO_PAGE_PATH = path.resolve(__dirname, '../../public/pro.html');
|
|
|
169
169
|
const DASHBOARD_PAGE_PATH = path.resolve(__dirname, '../../public/dashboard.html');
|
|
170
170
|
const LESSONS_PAGE_PATH = path.resolve(__dirname, '../../public/lessons.html');
|
|
171
171
|
const GUIDE_PAGE_PATH = path.resolve(__dirname, '../../public/guide.html');
|
|
172
|
+
const COMPARE_PAGE_PATH = path.resolve(__dirname, '../../public/compare.html');
|
|
172
173
|
const LEARN_PAGE_PATH = path.resolve(__dirname, '../../public/learn.html');
|
|
173
174
|
const LEARN_DIR = path.resolve(__dirname, '../../public/learn');
|
|
174
175
|
const BUYER_INTENT_SCRIPT_PATH = path.resolve(__dirname, '../../public/js/buyer-intent.js');
|
|
@@ -2791,6 +2792,16 @@ async function addContext(){
|
|
|
2791
2792
|
return;
|
|
2792
2793
|
}
|
|
2793
2794
|
|
|
2795
|
+
if (isGetLikeRequest && pathname === '/compare') {
|
|
2796
|
+
try {
|
|
2797
|
+
const html = fs.readFileSync(COMPARE_PAGE_PATH, 'utf-8');
|
|
2798
|
+
sendHtml(res, 200, html, {}, { headOnly: isHeadRequest });
|
|
2799
|
+
} catch {
|
|
2800
|
+
sendJson(res, 404, { error: 'Compare page not found' });
|
|
2801
|
+
}
|
|
2802
|
+
return;
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2794
2805
|
if (isGetLikeRequest && pathname === '/blog') {
|
|
2795
2806
|
try {
|
|
2796
2807
|
const blogPath = path.resolve(__dirname, '../../public/blog.html');
|
|
@@ -2848,7 +2859,7 @@ async function addContext(){
|
|
|
2848
2859
|
version: pkg.version,
|
|
2849
2860
|
status: 'ok',
|
|
2850
2861
|
docs: 'https://github.com/IgorGanapolsky/ThumbGate',
|
|
2851
|
-
endpoints: ['/health', '/dashboard', '/guide', '/learn', '/pro', '/v1/feedback/capture', '/v1/feedback/stats', '/v1/feedback/summary', '/v1/lessons/search', '/v1/search', '/v1/dashboard', '/v1/dashboard/render-spec', '/v1/settings/status', '/v1/dpo/export', '/v1/jobs', '/v1/jobs/harness', '/v1/analytics/databricks/export'],
|
|
2862
|
+
endpoints: ['/health', '/dashboard', '/guide', '/compare', '/learn', '/pro', '/v1/feedback/capture', '/v1/feedback/stats', '/v1/feedback/summary', '/v1/lessons/search', '/v1/search', '/v1/dashboard', '/v1/dashboard/render-spec', '/v1/settings/status', '/v1/dpo/export', '/v1/jobs', '/v1/jobs/harness', '/v1/analytics/databricks/export'],
|
|
2852
2863
|
}, {}, {
|
|
2853
2864
|
headOnly: isHeadRequest,
|
|
2854
2865
|
});
|