thumbgate 1.34.3 → 1.37.1

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 (200) hide show
  1. package/.agents/skills/cyberstrike-compare-not-clone/SKILL.md +36 -0
  2. package/.agents/skills/gitlab-sandbox-allowlist-not-trust/SKILL.md +77 -0
  3. package/.agents/skills/jit-harness-compare-not-clone/SKILL.md +34 -0
  4. package/.agents/skills/openui-catalog-compose-honesty/SKILL.md +64 -0
  5. package/.agents/skills/zvec-grep-compare-not-clone/SKILL.md +34 -0
  6. package/.claude-plugin/plugin.json +1 -1
  7. package/.well-known/llms.txt +1 -0
  8. package/.well-known/mcp/server-card.json +1 -1
  9. package/CONTRIBUTING.md +95 -0
  10. package/README.md +195 -632
  11. package/THIRD_PARTY_NOTICES.md +89 -0
  12. package/adapters/claude/.mcp.json +2 -2
  13. package/adapters/forge/forge.yaml +3 -3
  14. package/adapters/future-agi/.mcp.json +8 -0
  15. package/adapters/future-agi/FUTURE_AGI.md +23 -0
  16. package/adapters/future-agi/config.toml +3 -0
  17. package/adapters/future-agi/future-agi-bridge.js +9 -0
  18. package/adapters/future-agi/opencode.json +8 -0
  19. package/adapters/herdr/herdr-plugin.toml +18 -0
  20. package/adapters/mcp/server-stdio.js +238 -25
  21. package/adapters/opencode/opencode.json +1 -1
  22. package/adapters/workos/WORKOS.md +52 -0
  23. package/bin/cli.js +373 -5
  24. package/bin/futureagi-bridge +9 -0
  25. package/config/gate-templates.json +653 -4
  26. package/config/gates/actor-critic-audit.json +34 -0
  27. package/config/gates/default.json +21 -2
  28. package/config/gates/five-walls-governance.json +34 -0
  29. package/config/gates/future-agi-guardrails.json +34 -0
  30. package/config/gates/radware-threat-defense-2026.json +61 -0
  31. package/config/gates/simatree-data-governance.json +33 -0
  32. package/config/mcp-allowlists.json +4 -0
  33. package/config/merge-quality-checks.json +10 -1
  34. package/config/model-candidates.json +382 -24
  35. package/config/model-tiers.json +18 -0
  36. package/config/post-deploy-marketing-pages.json +10 -0
  37. package/config/progressive/01-wire-only.json +11 -0
  38. package/config/progressive/02-dashboard-empty-ok.json +10 -0
  39. package/config/progressive/03-one-lesson.json +10 -0
  40. package/config/progressive/04-warn-fires.json +11 -0
  41. package/config/progressive/05-strict-optional.json +11 -0
  42. package/config/progressive/README.md +15 -0
  43. package/config/schemas/broker-execution-receipt.schema.json +139 -0
  44. package/config/schemas/provider-execution-attestation-v1.schema.json +58 -0
  45. package/conformance/provider-attestation/vectors.json +320 -0
  46. package/docs/specs/provider-execution-attestation-v1.md +69 -0
  47. package/openapi/openapi.yaml +15 -0
  48. package/package.json +401 -147
  49. package/public/about.html +2 -2
  50. package/public/ai-malpractice-prevention.html +7 -7
  51. package/public/blog/a-10-dollar-vps-is-not-a-computer.html +143 -0
  52. package/public/blog/a-receipt-is-not-world-state.html +388 -0
  53. package/public/blog/git-at-agent-scale.html +374 -0
  54. package/public/blog/no-llm-in-the-gate.html +133 -0
  55. package/public/blog.html +80 -0
  56. package/public/case-studies.html +16 -1
  57. package/public/compare.html +28 -0
  58. package/public/diagnostic.html +216 -7
  59. package/public/docs/connectors.html +39 -0
  60. package/public/federal.html +2 -2
  61. package/public/founders.html +639 -0
  62. package/public/index.html +87 -9
  63. package/public/install.html +8 -8
  64. package/public/learn.html +39 -0
  65. package/public/numbers.html +2 -2
  66. package/public/peter.html +310 -0
  67. package/public/platform-partners.html +119 -0
  68. package/public/pricing.html +24 -3
  69. package/public/privacy.html +117 -0
  70. package/public/pro.html +17 -0
  71. package/public/support.html +62 -0
  72. package/public/terms.html +130 -0
  73. package/public/third-party-notices.html +95 -0
  74. package/public/yt.html +351 -0
  75. package/scripts/action-receipts.js +133 -3
  76. package/scripts/adaptive-governance-arena.js +349 -0
  77. package/scripts/admin-override.js +205 -0
  78. package/scripts/agent-action-inventory.js +869 -0
  79. package/scripts/agent-audit-trace.js +42 -2
  80. package/scripts/agent-egress-policy.js +1117 -0
  81. package/scripts/agent-memory-lifecycle.js +141 -2
  82. package/scripts/agent-operations-planner.js +441 -1
  83. package/scripts/agent-readiness.js +68 -0
  84. package/scripts/agent-security-central.js +647 -0
  85. package/scripts/allowlist-bridge-honesty.js +417 -0
  86. package/scripts/async-job-runner.js +102 -11
  87. package/scripts/audit-trail.js +212 -0
  88. package/scripts/auto-promote-gates.js +178 -27
  89. package/scripts/billing.js +1 -1
  90. package/scripts/broker-execution-receipts.js +719 -0
  91. package/scripts/budget-aware-gates-proof.js +423 -0
  92. package/scripts/claude-feedback-sync.js +29 -3
  93. package/scripts/claw-harness-production.js +237 -0
  94. package/scripts/cli-progress.js +111 -0
  95. package/scripts/cli-schema.js +163 -1
  96. package/scripts/codex-runbook-flywheel.js +318 -0
  97. package/scripts/context-footprint.js +186 -0
  98. package/scripts/contextfs.js +143 -61
  99. package/scripts/dashboard-limits.js +27 -0
  100. package/scripts/dashboard.js +279 -9
  101. package/scripts/deepseek-v4-runtime-guardrails.js +72 -6
  102. package/scripts/docker-sandbox-planner.js +18 -0
  103. package/scripts/double-blind-eval-protocol.js +252 -0
  104. package/scripts/edotenv-rl-gateway.js +259 -0
  105. package/scripts/ensure-production-search-corpus.js +162 -0
  106. package/scripts/eval-holdout.js +311 -0
  107. package/scripts/feedback-aggregate.js +21 -2
  108. package/scripts/feedback-loop.js +87 -5
  109. package/scripts/feedback-quality.js +9 -0
  110. package/scripts/file-ledger-lock.js +4 -1
  111. package/scripts/financial-control-plane.js +41 -1
  112. package/scripts/find-dormant-requires.js +118 -0
  113. package/scripts/fs-utils.js +84 -8
  114. package/scripts/gate-stats.js +2 -2
  115. package/scripts/gates-engine.js +859 -58
  116. package/scripts/generate-case-study-outreach.js +24 -15
  117. package/scripts/git-at-scale.js +628 -0
  118. package/scripts/governance-conflict-audit.js +1650 -0
  119. package/scripts/governance-difficulty-curriculum.js +328 -0
  120. package/scripts/graphrag-retrieval.js +275 -0
  121. package/scripts/gurobi-optimizer.js +324 -0
  122. package/scripts/gurobi_optimizer.py +485 -0
  123. package/scripts/harness-selector.js +82 -1
  124. package/scripts/hidden-entry-points.js +284 -0
  125. package/scripts/human-escalation.js +199 -1
  126. package/scripts/hybrid-feedback-context.js +152 -19
  127. package/scripts/intent-governed-execution.js +602 -0
  128. package/scripts/intervention-policy.js +123 -20
  129. package/scripts/jit-harness-compose.js +628 -0
  130. package/scripts/jsonl-watcher.js +10 -0
  131. package/scripts/lesson-embedding-index.js +95 -12
  132. package/scripts/lesson-retrieval.js +105 -19
  133. package/scripts/local-model-profile.js +19 -2
  134. package/scripts/mailer/resend-mailer.js +1 -1
  135. package/scripts/matryoshka-embedding.js +235 -0
  136. package/scripts/mcp-oauth.js +42 -4
  137. package/scripts/mcp-session-handles.js +1016 -0
  138. package/scripts/mcp-wiring-doctor.js +314 -0
  139. package/scripts/memory-firewall.js +115 -2
  140. package/scripts/memory-scope-readiness.js +299 -0
  141. package/scripts/memory-vs-rag-route.js +161 -0
  142. package/scripts/model-tier-router.js +148 -21
  143. package/scripts/nvidia-specdecode-al-doctor.js +536 -0
  144. package/scripts/openui-catalog-compose-honesty.js +593 -0
  145. package/scripts/operational-integrity.js +19 -1
  146. package/scripts/override-audit.js +213 -0
  147. package/scripts/package-manager-honesty-doctor.js +458 -0
  148. package/scripts/pr-manager.js +63 -1
  149. package/scripts/prove-herdr-adapter.js +52 -0
  150. package/scripts/prove-memory-pyramid-and-symbolic-canvas.js +95 -0
  151. package/scripts/prove-workos.js +73 -0
  152. package/scripts/provider-attestation-conformance.js +192 -0
  153. package/scripts/provider-receipt-contract.js +136 -0
  154. package/scripts/qwen38-max-cost-optimizer.js +401 -0
  155. package/scripts/radware-threat-defense.js +280 -0
  156. package/scripts/rag-embedding-identity.js +221 -0
  157. package/scripts/rag-precision-guardrails.js +112 -2
  158. package/scripts/remote-feedback-capture.js +159 -0
  159. package/scripts/research-agent-harness.js +256 -0
  160. package/scripts/rsi-safety-hillclimb.js +200 -0
  161. package/scripts/rule-sprawl.js +188 -0
  162. package/scripts/schedule-manager.js +147 -0
  163. package/scripts/self-heal.js +8 -0
  164. package/scripts/session-lease.js +415 -0
  165. package/scripts/simatree-data-governance.js +347 -0
  166. package/scripts/slo-alert-engine.js +172 -7
  167. package/scripts/solver-parity.js +539 -0
  168. package/scripts/stealth-memory-injection-gate.js +333 -0
  169. package/scripts/switchyard-router.js +366 -0
  170. package/scripts/telemetry-analytics.js +84 -27
  171. package/scripts/temporal-decay-weighting.js +138 -0
  172. package/scripts/test-all.js +165 -0
  173. package/scripts/token-savings.js +42 -0
  174. package/scripts/tool-kpi-tracker.js +108 -5
  175. package/scripts/tool-registry.js +193 -5
  176. package/scripts/universal-claim-evaluator.js +14 -2
  177. package/scripts/vector-store.js +279 -9
  178. package/scripts/workflow-notebook.js +391 -0
  179. package/scripts/workflow-sentinel.js +111 -12
  180. package/scripts/workos-production-guard.js +260 -0
  181. package/scripts/workspace-search-route.js +515 -0
  182. package/server.json +2 -2
  183. package/src/agent-identity-boundary.js +76 -0
  184. package/src/agent-retrieval-cache.js +155 -0
  185. package/src/alert-noise-ledger.js +502 -0
  186. package/src/api/server.js +802 -185
  187. package/src/git-fast-cache.js +220 -0
  188. package/src/git-wal-sync.js +156 -0
  189. package/src/hash-anchored-edit.js +82 -0
  190. package/src/hermes-platform-protocol.js +475 -0
  191. package/src/hermes-sync-plane.js +241 -0
  192. package/src/index.js +30 -1
  193. package/src/iso42001-compliance-guard.js +97 -0
  194. package/src/latency-budget.js +244 -0
  195. package/src/mcp-writeguard.js +316 -0
  196. package/src/miminions-adapter.js +106 -0
  197. package/src/pipeline-compass.js +104 -0
  198. package/src/ppl-alert-pipeline.js +284 -0
  199. package/src/rendezvous-router.js +90 -0
  200. package/src/security-questionnaire.js +195 -0
