thumbgate 1.15.0 → 1.16.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 (129) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +3 -3
  3. package/.well-known/llms.txt +5 -5
  4. package/.well-known/mcp/server-card.json +1 -1
  5. package/README.md +59 -35
  6. package/adapters/chatgpt/openapi.yaml +118 -2
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/mcp/server-stdio.js +210 -84
  9. package/adapters/opencode/opencode.json +1 -1
  10. package/bench/prompt-eval-suite.json +5 -1
  11. package/bin/cli.js +157 -8
  12. package/config/evals/agent-safety-eval.json +338 -22
  13. package/config/gates/routine.json +43 -0
  14. package/config/github-about.json +3 -3
  15. package/config/model-candidates.json +131 -0
  16. package/openapi/openapi.yaml +118 -2
  17. package/package.json +55 -48
  18. package/public/blog.html +7 -7
  19. package/public/codex-plugin.html +6 -6
  20. package/public/compare.html +29 -23
  21. package/public/dashboard.html +82 -10
  22. package/public/guide.html +28 -28
  23. package/public/index.html +216 -98
  24. package/public/learn.html +50 -22
  25. package/public/lessons.html +1 -1
  26. package/public/numbers.html +17 -17
  27. package/public/pro.html +82 -18
  28. package/scripts/agent-audit-trace.js +55 -0
  29. package/scripts/agent-memory-lifecycle.js +96 -0
  30. package/scripts/agent-readiness-plan.js +118 -0
  31. package/scripts/agentic-data-pipeline.js +21 -1
  32. package/scripts/agents-sdk-sandbox-plan.js +57 -0
  33. package/scripts/ai-org-governance.js +98 -0
  34. package/scripts/ai-search-distribution.js +43 -0
  35. package/scripts/artifact-agent-plan.js +81 -0
  36. package/scripts/billing.js +27 -8
  37. package/scripts/cli-schema.js +18 -2
  38. package/scripts/code-mode-mcp-plan.js +71 -0
  39. package/scripts/context-engine.js +1 -2
  40. package/scripts/context-manager.js +4 -1
  41. package/scripts/dashboard-render-spec.js +1 -1
  42. package/scripts/dashboard.js +275 -9
  43. package/scripts/decision-journal.js +13 -3
  44. package/scripts/document-workflow-governance.js +62 -0
  45. package/scripts/enterprise-agent-rollout.js +34 -0
  46. package/scripts/experience-replay-governance.js +69 -0
  47. package/scripts/export-hf-dataset.js +1 -1
  48. package/scripts/feedback-loop.js +92 -4
  49. package/scripts/feedback-to-rules.js +17 -23
  50. package/scripts/gates-engine.js +4 -6
  51. package/scripts/growth-campaigns.js +49 -0
  52. package/scripts/harness-selector.js +16 -4
  53. package/scripts/hybrid-supervisor-agent.js +64 -0
  54. package/scripts/inference-cache-policy.js +72 -0
  55. package/scripts/inference-economics.js +53 -0
  56. package/scripts/internal-agent-bootstrap.js +12 -2
  57. package/scripts/knowledge-layer-plan.js +108 -0
  58. package/scripts/lesson-inference.js +183 -44
  59. package/scripts/lesson-search.js +4 -1
  60. package/scripts/llm-client.js +157 -26
  61. package/scripts/mailer/resend-mailer.js +112 -1
  62. package/scripts/mcp-transport-strategy.js +66 -0
  63. package/scripts/memory-store-governance.js +60 -0
  64. package/scripts/meta-agent-loop.js +7 -13
  65. package/scripts/model-access-eligibility.js +38 -0
  66. package/scripts/model-migration-readiness.js +55 -0
  67. package/scripts/operational-integrity.js +96 -3
  68. package/scripts/otel-declarative-config.js +56 -0
  69. package/scripts/perplexity-client.js +1 -1
  70. package/scripts/post-training-governance.js +34 -0
  71. package/scripts/private-core-boundary.js +72 -0
  72. package/scripts/production-agent-readiness.js +40 -0
  73. package/scripts/prompt-eval.js +564 -32
  74. package/scripts/prompt-programs.js +93 -0
  75. package/scripts/provider-action-normalizer.js +585 -0
  76. package/scripts/scaling-law-claims.js +60 -0
  77. package/scripts/security-scanner.js +1 -1
  78. package/scripts/self-distill-agent.js +7 -32
  79. package/scripts/seo-gsd.js +232 -55
  80. package/scripts/skill-rag-router.js +53 -0
  81. package/scripts/spec-gate.js +1 -1
  82. package/scripts/student-consistent-training.js +73 -0
  83. package/scripts/synthetic-data-provenance.js +98 -0
  84. package/scripts/task-context-result.js +81 -0
  85. package/scripts/telemetry-analytics.js +149 -0
  86. package/scripts/thompson-sampling.js +2 -2
  87. package/scripts/token-savings.js +7 -6
  88. package/scripts/token-tco.js +46 -0
  89. package/scripts/tool-registry.js +63 -3
  90. package/scripts/verification-loop.js +10 -1
  91. package/scripts/verifier-scoring.js +71 -0
  92. package/scripts/workflow-sentinel.js +284 -28
  93. package/scripts/workspace-agent-routines.js +118 -0
  94. package/src/api/server.js +381 -120
  95. package/scripts/analytics-report.js +0 -328
  96. package/scripts/autonomous-workflow.js +0 -377
  97. package/scripts/billing-setup.js +0 -109
  98. package/scripts/creator-campaigns.js +0 -239
  99. package/scripts/cross-encoder-reranker.js +0 -235
  100. package/scripts/daemon-manager.js +0 -108
  101. package/scripts/decision-trace.js +0 -354
  102. package/scripts/delegation-runtime.js +0 -896
  103. package/scripts/dispatch-brief.js +0 -159
  104. package/scripts/distribution-surfaces.js +0 -110
  105. package/scripts/feedback-history-distiller.js +0 -382
  106. package/scripts/funnel-analytics.js +0 -35
  107. package/scripts/history-distiller.js +0 -200
  108. package/scripts/hosted-job-launcher.js +0 -256
  109. package/scripts/intent-router.js +0 -392
  110. package/scripts/lesson-reranker.js +0 -263
  111. package/scripts/lesson-retrieval.js +0 -148
  112. package/scripts/managed-lesson-agent.js +0 -183
  113. package/scripts/operational-dashboard.js +0 -103
  114. package/scripts/operational-summary.js +0 -129
  115. package/scripts/operator-artifacts.js +0 -608
  116. package/scripts/optimize-context.js +0 -17
  117. package/scripts/org-dashboard.js +0 -206
  118. package/scripts/partner-orchestration.js +0 -146
  119. package/scripts/predictive-insights.js +0 -356
  120. package/scripts/pulse.js +0 -80
  121. package/scripts/reflector-agent.js +0 -221
  122. package/scripts/sales-pipeline.js +0 -681
  123. package/scripts/session-episode-store.js +0 -329
  124. package/scripts/session-health-sensor.js +0 -242
  125. package/scripts/session-report.js +0 -120
  126. package/scripts/swarm-coordinator.js +0 -81
  127. package/scripts/tool-kpi-tracker.js +0 -12
  128. package/scripts/webhook-delivery.js +0 -62
  129. package/scripts/workflow-sprint-intake.js +0 -475
