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.
Files changed (35) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.well-known/mcp/server-card.json +1 -1
  4. package/README.md +16 -5
  5. package/adapters/README.md +1 -1
  6. package/adapters/claude/.mcp.json +2 -2
  7. package/adapters/codex/config.toml +2 -2
  8. package/adapters/mcp/server-stdio.js +10 -7
  9. package/adapters/opencode/opencode.json +1 -1
  10. package/config/github-about.json +1 -1
  11. package/package.json +20 -11
  12. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
  13. package/plugins/claude-codex-bridge/.mcp.json +1 -1
  14. package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
  15. package/plugins/codex-profile/.mcp.json +1 -1
  16. package/plugins/codex-profile/INSTALL.md +1 -1
  17. package/plugins/codex-profile/README.md +1 -1
  18. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
  19. package/plugins/opencode-profile/INSTALL.md +1 -1
  20. package/public/compare.html +302 -0
  21. package/public/index.html +36 -10
  22. package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
  23. package/scripts/ai-search-visibility.js +142 -0
  24. package/scripts/changeset-check.js +372 -0
  25. package/scripts/check-congruence.js +7 -4
  26. package/scripts/computer-use-firewall.js +45 -15
  27. package/scripts/docker-sandbox-planner.js +208 -0
  28. package/scripts/github-about.js +56 -0
  29. package/scripts/operational-integrity.js +7 -1
  30. package/scripts/published-cli.js +10 -1
  31. package/scripts/statusline-links.js +238 -0
  32. package/scripts/statusline.sh +39 -4
  33. package/scripts/sync-github-about.js +7 -4
  34. package/scripts/workflow-sentinel.js +83 -35
  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
- if (integrity && Array.isArray(integrity.blockers)) {
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
  });