@@ -3,6 +3,29 @@
3
3
 
4
4
  const MEMORY_TYPES = new Set(['episodic', 'semantic', 'procedural', 'preference', 'working']);
5
5
  const MEMORY_SCOPES = new Set(['task', 'session', 'user', 'project', 'org']);
6
+ const DEFAULT_MAX_PROMOTE_CHARS = 12_000;
7
+ const TRANSPORT_TURN_RE = /^(user|assistant|system|human|tool|model)\s*:/gim;
8
+ const TRANSPORT_META_RE = /\b(conversation[_-]?id|message[_-]?id|tool_call_id|chat_completion|role\s*:\s*["']?(user|assistant|system))\b/i;
9
+
10
+ function isTransportTranscript(text, options = {}) {
11
+ const content = normalizeText(text);
12
+ if (!content) return false;
13
+ const minTurns = Number.isFinite(options.minTurns) ? options.minTurns : 3;
14
+ const turnMarkers = (content.match(TRANSPORT_TURN_RE) || []).length;
15
+ if (turnMarkers >= minTurns) return true;
16
+ const lineCount = content.split(/\n/).length;
17
+ if (lineCount >= 40 && turnMarkers >= 2) return true;
18
+ if (TRANSPORT_META_RE.test(content) && content.length >= 1500) return true;
19
+ if (/\b(raw transcript|conversation dump|full chat log)\b/i.test(content)) return true;
20
+ return false;
21
+ }
22
+
23
+ function isOversizedMemoryBlob(text, options = {}) {
24
+ const content = normalizeText(text);
25
+ const maxChars = Number.isFinite(options.maxChars) ? options.maxChars : DEFAULT_MAX_PROMOTE_CHARS;
26
+ return content.length > maxChars;
27
+ }
28
+
6
29
  const HIGH_RISK_TERMS = new Set([
7
30
  'billing',
8
31
  'checkout',
@@ -260,6 +283,9 @@ function buildMemoryLifecyclePolicy(input = {}) {
260
283
  semanticWeight: 0.5,
261
284
  outcomeWeight: 0.25,
262
285
  requireSourceAnchors: true,
286
+ rejectTransportTranscripts: true,
287
+ rejectOversizedBlobs: true,
288
+ maxPromoteChars: DEFAULT_MAX_PROMOTE_CHARS,
263
289
  },
264
290
  privacy: {
265
291
  piiScanRequired: true,
@@ -269,12 +295,16 @@ function buildMemoryLifecyclePolicy(input = {}) {
269
295
  };
270
296
  }
271
297
 
298
+
272
299
  function evaluateMemoryPromotion(memory = {}, policy = buildMemoryLifecyclePolicy()) {
273
300
  const type = normalizeMemoryType(memory.type);
274
- const content = normalizeText(memory.content);
301
+ const content = normalizeText(memory.content || collectMemoryText(memory));
275
302
  const source = normalizeText(memory.source);
276
303
  const outcome = normalizeText(memory.outcome);
277
304
  const issues = [];
305
+ const maxChars = Number.isFinite(memory.maxPromoteChars)
306
+ ? memory.maxPromoteChars
307
+ : DEFAULT_MAX_PROMOTE_CHARS;
278
308
 
279
309
  if (!content) issues.push('missing_content');
280
310
  if (!source) issues.push('missing_source_anchor');
@@ -285,19 +315,127 @@ function evaluateMemoryPromotion(memory = {}, policy = buildMemoryLifecyclePolic
285
315
  if (type === 'preference' && memory.explicitUserSignal !== true) {
286
316
  issues.push('preference_without_explicit_signal');
287
317
  }
318
+ if (type === 'working' && memory.allowWorkingPromotion !== true) {
319
+ issues.push('working_memory_not_promotable');
320
+ }
321
+ if (isTransportTranscript(content)) {
322
+ issues.push('transport_transcript');
323
+ }
324
+ if (isOversizedMemoryBlob(content, { maxChars })) {
325
+ issues.push('oversized_blob');
326
+ }
327
+
328
+ const hardRetrievalBlocks = new Set([
329
+ 'secret_like_content',
330
+ 'transport_transcript',
331
+ 'oversized_blob',
332
+ ]);
333
+ const retrievalBlocked = issues.some((issue) => hardRetrievalBlocks.has(issue));
288
334
 
289
335
  return {
290
336
  type,
291
337
  decision: issues.length === 0 ? 'promote' : 'hold',
292
338
  issues,
293
- retrievalEligible: issues.length === 0 || !issues.includes('secret_like_content'),
339
+ retrievalEligible: !retrievalBlocked && (issues.length === 0 || !issues.includes('missing_content')),
294
340
  policyVersion: policy.generatedAt,
295
341
  };
296
342
  }
297
343
 
344
+ const PYRAMID_LAYERS = {
345
+ L0_CONVERSATION: 'L0_CONVERSATION',
346
+ L1_ATOM: 'L1_ATOM',
347
+ L2_SCENARIO: 'L2_SCENARIO',
348
+ L3_PERSONA_SOP: 'L3_PERSONA_SOP',
349
+ };
350
+
351
+ const SOP_LAYER_REGEX = /\b(sop|policy|prevention rule|guardrail|rule|directive|mandate|never allow|always require)\b/;
352
+ const WORKFLOW_LAYER_REGEX = /\b(workflow|scenario|pipeline|multi-step|sequence|playbook|recipe|lifecycle)\b/;
353
+ const LESSON_LAYER_REGEX = /\b(fact|lesson|observation|result|fix|patch|metric|verified)\b/;
354
+
355
+ function isSopLayer(type, tags, text) {
356
+ if (type === 'preference') return true;
357
+ if (tags.has('sop') || tags.has('rule') || tags.has('policy') || tags.has('guardrail')) return true;
358
+ return SOP_LAYER_REGEX.test(text);
359
+ }
360
+
361
+ function isWorkflowLayer(type, tags, text) {
362
+ if (type === 'procedural') return true;
363
+ if (tags.has('workflow') || tags.has('scenario') || tags.has('pattern')) return true;
364
+ return WORKFLOW_LAYER_REGEX.test(text);
365
+ }
366
+
367
+ function isAtomLayer(type, tags, text, memory) {
368
+ if (type === 'semantic') return true;
369
+ if (tags.has('fact') || tags.has('lesson') || tags.has('atom')) return true;
370
+ if (Boolean(memory.whatWentWrong) || Boolean(memory.whatWorked)) return true;
371
+ return LESSON_LAYER_REGEX.test(text);
372
+ }
373
+
374
+ function classifyPyramidLayer(memory = {}) {
375
+ const explicit = normalizeText(memory.pyramidLayer || memory.layer).toUpperCase();
376
+ if (PYRAMID_LAYERS[explicit]) return PYRAMID_LAYERS[explicit];
377
+
378
+ const type = normalizeMemoryType(memory.type);
379
+ const text = collectMemoryText(memory).toLowerCase();
380
+ const tagList = Array.isArray(memory.tags) ? memory.tags.map((t) => normalizeText(t).toLowerCase()) : [];
381
+ const tags = new Set(tagList);
382
+
383
+ if (isSopLayer(type, tags, text)) return PYRAMID_LAYERS.L3_PERSONA_SOP;
384
+ if (isWorkflowLayer(type, tags, text)) return PYRAMID_LAYERS.L2_SCENARIO;
385
+ if (isAtomLayer(type, tags, text, memory)) return PYRAMID_LAYERS.L1_ATOM;
386
+ return PYRAMID_LAYERS.L0_CONVERSATION;
387
+ }
388
+
389
+
390
+ function distillMemoryPyramid(memories = [], options = {}) {
391
+ const safeMemories = Array.isArray(memories) ? memories : [];
392
+ const layers = {
393
+ [PYRAMID_LAYERS.L3_PERSONA_SOP]: [],
394
+ [PYRAMID_LAYERS.L2_SCENARIO]: [],
395
+ [PYRAMID_LAYERS.L1_ATOM]: [],
396
+ [PYRAMID_LAYERS.L0_CONVERSATION]: [],
397
+ };
398
+
399
+ for (const item of safeMemories) {
400
+ const layer = classifyPyramidLayer(item);
401
+ layers[layer].push({
402
+ ...item,
403
+ pyramidLayer: layer,
404
+ });
405
+ }
406
+
407
+ const totalCount = safeMemories.length;
408
+ const charsPerToken = Math.max(1, Number(options.charsPerToken) || 4);
409
+
410
+ const layerStats = {};
411
+ for (const [layerKey, items] of Object.entries(layers)) {
412
+ const rawContent = items.map((m) => collectMemoryText(m)).join('\n');
413
+ const estimatedTokens = Math.ceil(rawContent.length / charsPerToken);
414
+ layerStats[layerKey] = {
415
+ count: items.length,
416
+ estimatedTokens,
417
+ sharePercent: totalCount > 0 ? Number(((items.length / totalCount) * 100).toFixed(1)) : 0,
418
+ };
419
+ }
420
+
421
+ return {
422
+ kind: 'semantic-memory-pyramid',
423
+ totalMemories: totalCount,
424
+ layers: layerStats,
425
+ pyramidItems: layers,
426
+ };
427
+ }
428
+
298
429
  module.exports = {
430
+ isTransportTranscript,
431
+ isOversizedMemoryBlob,
432
+ DEFAULT_MAX_PROMOTE_CHARS,
433
+
434
+ PYRAMID_LAYERS,
299
435
  buildMemoryLifecyclePolicy,
300
436
  buildMemoryLifecycleView,
437
+ classifyPyramidLayer,
438
+ distillMemoryPyramid,
301
439
  evaluateMemoryPromotion,
302
440
  extractMemoryEntities,
303
441
  inferMemoryScope,
@@ -305,3 +443,4 @@ module.exports = {
305
443
  scoreHybridMemoryMatch,
306
444
  scoreMemoryDecay,
307
445
  };
446
+
@@ -1,12 +1,426 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
+ const path = require('node:path');
5
+
4
6
  function toList(value) {
5
7
  if (Array.isArray(value)) return value.map(String).map((entry) => entry.trim()).filter(Boolean);
6
8
  if (typeof value === 'string') return value.split(',').map((entry) => entry.trim()).filter(Boolean);
7
9
  return [];
8
10
  }
9
11
 
12
+ const BUSINESS_FUNCTION_AGENT_SOURCE = 'https://events.aiunleashedglobalsummit.com/aias-registration-yt';
13
+ const BUSINESS_FUNCTIONS = Object.freeze([
14
+ Object.freeze({ id: 'lead_generation', label: 'Lead Generation' }),
15
+ Object.freeze({ id: 'content_creation', label: 'Content Creation' }),
16
+ Object.freeze({ id: 'product_validation', label: 'Product Development & Validation' }),
17
+ Object.freeze({ id: 'marketing', label: 'Personalized Marketing' }),
18
+ Object.freeze({ id: 'sales_conversion', label: 'Sales & Conversion' }),
19
+ Object.freeze({ id: 'operations', label: 'Operations & Automation' }),
20
+ ]);
21
+ const BUSINESS_FUNCTION_HANDOFF_EDGES = Object.freeze([
22
+ Object.freeze({ from: 'content_creation', to: 'marketing' }),
23
+ Object.freeze({ from: 'product_validation', to: 'marketing' }),
24
+ Object.freeze({ from: 'marketing', to: 'lead_generation' }),
25
+ Object.freeze({ from: 'lead_generation', to: 'sales_conversion' }),
26
+ Object.freeze({ from: 'sales_conversion', to: 'operations' }),
27
+ ]);
28
+ const BUSINESS_FUNCTION_IDS = new Set(BUSINESS_FUNCTIONS.map((entry) => entry.id));
29
+ const BUSINESS_FUNCTION_EDGE_IDS = new Set(
30
+ BUSINESS_FUNCTION_HANDOFF_EDGES.map((edge) => `${edge.from}:${edge.to}`),
31
+ );
32
+ const BUSINESS_FUNCTION_RISKS = new Set(['low', 'medium', 'high']);
33
+ const INTERNAL_BUSINESS_ACTION = /^internal:/i;
34
+
35
+ function cleanText(value) {
36
+ return value === undefined || value === null ? '' : String(value).trim();
37
+ }
38
+
39
+ function cleanUniqueList(value) {
40
+ return Array.from(new Set(toList(value)));
41
+ }
42
+
43
+ function finiteNumber(value) {
44
+ // Reject booleans/arrays/objects — Number(true)===1 and Number([])===0 would
45
+ // silently pass zero-cost / ranking gates.
46
+ if (typeof value === 'number') {
47
+ return Number.isFinite(value) ? value : null;
48
+ }
49
+ if (typeof value !== 'string') return null;
50
+ const trimmed = value.trim();
51
+ if (!trimmed) return null;
52
+ const parsed = Number(trimmed);
53
+ return Number.isFinite(parsed) ? parsed : null;
54
+ }
55
+
56
+ function roundNumber(value, decimals = 2) {
57
+ const factor = 10 ** decimals;
58
+ return Math.round((value + Number.EPSILON) * factor) / factor;
59
+ }
60
+
61
+ function hasMeasuredBaseline(baseline) {
62
+ if (!baseline || typeof baseline !== 'object') return false;
63
+ const measuredAt = cleanText(baseline.measuredAt);
64
+ return finiteNumber(baseline.value) !== null
65
+ && Boolean(cleanText(baseline.unit))
66
+ && Boolean(cleanText(baseline.source))
67
+ && Boolean(measuredAt)
68
+ && Number.isFinite(Date.parse(measuredAt));
69
+ }
70
+
71
+ function failedBusinessRules(rules) {
72
+ return rules.filter(([valid]) => !valid).map(([, issue]) => issue);
73
+ }
74
+
75
+ function calculateBusinessCandidateEstimates(values) {
76
+ const {
77
+ estimatedMonthlyValueUsd,
78
+ recurringMonthlyCostUsd,
79
+ implementationCostUsd,
80
+ confidence,
81
+ } = values;
82
+ const valueInputsValid = estimatedMonthlyValueUsd !== null
83
+ && estimatedMonthlyValueUsd > 0
84
+ && recurringMonthlyCostUsd !== null
85
+ && recurringMonthlyCostUsd >= 0
86
+ && confidence !== null
87
+ && confidence > 0
88
+ && confidence <= 1;
89
+ const expectedMonthlyNetValueUsd = valueInputsValid
90
+ ? roundNumber((estimatedMonthlyValueUsd * confidence) - recurringMonthlyCostUsd)
91
+ : null;
92
+ const costInputsValid = implementationCostUsd !== null
93
+ && implementationCostUsd > 0
94
+ && expectedMonthlyNetValueUsd !== null
95
+ && expectedMonthlyNetValueUsd > 0;
96
+
97
+ return {
98
+ expectedMonthlyNetValueUsd,
99
+ paybackMonths: costInputsValid
100
+ ? roundNumber(implementationCostUsd / expectedMonthlyNetValueUsd, 3)
101
+ : null,
102
+ };
103
+ }
104
+
105
+ function normalizeBusinessFunctionCandidate(input = {}) {
106
+ const functionId = cleanText(input.functionId);
107
+ const estimatedMonthlyValueUsd = finiteNumber(input.estimatedMonthlyValueUsd);
108
+ const recurringMonthlyCostUsd = finiteNumber(input.recurringMonthlyCostUsd);
109
+ const implementationCostUsd = finiteNumber(input.implementationCostUsd);
110
+ const implementationHours = finiteNumber(input.implementationHours);
111
+ const confidence = finiteNumber(input.confidence);
112
+ const risk = cleanText(input.risk).toLowerCase();
113
+ const evidence = cleanUniqueList(input.evidence);
114
+ const estimates = calculateBusinessCandidateEstimates({
115
+ estimatedMonthlyValueUsd,
116
+ recurringMonthlyCostUsd,
117
+ implementationCostUsd,
118
+ confidence,
119
+ });
120
+ const issues = failedBusinessRules([
121
+ [BUSINESS_FUNCTION_IDS.has(functionId), 'unsupported_business_function'],
122
+ [Boolean(cleanText(input.objective)), 'missing_objective'],
123
+ [Boolean(cleanText(input.primaryKpi)), 'missing_primary_kpi'],
124
+ [Boolean(cleanText(input.inputSource)), 'missing_input_source'],
125
+ [hasMeasuredBaseline(input.baseline), 'missing_measured_baseline'],
126
+ [estimatedMonthlyValueUsd !== null && estimatedMonthlyValueUsd > 0, 'missing_estimated_monthly_value_usd'],
127
+ [recurringMonthlyCostUsd !== null && recurringMonthlyCostUsd >= 0, 'missing_recurring_monthly_cost_usd'],
128
+ [implementationCostUsd !== null && implementationCostUsd > 0, 'missing_implementation_cost_usd'],
129
+ [implementationHours !== null && implementationHours > 0, 'missing_implementation_hours'],
130
+ [confidence !== null && confidence > 0 && confidence <= 1, 'invalid_confidence'],
131
+ [BUSINESS_FUNCTION_RISKS.has(risk), 'invalid_risk'],
132
+ [evidence.length > 0, 'missing_evidence'],
133
+ [
134
+ estimates.expectedMonthlyNetValueUsd === null || estimates.expectedMonthlyNetValueUsd > 0,
135
+ 'non_positive_expected_monthly_net_value',
136
+ ],
137
+ ]);
138
+
139
+ return {
140
+ agentId: cleanText(input.agentId) || functionId || 'unnamed-agent',
141
+ functionId: functionId || null,
142
+ objective: cleanText(input.objective) || null,
143
+ primaryKpi: cleanText(input.primaryKpi) || null,
144
+ inputSource: cleanText(input.inputSource) || null,
145
+ baseline: hasMeasuredBaseline(input.baseline) ? {
146
+ value: finiteNumber(input.baseline.value),
147
+ unit: cleanText(input.baseline.unit),
148
+ measuredAt: new Date(input.baseline.measuredAt).toISOString(),
149
+ source: cleanText(input.baseline.source),
150
+ } : null,
151
+ estimates: {
152
+ estimatedMonthlyValueUsd,
153
+ recurringMonthlyCostUsd,
154
+ implementationCostUsd,
155
+ implementationHours,
156
+ confidence,
157
+ ...estimates,
158
+ },
159
+ risk: risk || null,
160
+ evidence,
161
+ status: issues.length === 0 ? 'evidence_complete' : 'insufficient_evidence',
162
+ launchGate: risk === 'high' ? 'human_review_required' : 'standard_governance',
163
+ issues: Array.from(new Set(issues)),
164
+ };
165
+ }
166
+
167
+ function rejectDuplicateBusinessAgentIds(candidates) {
168
+ const counts = new Map();
169
+ for (const candidate of candidates) {
170
+ counts.set(candidate.agentId, (counts.get(candidate.agentId) || 0) + 1);
171
+ }
172
+ return candidates.map((candidate) => (
173
+ counts.get(candidate.agentId) > 1
174
+ ? {
175
+ ...candidate,
176
+ status: 'insufficient_evidence',
177
+ issues: Array.from(new Set([...candidate.issues, 'duplicate_agent_id'])),
178
+ }
179
+ : candidate
180
+ ));
181
+ }
182
+
183
+ function rankBusinessFunctionAgents(candidates = []) {
184
+ const normalized = Array.isArray(candidates)
185
+ ? rejectDuplicateBusinessAgentIds(candidates.map(normalizeBusinessFunctionCandidate))
186
+ : [];
187
+ const eligible = normalized
188
+ .filter((candidate) => candidate.status === 'evidence_complete')
189
+ .sort((left, right) => (
190
+ left.estimates.paybackMonths - right.estimates.paybackMonths
191
+ || right.estimates.expectedMonthlyNetValueUsd - left.estimates.expectedMonthlyNetValueUsd
192
+ || left.estimates.implementationHours - right.estimates.implementationHours
193
+ || left.agentId.localeCompare(right.agentId)
194
+ ));
195
+ const ranks = new Map(eligible.map((candidate, index) => [candidate.agentId, index + 1]));
196
+ const rankedCandidates = normalized.map((candidate) => ({
197
+ ...candidate,
198
+ rank: ranks.get(candidate.agentId) || null,
199
+ })).sort((left, right) => {
200
+ if (left.rank === null && right.rank === null) return left.agentId.localeCompare(right.agentId);
201
+ if (left.rank === null) return 1;
202
+ if (right.rank === null) return -1;
203
+ return left.rank - right.rank;
204
+ });
205
+
206
+ return {
207
+ schemaVersion: 'business-function-agent-ranking-v1',
208
+ status: eligible.length > 0 ? 'ranked_estimates' : 'insufficient_evidence',
209
+ source: {
210
+ url: BUSINESS_FUNCTION_AGENT_SOURCE,
211
+ claimsVerifiedByThumbGate: false,
212
+ },
213
+ methodology: {
214
+ estimated: true,
215
+ formula: 'paybackMonths = implementationCostUsd / ((estimatedMonthlyValueUsd * confidence) - recurringMonthlyCostUsd)',
216
+ warning: 'Planning estimate; not achieved revenue or production proof.',
217
+ },
218
+ candidateCount: normalized.length,
219
+ evidenceCompleteCount: eligible.length,
220
+ recommendedPilot: eligible.length > 0 ? eligible[0].agentId : null,
221
+ candidates: rankedCandidates,
222
+ };
223
+ }
224
+
225
+ function validBusinessObjectSchema(schema) {
226
+ const properties = schema?.properties;
227
+ // Arrays are typeof 'object' — reject them so properties: ['x'] cannot pass.
228
+ if (
229
+ schema?.type !== 'object'
230
+ || !properties
231
+ || typeof properties !== 'object'
232
+ || Array.isArray(properties)
233
+ ) {
234
+ return false;
235
+ }
236
+ const propertyNames = Object.keys(properties);
237
+ return propertyNames.length > 0
238
+ && Array.isArray(schema.required)
239
+ && schema.required.length > 0
240
+ && schema.required.every((field) => propertyNames.includes(field));
241
+ }
242
+
243
+ function validBusinessConsent(consent) {
244
+ if (consent?.verified !== true) return false;
245
+ const recordedAt = cleanText(consent.recordedAt);
246
+ return Boolean(cleanText(consent.source))
247
+ && Boolean(recordedAt)
248
+ && Number.isFinite(Date.parse(recordedAt));
249
+ }
250
+
251
+ function validBusinessApproval(approval) {
252
+ return approval?.status === 'approved' && Boolean(cleanText(approval.approvalId));
253
+ }
254
+
255
+ function buildBusinessHandoffIssues(context) {
256
+ const {
257
+ input,
258
+ fromFunction,
259
+ toFunction,
260
+ requestedActions,
261
+ requiredEvidence,
262
+ qualificationEvidence,
263
+ dataScopes,
264
+ estimatedCostUsd,
265
+ maxCostUsd,
266
+ slaMinutes,
267
+ } = context;
268
+ const isLeadToSales = fromFunction === 'lead_generation' && toFunction === 'sales_conversion';
269
+ const externalActionRequested = requestedActions.some((action) => !INTERNAL_BUSINESS_ACTION.test(action));
270
+
271
+ return failedBusinessRules([
272
+ [BUSINESS_FUNCTION_IDS.has(fromFunction), 'unsupported_source_function'],
273
+ [BUSINESS_FUNCTION_IDS.has(toFunction), 'unsupported_target_function'],
274
+ [BUSINESS_FUNCTION_EDGE_IDS.has(`${fromFunction}:${toFunction}`), 'undeclared_handoff_edge'],
275
+ [Boolean(cleanText(input.workflowId)), 'missing_workflow_id'],
276
+ [Boolean(cleanText(input.correlationId)), 'missing_correlation_id'],
277
+ [Boolean(cleanText(input.objective)), 'missing_objective'],
278
+ [Boolean(cleanText(input.primaryKpi)), 'missing_primary_kpi'],
279
+ [Boolean(cleanText(input.expectedOutcome)), 'missing_expected_outcome'],
280
+ [validBusinessObjectSchema(input.inputSchema), 'invalid_input_schema'],
281
+ [validBusinessObjectSchema(input.outputSchema), 'invalid_output_schema'],
282
+ [dataScopes.length > 0, 'missing_data_scope'],
283
+ [typeof input.containsPersonalData === 'boolean', 'missing_data_classification'],
284
+ [requiredEvidence.length > 0, 'missing_required_evidence'],
285
+ [Boolean(cleanText(input.idempotencyKey)), 'missing_idempotency_key'],
286
+ [estimatedCostUsd !== null && estimatedCostUsd >= 0, 'invalid_estimated_cost_usd'],
287
+ [maxCostUsd !== null && maxCostUsd >= 0, 'invalid_max_cost_usd'],
288
+ [estimatedCostUsd === null || maxCostUsd === null || estimatedCostUsd <= maxCostUsd, 'handoff_over_budget'],
289
+ [slaMinutes !== null && slaMinutes > 0, 'invalid_sla_minutes'],
290
+ [input.containsPersonalData !== true || validBusinessConsent(input.consent), 'personal_data_requires_verified_consent'],
291
+ [!isLeadToSales || qualificationEvidence.length > 0, 'lead_to_sales_requires_qualification_evidence'],
292
+ [!isLeadToSales || validBusinessConsent(input.consent), 'lead_to_sales_requires_verified_consent'],
293
+ [!externalActionRequested || validBusinessApproval(input.approval), 'external_action_requires_approval_receipt'],
294
+ ]);
295
+ }
296
+
297
+ function buildBusinessOutcomeReceiptTemplate(contract) {
298
+ return {
299
+ schemaVersion: 'task-outcome-template-v1',
300
+ recordable: false,
301
+ verificationState: 'not_run',
302
+ reason: 'Add verified results before recording.',
303
+ taskOutcome: {
304
+ taskId: contract.correlationId,
305
+ taskType: `business_handoff:${contract.fromFunction}:${contract.toFunction}`,
306
+ goal: contract.objective,
307
+ expectedOutcome: contract.expectedOutcome,
308
+ status: null,
309
+ verification: { performed: false, passed: false, evidence: [] },
310
+ toolCalls: [],
311
+ policy: null,
312
+ efficiency: null,
313
+ businessOutcome: null,
314
+ metadata: {
315
+ workflowId: contract.workflowId,
316
+ primaryKpi: contract.primaryKpi,
317
+ estimateOnly: true,
318
+ },
319
+ },
320
+ };
321
+ }
322
+
323
+ function evaluateBusinessFunctionHandoff(input = {}) {
324
+ const fromFunction = cleanText(input.fromFunction);
325
+ const toFunction = cleanText(input.toFunction);
326
+ const requestedActions = cleanUniqueList(input.requestedActions);
327
+ const requiredEvidence = cleanUniqueList(input.requiredEvidence);
328
+ const qualificationEvidence = cleanUniqueList(input.qualificationEvidence);
329
+ const dataScopes = cleanUniqueList(input.dataScopes);
330
+ const estimatedCostUsd = finiteNumber(input.budget?.estimatedCostUsd);
331
+ const maxCostUsd = finiteNumber(input.budget?.maxCostUsd);
332
+ const slaMinutes = finiteNumber(input.slaMinutes);
333
+ const issues = buildBusinessHandoffIssues({
334
+ input,
335
+ fromFunction,
336
+ toFunction,
337
+ requestedActions,
338
+ requiredEvidence,
339
+ qualificationEvidence,
340
+ dataScopes,
341
+ estimatedCostUsd,
342
+ maxCostUsd,
343
+ slaMinutes,
344
+ });
345
+
346
+ const contract = {
347
+ contractVersion: 'business-function-handoff-v1',
348
+ workflowId: cleanText(input.workflowId) || null,
349
+ correlationId: cleanText(input.correlationId) || null,
350
+ fromFunction: fromFunction || null,
351
+ toFunction: toFunction || null,
352
+ objective: cleanText(input.objective) || null,
353
+ primaryKpi: cleanText(input.primaryKpi) || null,
354
+ expectedOutcome: cleanText(input.expectedOutcome) || null,
355
+ inputSchema: validBusinessObjectSchema(input.inputSchema) ? input.inputSchema : null,
356
+ outputSchema: validBusinessObjectSchema(input.outputSchema) ? input.outputSchema : null,
357
+ dataScopes,
358
+ containsPersonalData: input.containsPersonalData === true,
359
+ consent: validBusinessConsent(input.consent) ? {
360
+ verified: true,
361
+ source: cleanText(input.consent.source),
362
+ recordedAt: new Date(input.consent.recordedAt).toISOString(),
363
+ } : null,
364
+ qualificationEvidence,
365
+ requestedActions,
366
+ budget: { estimatedCostUsd, maxCostUsd },
367
+ slaMinutes,
368
+ idempotencyKey: cleanText(input.idempotencyKey) || null,
369
+ requiredEvidence,
370
+ approval: validBusinessApproval(input.approval)
371
+ ? { status: 'approved', approvalId: cleanText(input.approval.approvalId) }
372
+ : null,
373
+ };
374
+ const decision = issues.length === 0 ? 'allow' : 'deny';
375
+
376
+ return {
377
+ schemaVersion: 'business-function-handoff-evaluation-v1',
378
+ decision,
379
+ issues: Array.from(new Set(issues)),
380
+ contract,
381
+ outcomeReceiptTemplate: decision === 'allow'
382
+ ? buildBusinessOutcomeReceiptTemplate(contract)
383
+ : null,
384
+ };
385
+ }
386
+
387
+ function parseBusinessAgentJson(value, fallback) {
388
+ if (value === undefined || value === null || value === '') return fallback;
389
+ if (typeof value !== 'string') return value;
390
+ try {
391
+ return JSON.parse(value);
392
+ } catch (error) {
393
+ const invalidJson = new Error('Business agent JSON input is invalid.');
394
+ invalidJson.code = 'INVALID_BUSINESS_AGENT_JSON';
395
+ invalidJson.cause = error;
396
+ throw invalidJson;
397
+ }
398
+ }
399
+
400
+ function buildBusinessFunctionAgentPlan(input = {}) {
401
+ const candidates = parseBusinessAgentJson(input.candidatesJson, input.candidates || []);
402
+ const handoff = parseBusinessAgentJson(input.handoffJson, input.handoff || null);
403
+ const ranking = rankBusinessFunctionAgents(candidates);
404
+ const evaluatedHandoff = handoff ? evaluateBusinessFunctionHandoff(handoff) : null;
405
+ return {
406
+ name: 'thumbgate-business-function-agent-team',
407
+ status: evaluatedHandoff?.decision === 'deny'
408
+ ? 'blocked'
409
+ : ranking.status,
410
+ catalog: {
411
+ functions: BUSINESS_FUNCTIONS,
412
+ defaultHandoffEdges: BUSINESS_FUNCTION_HANDOFF_EDGES,
413
+ },
414
+ ranking,
415
+ handoff: evaluatedHandoff,
416
+ nextActions: [
417
+ 'Pilot the shortest evidence-backed payback',
418
+ 'Gate every cross-function handoff',
419
+ 'Record actual KPIs apart from estimates',
420
+ ],
421
+ };
422
+ }
423
+
10
424
  function buildLoopRoutinePlan(input = {}) {
11
425
  const tasks = toList(input.tasks);
12
426
  const cadenceMinutes = Number(input.cadenceMinutes || input.cadence || 60);
@@ -568,6 +982,21 @@ function parseArgs(argv = process.argv.slice(2)) {
568
982
  return args;
569
983
  }
570
984
 
985
+ function readBusinessTeamOption(argv, name) {
986
+ const prefix = `--${name}=`;
987
+ return argv.find((arg) => arg.startsWith(prefix))?.slice(prefix.length);
988
+ }
989
+
990
+ function parseBusinessTeamArgs(argv = process.argv.slice(2)) {
991
+ if (!argv.includes('business-team')) return null;
992
+ return {
993
+ command: 'business-team',
994
+ json: argv.includes('--json'),
995
+ candidatesJson: readBusinessTeamOption(argv, 'candidates-json'),
996
+ handoffJson: readBusinessTeamOption(argv, 'handoff-json'),
997
+ };
998
+ }
999
+
571
1000
  function runCli(args) {
572
1001
  const command = args.command || 'loop';
573
1002
  const builders = {
@@ -587,6 +1016,7 @@ function runCli(args) {
587
1016
  format: buildOutputFormatPlan,
588
1017
  'gtm-agency': buildMarketingAgencyGtmPlan,
589
1018
  bedrock: buildBedrockAgentCorePlan,
1019
+ 'business-team': buildBusinessFunctionAgentPlan,
590
1020
  loop: buildLoopRoutinePlan,
591
1021
  };
592
1022
  const report = (builders[command] || buildLoopRoutinePlan)(args);
@@ -598,7 +1028,10 @@ function runCli(args) {
598
1028
  }
599
1029
  }
600
1030
 
601
- if (require.main === module) runCli(parseArgs());
1031
+ // Path-based entrypoint check avoids SonarCloud S3403 on `require.main === module`.
1032
+ if (path.resolve(process.argv[1] || '') === path.resolve(__filename)) {
1033
+ runCli(parseBusinessTeamArgs() || parseArgs());
1034
+ }
602
1035
 
603
1036
  module.exports = {
604
1037
  buildLoopRoutinePlan,
@@ -618,4 +1051,11 @@ module.exports = {
618
1051
  buildBedrockAgentCorePlan,
619
1052
  buildLegalAgentGovernancePlan,
620
1053
  buildDynamicWorkflowReadinessPlan,
1054
+ BUSINESS_FUNCTIONS,
1055
+ BUSINESS_FUNCTION_HANDOFF_EDGES,
1056
+ normalizeBusinessFunctionCandidate,
1057
+ rankBusinessFunctionAgents,
1058
+ evaluateBusinessFunctionHandoff,
1059
+ buildBusinessOutcomeReceiptTemplate,
1060
+ buildBusinessFunctionAgentPlan,
621
1061
  };