@@ -17,6 +17,11 @@ const {
17
17
  const { buildDockerSandboxPlan } = require('./docker-sandbox-planner');
18
18
  const { evaluatePretool } = require('./hybrid-feedback-context');
19
19
  const { getInterventionRecommendation } = require('./intervention-policy');
20
+ const {
21
+ buildCostControl,
22
+ buildWorkflowControl,
23
+ normalizeProviderAction,
24
+ } = require('./provider-action-normalizer');
20
25
 
21
26
  const GOVERNANCE_STATE_PATH = path.join(process.env.HOME || '/tmp', '.thumbgate', 'governance-state.json');
22
27
  const DEFAULT_PROTECTED_FILE_GLOBS = [
@@ -33,7 +38,7 @@ const DEFAULT_PROTECTED_FILE_GLOBS = [
33
38
  'config/gates/**',
34
39
  ];
35
40
  const EDIT_LIKE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit']);
36
- const HIGH_RISK_BASH_PATTERN = /\b(?:git\s+(?:add|commit|push)|gh\s+pr\s+(?:create|merge)|gh\s+release\s+create|npm\s+publish|yarn\s+publish|pnpm\s+publish|rm\s+-rf)\b/i;
41
+ const HIGH_RISK_BASH_PATTERN = /\b(?:git\s+(?:add|commit|push)|gh\s+(?:pr\s+(?:create|merge)|workflow\s+run|release\s+create)|npm\s+publish|yarn\s+publish|pnpm\s+publish|rm\s+-rf)\b/i;
37
42
 
38
43
  const SURFACE_RULES = [
39
44
  { key: 'policy', pattern: /^(?:AGENTS\.md|CLAUDE(?:\.local)?\.md|GEMINI\.md|config\/gates\/|config\/mcp-allowlists\.json|scripts\/tool-registry\.js)/ },
@@ -392,6 +397,8 @@ function scoreRisk({
392
397
  blastRadius,
393
398
  taskScopeViolation,
394
399
  protectedSurface,
400
+ costControl,
401
+ workflowControl,
395
402
  }) {
396
403
  const drivers = [];
397
404
  const commandInfo = classifyCommand(toolInput.command || '');
@@ -399,8 +406,25 @@ function scoreRisk({
399
406
  if (isHighRiskAction(toolName, toolInput, affectedFiles)) {
400
407
  addDriver(drivers, 'high_risk_action', 0.18, 'Command or edit pattern is classified as high risk.');
401
408
  }
402
- if (commandInfo.isPrCreate || commandInfo.isPrMerge || commandInfo.isPublish || commandInfo.isReleaseCreate || commandInfo.isTagCreate) {
403
- addDriver(drivers, 'governed_command', 0.16, 'Action touches PR, release, or publish workflow state.');
409
+ if (commandInfo.isPrCreate
410
+ || commandInfo.isPrMerge
411
+ || commandInfo.isWorkflowRun
412
+ || commandInfo.isPublish
413
+ || commandInfo.isReleaseCreate
414
+ || commandInfo.isTagCreate) {
415
+ addDriver(drivers, 'governed_command', 0.16, 'Action touches PR, workflow dispatch, release, or publish workflow state.');
416
+ }
417
+ if (commandInfo.isWorkflowRun) {
418
+ addDriver(
419
+ drivers,
420
+ 'workflow_dispatch',
421
+ 0.2,
422
+ 'GitHub Actions workflow dispatch can trigger environment-specific builds or releases.',
423
+ {
424
+ workflowName: commandInfo.workflowName,
425
+ workflowRef: commandInfo.workflowRef,
426
+ }
427
+ );
404
428
  }
405
429
  if (/\bgit\s+push\b.*(?:--force|-f)\b/i.test(commandInfo.text)) {
406
430
  addDriver(drivers, 'force_push', 0.5, 'Force push predicts destructive branch history rewrite.');
@@ -465,6 +489,56 @@ function scoreRisk({
465
489
  { blockers: integrity.blockers.map((blocker) => blocker.code) }
466
490
  );
467
491
  }
492
+ if (costControl && costControl.mode && costControl.mode !== 'allow') {
493
+ addDriver(
494
+ drivers,
495
+ 'cost_control',
496
+ costControl.mode === 'block' ? 0.5 : 0.18,
497
+ costControl.mode === 'block'
498
+ ? 'Estimated model usage exceeds the configured per-action budget.'
499
+ : 'Estimated model usage is high enough to require review.',
500
+ { mode: costControl.mode, reasons: costControl.reasons }
501
+ );
502
+ }
503
+ if (workflowControl && workflowControl.workflow && workflowControl.workflow.pattern !== 'single_action') {
504
+ const workflow = workflowControl.workflow;
505
+ if (workflow.pattern === 'agent') {
506
+ addDriver(
507
+ drivers,
508
+ 'open_ended_agent',
509
+ workflow.hasInspectionEvidence ? 0.14 : 0.28,
510
+ workflow.hasInspectionEvidence
511
+ ? 'Open-ended agent action declares inspection evidence.'
512
+ : 'Open-ended agent action lacks explicit environment-inspection evidence.',
513
+ { pattern: workflow.pattern, toolCount: workflow.toolCount }
514
+ );
515
+ } else if (workflow.pattern === 'parallelization') {
516
+ addDriver(
517
+ drivers,
518
+ 'parallel_workflow',
519
+ workflow.branchCount > 1 ? 0.12 : 0.06,
520
+ 'Parallel workflow fan-out increases aggregate cost and review surface.',
521
+ { branchCount: workflow.branchCount }
522
+ );
523
+ } else {
524
+ addDriver(
525
+ drivers,
526
+ 'workflow_pattern',
527
+ 0.06,
528
+ `Provider action declared ${workflow.pattern} workflow pattern.`,
529
+ { pattern: workflow.pattern, stepCount: workflow.stepCount, routeCount: workflow.routeCount }
530
+ );
531
+ }
532
+ if (workflow.requiresInspection && !workflow.hasInspectionEvidence) {
533
+ addDriver(
534
+ drivers,
535
+ 'missing_environment_inspection',
536
+ workflow.pattern === 'agent' ? 0.22 : 0.14,
537
+ 'Action has no declared way to observe whether the tool/workflow result succeeded.',
538
+ { pattern: workflow.pattern }
539
+ );
540
+ }
541
+ }
468
542
  if (memoryGuard && memoryGuard.mode && memoryGuard.mode !== 'allow') {
469
543
  addDriver(
470
544
  drivers,
@@ -535,8 +609,28 @@ function buildEvidence({
535
609
  blastRadius,
536
610
  taskScopeViolation,
537
611
  protectedSurface,
612
+ normalizedAction,
613
+ costControl,
614
+ workflowControl,
538
615
  }) {
539
616
  const evidence = [];
617
+ if (normalizedAction && normalizedAction.provider !== 'unknown') {
618
+ evidence.push(
619
+ `Provider action normalized from ${normalizedAction.provider}: ${normalizedAction.actionType} / ${normalizedAction.intent}.`
620
+ );
621
+ }
622
+ if (costControl && costControl.mode && costControl.mode !== 'allow') {
623
+ evidence.push(`Cost control ${costControl.mode}: ${costControl.reasons.join(' ')}`);
624
+ }
625
+ if (workflowControl && workflowControl.workflow && workflowControl.workflow.pattern !== 'single_action') {
626
+ const workflow = workflowControl.workflow;
627
+ evidence.push(
628
+ `Workflow pattern ${workflow.pattern}: ${workflow.branchCount} branch(es), ${workflow.stepCount} step(s), ${workflow.toolCount} tool(s), inspection evidence ${workflow.hasInspectionEvidence ? 'present' : 'missing'}.`
629
+ );
630
+ if (workflowControl.mode !== 'allow') {
631
+ evidence.push(`Workflow control ${workflowControl.mode}: ${workflowControl.reasons.join(' ')}`);
632
+ }
633
+ }
540
634
  if (memoryGuard && memoryGuard.mode && memoryGuard.mode !== 'allow') {
541
635
  evidence.push(`Memory guard predicted ${memoryGuard.mode}: ${memoryGuard.reason}`);
542
636
  }
@@ -595,6 +689,23 @@ function addIntegrityRemediations(push, integrity) {
595
689
  action: 'Update branch governance with prNumber or prUrl before merging.',
596
690
  why: 'Merge actions should be tied to one explicit review surface.',
597
691
  },
692
+ {
693
+ codes: [
694
+ 'missing_workflow_dispatch_evidence',
695
+ 'missing_workflow_environment',
696
+ 'missing_workflow_name',
697
+ 'workflow_name_mismatch',
698
+ 'missing_workflow_ref',
699
+ 'workflow_ref_mismatch',
700
+ 'missing_workflow_sha',
701
+ 'workflow_sha_mismatch',
702
+ 'missing_workflow_job',
703
+ ],
704
+ id: 'verify_workflow_dispatch',
705
+ title: 'Verify workflow dispatch target',
706
+ action: 'Set branch governance workflowDispatch with environment, workflow, ref, sha, and expected job before running gh workflow run.',
707
+ why: 'Environment-specific build dispatches must prove the workflow file, branch/ref, HEAD SHA, and job name before execution.',
708
+ },
598
709
  {
599
710
  codes: ['missing_release_version', 'release_version_mismatch'],
600
711
  id: 'align_release_version',
@@ -627,6 +738,8 @@ function buildRemediations({
627
738
  memoryGuard,
628
739
  learnedPolicy,
629
740
  executionSurface,
741
+ costControl,
742
+ workflowControl,
630
743
  }) {
631
744
  const remediations = [];
632
745
  const seen = new Set();
@@ -696,6 +809,30 @@ function buildRemediations({
696
809
  'Isolated execution limits host damage when a high-risk local action goes wrong.'
697
810
  );
698
811
  }
812
+ if (costControl && costControl.mode && costControl.mode !== 'allow') {
813
+ push(
814
+ 'reduce_model_budget',
815
+ 'Reduce model budget before execution',
816
+ 'Trim context, lower max output, batch the work, or split the action before retrying.',
817
+ 'High token or cost estimates should be reviewed before the model/tool loop continues.'
818
+ );
819
+ }
820
+ if (workflowControl && workflowControl.mode && workflowControl.mode !== 'allow') {
821
+ push(
822
+ 'add_environment_inspection',
823
+ 'Add environment inspection evidence',
824
+ 'Declare how the agent will observe results after action: read-before-write, screenshots, API response checks, test commands, or generated-output validation.',
825
+ 'Open-ended agents and inspection-sensitive workflows need a concrete feedback signal before they can be trusted in production.'
826
+ );
827
+ }
828
+ if (workflowControl?.workflow?.pattern === 'agent') {
829
+ push(
830
+ 'prefer_workflow_when_possible',
831
+ 'Prefer a predefined workflow when possible',
832
+ 'If the task shape is known, split it into explicit workflow steps instead of letting an open-ended agent improvise.',
833
+ 'Predefined workflows are easier to test, evaluate, budget, and audit than open-ended agents.'
834
+ );
835
+ }
699
836
 
700
837
  return remediations;
701
838
  }
@@ -709,6 +846,9 @@ function buildReasoning(report) {
709
846
  lines.push(
710
847
  `Decision control: ${report.decisionControl.decisionOwner} owns a ${report.decisionControl.reversibility} action via ${report.decisionControl.executionMode}.`
711
848
  );
849
+ if (report.decisionControl.deliberation?.required) {
850
+ lines.push(`Deliberation policy: ${report.decisionControl.deliberation.mode} before final approval.`);
851
+ }
712
852
  }
713
853
  if (report.learnedPolicy && report.learnedPolicy.enabled && report.learnedPolicy.prediction) {
714
854
  lines.push(
@@ -718,6 +858,14 @@ function buildReasoning(report) {
718
858
  if (report.executionSurface?.shouldSandbox) {
719
859
  lines.push(`Execution surface: ${report.executionSurface.summary}`);
720
860
  }
861
+ if (report.costControl && report.costControl.mode !== 'allow') {
862
+ lines.push(`Cost control: ${report.costControl.mode} — ${report.costControl.reasons.join(' ')}`);
863
+ }
864
+ if (report.workflowControl && report.workflowControl.workflow.pattern !== 'single_action') {
865
+ lines.push(
866
+ `Workflow control: ${report.workflowControl.mode} for ${report.workflowControl.workflow.pattern} with inspection ${report.workflowControl.workflow.hasInspectionEvidence ? 'present' : 'missing'}.`
867
+ );
868
+ }
721
869
  for (const driver of report.drivers.slice(0, 4)) {
722
870
  lines.push(`Driver ${driver.key} (+${driver.weight}): ${driver.reason}`);
723
871
  }
@@ -763,6 +911,52 @@ function classifyReversibility({ command, blastRadius, integrity, protectedSurfa
763
911
  return 'two_way_door';
764
912
  }
765
913
 
914
+ function buildDeliberationPolicy({
915
+ executionMode,
916
+ reversibility,
917
+ risk,
918
+ hasOperationalBlockers,
919
+ }) {
920
+ const riskBand = risk && risk.band ? risk.band : 'very_low';
921
+ const riskScore = risk && typeof risk.score === 'number' ? risk.score : 0;
922
+ const needsConsistencyCheck = executionMode === 'blocked'
923
+ || reversibility === 'one_way_door'
924
+ || riskBand === 'very_high'
925
+ || riskScore >= 0.72
926
+ || hasOperationalBlockers;
927
+ const required = executionMode !== 'auto_execute' || riskScore >= 0.45 || hasOperationalBlockers;
928
+ const mode = needsConsistencyCheck
929
+ ? 'reason_then_consistency_check'
930
+ : required
931
+ ? 'reason_then_decide'
932
+ : 'brief_rationale';
933
+
934
+ return {
935
+ required,
936
+ mode,
937
+ minSentences: needsConsistencyCheck ? 4 : required ? 2 : 1,
938
+ summarizeOnly: true,
939
+ instruction: required
940
+ ? 'Pause before answering, compare safety, reversibility, prior-failure, and evidence signals, then summarize only the decision evidence.'
941
+ : 'Give a brief evidence summary before approving fast-path execution.',
942
+ consistencyCheck: {
943
+ required: needsConsistencyCheck,
944
+ variants: needsConsistencyCheck
945
+ ? [
946
+ 'Re-evaluate the same action from the failure-prevention perspective.',
947
+ 'Re-evaluate the same action from the reversibility and rollback perspective.',
948
+ 'Re-evaluate the same action from the user-intent and evidence perspective.',
949
+ ]
950
+ : [],
951
+ requiredAgreement: needsConsistencyCheck ? 'all_variants_same_execution_mode' : 'not_required',
952
+ onDisagreement: 'checkpoint_required',
953
+ rationale: needsConsistencyCheck
954
+ ? 'High-risk and one-way-door actions should be stable under paraphrased evaluation before an agent proceeds.'
955
+ : 'Low-risk fast-path actions do not require paraphrase stability checks.',
956
+ },
957
+ };
958
+ }
959
+
766
960
  function buildDecisionControl({
767
961
  decision,
768
962
  risk,
@@ -770,6 +964,8 @@ function buildDecisionControl({
770
964
  blastRadius,
771
965
  integrity,
772
966
  protectedSurface,
967
+ costControl,
968
+ workflowControl,
773
969
  }) {
774
970
  const reversibility = classifyReversibility({
775
971
  command,
@@ -778,9 +974,15 @@ function buildDecisionControl({
778
974
  protectedSurface,
779
975
  });
780
976
  const hasOperationalBlockers = Boolean(integrity && Array.isArray(integrity.blockers) && integrity.blockers.length > 0);
977
+ const hasCostWarning = Boolean(costControl && costControl.mode === 'warn');
978
+ const hasCostBlock = Boolean(costControl && costControl.mode === 'block');
979
+ const hasWorkflowWarning = Boolean(workflowControl && workflowControl.mode === 'warn');
980
+ const hasWorkflowBlock = Boolean(workflowControl && workflowControl.mode === 'block');
781
981
  const requiresCheckpoint = decision === 'warn'
782
- || (decision === 'allow' && (reversibility !== 'two_way_door' || hasOperationalBlockers));
982
+ || (decision === 'allow' && (reversibility !== 'two_way_door' || hasOperationalBlockers || hasCostWarning || hasWorkflowWarning));
783
983
  const executionMode = decision === 'deny'
984
+ || hasCostBlock
985
+ || hasWorkflowBlock
784
986
  ? 'blocked'
785
987
  : requiresCheckpoint
786
988
  ? 'checkpoint_required'
@@ -792,12 +994,19 @@ function buildDecisionControl({
792
994
  ? 'shared'
793
995
  : 'human'
794
996
  : 'agent';
997
+ const deliberation = buildDeliberationPolicy({
998
+ executionMode,
999
+ reversibility,
1000
+ risk,
1001
+ hasOperationalBlockers,
1002
+ });
795
1003
 
796
1004
  return {
797
1005
  executionMode,
798
1006
  decisionOwner,
799
1007
  reversibility,
800
- requiresHumanApproval: executionMode === 'checkpoint_required' && decisionOwner !== 'agent',
1008
+ deliberation,
1009
+ requiresHumanApproval: (executionMode === 'checkpoint_required' && decisionOwner !== 'agent') || hasCostBlock || hasWorkflowBlock,
801
1010
  recommendedAction: executionMode === 'blocked'
802
1011
  ? 'halt'
803
1012
  : executionMode === 'checkpoint_required'
@@ -811,8 +1020,14 @@ function buildDecisionControl({
811
1020
  };
812
1021
  }
813
1022
 
814
- function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blastRadius, command }) {
1023
+ function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blastRadius, command, costControl, workflowControl }) {
815
1024
  const hasOperationalBlockers = Boolean(integrity && Array.isArray(integrity.blockers) && integrity.blockers.length > 0);
1025
+ if (costControl && costControl.mode === 'block') {
1026
+ return 'deny';
1027
+ }
1028
+ if (workflowControl && workflowControl.mode === 'block') {
1029
+ return 'deny';
1030
+ }
816
1031
  const destructiveBypass = /\bgit\s+push\b.*(?:--force|-f)\b/i.test(command) || /\bgh\s+pr\s+merge\b.*--admin\b/i.test(command);
817
1032
  const learnedPrediction = learnedPolicy && learnedPolicy.enabled ? learnedPolicy.prediction : null;
818
1033
  const learnedHardStop = Boolean(
@@ -859,52 +1074,78 @@ function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blas
859
1074
  if (destructiveBypass || learnedHardStop || repeatedHighBlast || (hasOperationalBlockers && riskScore >= 0.72) || riskScore >= 0.86) {
860
1075
  return 'deny';
861
1076
  }
862
- if (riskScore >= 0.45 || (learnedWarning && riskScore >= 0.3) || (learnedRecall && riskScore >= 0.34)) {
1077
+ if ((workflowControl && workflowControl.mode === 'warn') || (costControl && costControl.mode === 'warn') || riskScore >= 0.45 || (learnedWarning && riskScore >= 0.3) || (learnedRecall && riskScore >= 0.34)) {
863
1078
  return 'warn';
864
1079
  }
865
1080
  return 'allow';
866
1081
  }
867
1082
 
868
1083
  function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
1084
+ const normalizedAction = options.normalizedAction || normalizeProviderAction({
1085
+ provider: options.provider,
1086
+ model: options.model,
1087
+ toolName,
1088
+ toolInput,
1089
+ command: toolInput.command,
1090
+ filePath: toolInput.file_path || toolInput.filePath || toolInput.path,
1091
+ changedFiles: toolInput.changed_files || toolInput.changedFiles,
1092
+ usage: options.usage,
1093
+ tokenEstimate: options.tokenEstimate,
1094
+ costUsd: options.costUsd,
1095
+ });
1096
+ const normalizedToolName = normalizedAction.toolName || toolName;
1097
+ const normalizedToolInput = {
1098
+ ...toolInput,
1099
+ ...normalizedAction.toolInput,
1100
+ };
1101
+ if (normalizedAction.command && !normalizedToolInput.command) {
1102
+ normalizedToolInput.command = normalizedAction.command;
1103
+ }
1104
+ if (normalizedAction.affectedFiles.length > 0 && !normalizedToolInput.changed_files && !normalizedToolInput.changedFiles) {
1105
+ normalizedToolInput.changed_files = normalizedAction.affectedFiles;
1106
+ }
1107
+ const costControl = buildCostControl(normalizedAction, options.budget || toolInput.budget || {});
1108
+ const workflowControl = buildWorkflowControl(normalizedAction, options.workflowPolicy || toolInput.workflowPolicy || options.budget || toolInput.budget || {});
869
1109
  const governanceState = options.governanceState || loadGovernanceState();
870
- const repoPath = options.repoPath || toolInput.repoPath || toolInput.cwd || process.cwd();
1110
+ const repoPath = options.repoPath || normalizedToolInput.repoPath || normalizedToolInput.cwd || process.cwd();
871
1111
  const repoRoot = resolveRepoRoot(repoPath) || null;
872
1112
  const affectedFiles = Array.isArray(options.affectedFiles)
873
1113
  ? options.affectedFiles.map((filePath) => normalizePosix(filePath)).filter(Boolean)
874
- : collectAffectedFiles(toolName, toolInput, repoRoot);
875
- const highRiskAction = isHighRiskAction(toolName, toolInput, affectedFiles);
1114
+ : collectAffectedFiles(normalizedToolName, normalizedToolInput, repoRoot);
1115
+ const highRiskAction = isHighRiskAction(normalizedToolName, normalizedToolInput, affectedFiles);
876
1116
  const baseBranch = options.baseBranch
877
1117
  || (governanceState.branchGovernance && governanceState.branchGovernance.baseBranch)
878
- || toolInput.baseBranch
1118
+ || normalizedToolInput.baseBranch
879
1119
  || DEFAULT_BASE_BRANCH;
880
1120
  const integrity = evaluateOperationalIntegrity({
881
1121
  repoPath,
882
1122
  baseBranch,
883
- command: toolInput.command,
1123
+ command: normalizedToolInput.command,
884
1124
  changedFiles: affectedFiles,
1125
+ headSha: options.headSha || toolInput.headSha,
885
1126
  requirePrForReleaseSensitive: options.requirePrForReleaseSensitive === true,
886
1127
  requireVersionNotBehindBase: options.requireVersionNotBehindBase === true,
887
1128
  branchGovernance: governanceState.branchGovernance,
888
1129
  });
889
1130
  const taskScopeViolation = buildTaskScopeViolation(governanceState.taskScope, affectedFiles);
890
1131
  const protectedSurface = buildProtectedSurface(governanceState, affectedFiles);
891
- const protectedSurfaceForRisk = isProtectedApprovalRelevant(toolName, toolInput)
1132
+ const protectedSurfaceForRisk = isProtectedApprovalRelevant(normalizedToolName, normalizedToolInput)
892
1133
  ? protectedSurface
893
1134
  : {
894
1135
  ...protectedSurface,
895
1136
  protectedFiles: [],
896
1137
  unapprovedProtectedFiles: [],
897
1138
  };
898
- const rawMemoryGuard = options.memoryGuard || evaluatePretool(toolName, JSON.stringify({
899
- toolName,
900
- command: toolInput.command || null,
901
- filePath: toolInput.file_path || toolInput.filePath || toolInput.path || null,
1139
+ const rawMemoryGuard = options.memoryGuard || evaluatePretool(normalizedToolName, JSON.stringify({
1140
+ toolName: normalizedToolName,
1141
+ command: normalizedToolInput.command || null,
1142
+ filePath: normalizedToolInput.file_path || normalizedToolInput.filePath || normalizedToolInput.path || null,
902
1143
  affectedFiles,
903
1144
  }), options.feedbackOptions || {});
904
1145
  const memoryGuard = normalizeMemoryGuardForSentinel(rawMemoryGuard, highRiskAction);
905
1146
  const learnedPolicy = getInterventionRecommendation({
906
- toolName,
907
- command: toolInput.command || '',
1147
+ toolName: normalizedToolName,
1148
+ command: normalizedToolInput.command || '',
908
1149
  affectedFiles,
909
1150
  integrity,
910
1151
  memoryGuard,
@@ -922,8 +1163,8 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
922
1163
  protectedSurface: protectedSurfaceForRisk,
923
1164
  });
924
1165
  const risk = scoreRisk({
925
- toolName,
926
- toolInput,
1166
+ toolName: normalizedToolName,
1167
+ toolInput: normalizedToolInput,
927
1168
  affectedFiles,
928
1169
  integrity,
929
1170
  memoryGuard,
@@ -931,17 +1172,19 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
931
1172
  blastRadius,
932
1173
  taskScopeViolation,
933
1174
  protectedSurface: protectedSurfaceForRisk,
1175
+ costControl,
1176
+ workflowControl,
934
1177
  });
935
1178
  const executionSurface = buildDockerSandboxPlan({
936
- toolName,
937
- actionType: getSentinelActionType(toolName),
938
- command: toolInput.command,
1179
+ toolName: normalizedToolName,
1180
+ actionType: getSentinelActionType(normalizedToolName),
1181
+ command: normalizedToolInput.command,
939
1182
  repoPath,
940
1183
  affectedFiles,
941
1184
  riskBand: risk.band,
942
1185
  riskScore: risk.score,
943
1186
  requiresNetwork: Boolean(
944
- /\b(?:curl|wget|gh\s+pr|git\s+push|npm\s+publish|yarn\s+publish|pnpm\s+publish)\b/i.test(toolInput.command || '')
1187
+ /\b(?:curl|wget|gh\s+pr|git\s+push|npm\s+publish|yarn\s+publish|pnpm\s+publish)\b/i.test(normalizedToolInput.command || '')
945
1188
  ),
946
1189
  });
947
1190
  const decision = chooseDecision({
@@ -953,7 +1196,9 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
953
1196
  ...blastRadius,
954
1197
  unapprovedProtectedFiles: protectedSurfaceForRisk.unapprovedProtectedFiles.length,
955
1198
  },
956
- command: toolInput.command || '',
1199
+ command: normalizedToolInput.command || '',
1200
+ costControl,
1201
+ workflowControl,
957
1202
  });
958
1203
  const evidence = buildEvidence({
959
1204
  integrity,
@@ -962,6 +1207,9 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
962
1207
  blastRadius,
963
1208
  taskScopeViolation,
964
1209
  protectedSurface: protectedSurfaceForRisk,
1210
+ normalizedAction,
1211
+ costControl,
1212
+ workflowControl,
965
1213
  });
966
1214
  const remediations = buildRemediations({
967
1215
  integrity,
@@ -971,6 +1219,8 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
971
1219
  memoryGuard,
972
1220
  learnedPolicy,
973
1221
  executionSurface,
1222
+ costControl,
1223
+ workflowControl,
974
1224
  });
975
1225
  const summary = decision === 'allow'
976
1226
  ? 'No predictive workflow blockers detected.'
@@ -979,7 +1229,10 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
979
1229
  : 'Predicted workflow failure before execution.';
980
1230
  const report = {
981
1231
  sentinelVersion: 'workflow-sentinel-v2',
982
- toolName,
1232
+ toolName: normalizedToolName,
1233
+ normalizedAction,
1234
+ costControl,
1235
+ workflowControl,
983
1236
  decision,
984
1237
  riskScore: risk.score,
985
1238
  band: risk.band,
@@ -1005,13 +1258,15 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
1005
1258
  report.decisionControl = buildDecisionControl({
1006
1259
  decision,
1007
1260
  risk,
1008
- command: toolInput.command || '',
1261
+ command: normalizedToolInput.command || '',
1009
1262
  blastRadius: {
1010
1263
  ...blastRadius,
1011
1264
  unapprovedProtectedFiles: protectedSurfaceForRisk.unapprovedProtectedFiles.length,
1012
1265
  },
1013
1266
  integrity,
1014
1267
  protectedSurface: protectedSurfaceForRisk,
1268
+ costControl,
1269
+ workflowControl,
1015
1270
  });
1016
1271
  report.reasoning = buildReasoning(report);
1017
1272
  return report;
@@ -1019,6 +1274,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
1019
1274
 
1020
1275
  module.exports = {
1021
1276
  buildDecisionControl,
1277
+ buildDeliberationPolicy,
1022
1278
  DEFAULT_PROTECTED_FILE_GLOBS,
1023
1279
  buildBlastRadius,
1024
1280
  buildEvidence,
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const ROUTINE_TYPES = Object.freeze({
5
+ security_audit: {
6
+ schedule: 'daily',
7
+ trigger: 'schedule',
8
+ approval: 'pull_request_required',
9
+ checks: ['npm test', 'npm run test:coverage', 'npm run self-heal:check'],
10
+ },
11
+ post_merge_hygiene: {
12
+ schedule: 'after_pr_merge',
13
+ trigger: 'webhook',
14
+ approval: 'pull_request_required',
15
+ checks: ['npm run self-heal:check', 'npm run prove:automation'],
16
+ },
17
+ data_table_refresh: {
18
+ schedule: 'daily',
19
+ trigger: 'schedule',
20
+ approval: 'human_approval_for_schema_changes',
21
+ checks: ['npm run test:data-pipeline', 'npm run self-heal:check'],
22
+ },
23
+ portfolio_research: {
24
+ schedule: 'hourly_market_window',
25
+ trigger: 'schedule',
26
+ approval: 'always_ask_before_trade_or_publish',
27
+ checks: ['risk_limit_check', 'source_reconciliation', 'decision_journal_append'],
28
+ },
29
+ });
30
+
31
+ function normalizeText(value) {
32
+ if (value === undefined || value === null) return '';
33
+ return String(value).trim();
34
+ }
35
+
36
+ function normalizeConnector(connector) {
37
+ if (typeof connector === 'string') {
38
+ return {
39
+ name: connector,
40
+ mode: 'read',
41
+ auth: 'user_or_service_account',
42
+ approval: 'always_ask_for_write',
43
+ };
44
+ }
45
+ return {
46
+ name: normalizeText(connector?.name) || 'custom',
47
+ mode: normalizeText(connector?.mode) || 'read',
48
+ auth: normalizeText(connector?.auth) || 'user_or_service_account',
49
+ approval: normalizeText(connector?.approval) || 'always_ask_for_write',
50
+ };
51
+ }
52
+
53
+ function buildWorkspaceAgentRoutine(input = {}) {
54
+ const type = normalizeText(input.type) || 'post_merge_hygiene';
55
+ const defaults = ROUTINE_TYPES[type] || ROUTINE_TYPES.post_merge_hygiene;
56
+ const name = normalizeText(input.name) || `ThumbGate ${type.replaceAll('_', ' ')}`;
57
+ const connectors = Array.isArray(input.connectors) ? input.connectors.map(normalizeConnector) : [];
58
+ const checks = Array.isArray(input.checks) && input.checks.length > 0 ? input.checks : defaults.checks;
59
+ const routine = {
60
+ name,
61
+ type,
62
+ repository: normalizeText(input.repository) || 'IgorGanapolsky/ThumbGate',
63
+ trigger: normalizeText(input.trigger) || defaults.trigger,
64
+ schedule: normalizeText(input.schedule) || defaults.schedule,
65
+ modelPolicy: normalizeText(input.modelPolicy) || 'use_best_available_for_audit_then_small_model_for_summaries',
66
+ connectors,
67
+ permissionMode: normalizeText(input.permissionMode) || 'least_privilege',
68
+ approvalPolicy: normalizeText(input.approvalPolicy) || defaults.approval,
69
+ branchPolicy: 'feature_branch_only',
70
+ checks,
71
+ evidenceRequired: [
72
+ 'branch_name',
73
+ 'commit_sha',
74
+ 'test_output',
75
+ 'decision_journal_entry',
76
+ 'pull_request_url_or_no_change_reason',
77
+ ],
78
+ blockedActions: [
79
+ 'direct_main_write',
80
+ 'secret_persistence',
81
+ 'credentialed_write_without_approval',
82
+ 'schema_migration_without_approval',
83
+ 'trade_or_portfolio_action_without_risk_limits',
84
+ ],
85
+ };
86
+
87
+ return {
88
+ routine,
89
+ prompt: [
90
+ `Run ${routine.name} for ${routine.repository}.`,
91
+ 'Use ThumbGate before every risky tool action.',
92
+ 'Create a feature branch for any code change.',
93
+ `Run checks: ${routine.checks.join(', ')}.`,
94
+ 'Append evidence to the decision journal.',
95
+ 'Open a pull request only when changes and proof exist.',
96
+ 'Stop and report blockers instead of bypassing checks.',
97
+ ].join(' '),
98
+ };
99
+ }
100
+
101
+ function buildWorkspaceAgentDirectory(input = {}) {
102
+ const repository = normalizeText(input.repository) || 'IgorGanapolsky/ThumbGate';
103
+ return {
104
+ generatedAt: normalizeText(input.generatedAt) || new Date().toISOString(),
105
+ directory: [
106
+ buildWorkspaceAgentRoutine({ type: 'security_audit', repository }).routine,
107
+ buildWorkspaceAgentRoutine({ type: 'post_merge_hygiene', repository }).routine,
108
+ buildWorkspaceAgentRoutine({ type: 'data_table_refresh', repository }).routine,
109
+ buildWorkspaceAgentRoutine({ type: 'portfolio_research', repository }).routine,
110
+ ],
111
+ };
112
+ }
113
+
114
+ module.exports = {
115
+ ROUTINE_TYPES,
116
+ buildWorkspaceAgentDirectory,
117
+ buildWorkspaceAgentRoutine,
118
+ };