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
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * MiMinions Adapter
5
+ *
6
+ * Bridges MiMinions WorkflowRun / WorkflowTrace schemas (Pydantic-AI)
7
+ * into ThumbGate reliability telemetry, pre-action WriteGuard interdiction,
8
+ * and DPO feedback loops.
9
+ */
10
+
11
+ const { classifyMcpTool, scrubSensitiveData, scanForDestructivePatterns, RISK_TIERS } = require("./mcp-writeguard");
12
+
13
+ /**
14
+ * Validates and wraps a MiMinions tool invocation before execution.
15
+ *
16
+ * @param {Object} toolCall - { tool_name, args, kwargs }
17
+ * @param {Object} [options] - Configuration overrides
18
+ * @returns {Object} Evaluation verdict { allowed, tier, scrubbedKwargs, reason }
19
+ */
20
+ function guardMiMinionsToolCall(toolCall, options = {}) {
21
+ const toolName = toolCall.tool_name || toolCall.toolName || "unknown_tool";
22
+ const tier = classifyMcpTool(toolName, options.toolOverrides);
23
+ const rawKwargs = toolCall.kwargs || {};
24
+ const scrubbedKwargs = scrubSensitiveData(rawKwargs);
25
+
26
+ // Scan for destructive patterns if privileged
27
+ const destructiveFindings = scanForDestructivePatterns(toolName, scrubbedKwargs);
28
+ if (destructiveFindings.length > 0) {
29
+ return {
30
+ allowed: false,
31
+ tier,
32
+ decision: "blocked",
33
+ reasons: destructiveFindings,
34
+ scrubbedKwargs,
35
+ };
36
+ }
37
+
38
+ if (tier === RISK_TIERS.ADMIN && !options.allowAdmin) {
39
+ return {
40
+ allowed: false,
41
+ tier,
42
+ decision: "escalated",
43
+ reasons: ["Admin tool calls require explicit operator approval"],
44
+ scrubbedKwargs,
45
+ };
46
+ }
47
+
48
+ return {
49
+ allowed: true,
50
+ tier,
51
+ decision: "allowed",
52
+ reasons: [],
53
+ scrubbedKwargs,
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Converts a ThumbGate task outcome / receipt into a MiMinions-compatible WorkflowRun object.
59
+ *
60
+ * @param {Object} receipt - ThumbGate task receipt
61
+ * @returns {Object} MiMinions WorkflowRun JSON schema
62
+ */
63
+ function exportToMiMinionsWorkflowRun(receipt) {
64
+ const agentName = receipt.agentName || receipt.agentId || "ThumbGate-Agent";
65
+ const trace = [];
66
+
67
+ // Agent prompt record
68
+ trace.push({
69
+ type: "agent",
70
+ id: "run_" + (receipt.taskId || "task_0"),
71
+ created_at: receipt.createdAt || new Date().toISOString(),
72
+ prompt: receipt.prompt || "",
73
+ output: receipt.result || receipt.summary || null,
74
+ });
75
+
76
+ // Tool execution records
77
+ if (Array.isArray(receipt.toolCalls)) {
78
+ receipt.toolCalls.forEach((tc, idx) => {
79
+ trace.push({
80
+ type: "tool",
81
+ tool_name: tc.tool || tc.name || "unknown",
82
+ args: tc.args || [],
83
+ kwargs: scrubSensitiveData(tc.parameters || tc.kwargs || {}),
84
+ result: tc.result || null,
85
+ error: tc.error || null,
86
+ order: idx + 1,
87
+ timestamp: tc.timestamp || new Date().toISOString(),
88
+ status: tc.status || (tc.error ? "error" : "success"),
89
+ execution_time_ms: tc.latencyMs || tc.executionTimeMs || null,
90
+ });
91
+ });
92
+ }
93
+
94
+ return {
95
+ schema_version: 2,
96
+ id: "wf_" + (receipt.workflowId || receipt.taskId || "run"),
97
+ created_at: receipt.createdAt || new Date().toISOString(),
98
+ agent_name: agentName,
99
+ trace,
100
+ };
101
+ }
102
+
103
+ module.exports = {
104
+ guardMiMinionsToolCall,
105
+ exportToMiMinionsWorkflowRun,
106
+ };
@@ -0,0 +1,104 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { wiringReport } = require('../scripts/mcp-wiring-doctor');
6
+ const { getFeedbackPaths } = require('../scripts/feedback-paths');
7
+
8
+ const AXES = [
9
+ { id: 'design', name: 'Design', meaning: 'Interfaces wired (MCP / hooks)' },
10
+ { id: 'implement', name: 'Implement', meaning: 'Lessons store writable' },
11
+ { id: 'data', name: 'Data', meaning: 'Feedback rows captured' },
12
+ { id: 'promote', name: 'Promote', meaning: 'Prevention rules exist' },
13
+ { id: 'evaluate', name: 'Evaluate', meaning: 'Local summary or stats exist' },
14
+ { id: 'deploy', name: 'Deploy', meaning: 'Dashboard or proof surface present' },
15
+ ];
16
+
17
+ function countJsonlLines(filePath) {
18
+ try {
19
+ if (!filePath || !fs.existsSync(filePath)) return 0;
20
+ return fs.readFileSync(filePath, 'utf8').split('\n').filter((line) => line.trim()).length;
21
+ } catch {
22
+ return 0;
23
+ }
24
+ }
25
+
26
+ function fileNonEmpty(filePath) {
27
+ try {
28
+ return Boolean(filePath && fs.existsSync(filePath) && fs.statSync(filePath).size > 0);
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ function getPipelineCompass(options = {}) {
35
+ const projectRoot = path.resolve(options.projectRoot || process.cwd());
36
+ const env = options.env || process.env;
37
+ const wiring = wiringReport(projectRoot, env);
38
+ const paths = getFeedbackPaths({
39
+ projectDir: projectRoot,
40
+ env,
41
+ feedbackDir: options.feedbackDir,
42
+ });
43
+
44
+ const feedbackRows = countJsonlLines(paths.FEEDBACK_LOG_PATH);
45
+ const memoryRows = countJsonlLines(paths.MEMORY_LOG_PATH);
46
+ const hasRules = fileNonEmpty(paths.PREVENTION_RULES_PATH);
47
+ const hasSummary = fileNonEmpty(paths.SUMMARY_PATH);
48
+ const dashboardHtml = path.join(projectRoot, 'public', 'dashboard.html');
49
+ const localDashboard = fileNonEmpty(dashboardHtml)
50
+ || fs.existsSync(path.join(projectRoot, 'bin', 'dashboard-cli.js'));
51
+
52
+ const axes = {
53
+ design: wiring.overall !== 'error',
54
+ // Require a real writable lessons store — existence alone is not enough
55
+ // (mode-0555 dirs exist but reject feedback appends with EACCES).
56
+ implement: Boolean(wiring.lessonsStore && wiring.lessonsStore.writable),
57
+ data: feedbackRows > 0,
58
+ promote: hasRules || memoryRows > 0,
59
+ evaluate: hasSummary || feedbackRows > 0,
60
+ deploy: localDashboard || wiring.overall === 'ok',
61
+ };
62
+
63
+ const scores = AXES.map((axis) => ({
64
+ ...axis,
65
+ pass: Boolean(axes[axis.id]),
66
+ }));
67
+ const passed = scores.filter((axis) => axis.pass).length;
68
+
69
+ return {
70
+ motto: 'Pipeline first, algorithms second.',
71
+ source: 'TradeMaster-style 6-module loop mapped onto existing ThumbGate surfaces. Not an RL trading platform.',
72
+ projectRoot,
73
+ wiring: wiring.overall,
74
+ feedbackRows,
75
+ memoryRows,
76
+ axes: scores,
77
+ passed,
78
+ total: AXES.length,
79
+ ready: wiring.overall !== 'error' && axes.design,
80
+ };
81
+ }
82
+
83
+ function formatPipelineCompass(report) {
84
+ const lines = [
85
+ `ThumbGate pipeline compass (${report.passed}/${report.total})`,
86
+ report.motto,
87
+ '',
88
+ ];
89
+ for (const axis of report.axes) {
90
+ lines.push(`${axis.pass ? '✓' : '·'} ${axis.name.padEnd(10)} ${axis.meaning}`);
91
+ }
92
+ lines.push('');
93
+ lines.push(`Feedback rows: ${report.feedbackRows} Memory rows: ${report.memoryRows} Wiring: ${report.wiring}`);
94
+ if (!report.ready) {
95
+ lines.push('Next: npx thumbgate init && npx thumbgate doctor (pipeline before more gates)');
96
+ }
97
+ return lines.join('\n');
98
+ }
99
+
100
+ module.exports = {
101
+ AXES,
102
+ getPipelineCompass,
103
+ formatPipelineCompass,
104
+ };
@@ -0,0 +1,284 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * PPL (Piped Processing Language) Engine for Agent Observability & Pre-Action Alerting.
5
+ *
6
+ * Implements a lightweight, zero-dependency Unix pipeline processing model:
7
+ * source | filter <expr> | stats <agg> by <field> | where <expr> | eval <expr> | alert <params>
8
+ *
9
+ * Designed to prevent alert-rule sprawl, false-positive fatigue, and telemetry ingestion bottlenecks.
10
+ */
11
+
12
+ class PPLPipeline {
13
+ constructor(stages = []) {
14
+ this.stages = stages;
15
+ }
16
+
17
+ static parse(pplString) {
18
+ if (typeof pplString !== 'string' || !pplString.trim()) {
19
+ throw new Error('PPL pipeline string must be a non-empty string');
20
+ }
21
+
22
+ const stageDefs = pplString
23
+ .split('|')
24
+ .map((s) => s.trim())
25
+ .filter(Boolean);
26
+
27
+ const stages = [];
28
+
29
+ for (const rawStage of stageDefs) {
30
+ const parts = rawStage.split(/\s+/);
31
+ const op = parts[0].toLowerCase();
32
+ const rest = rawStage.slice(parts[0].length).trim();
33
+
34
+ if (op === 'filter' || op === 'where') {
35
+ stages.push({ type: 'filter', expr: rest });
36
+ } else if (op === 'stats') {
37
+ stages.push(PPLPipeline._parseStats(rest));
38
+ } else if (op === 'eval') {
39
+ stages.push(PPLPipeline._parseEval(rest));
40
+ } else if (op === 'dedup') {
41
+ stages.push({ type: 'dedup', field: rest.trim() });
42
+ } else if (op === 'head' || op === 'limit') {
43
+ stages.push({ type: 'limit', count: Number.parseInt(rest, 10) || 10 });
44
+ } else {
45
+ throw new Error(`Unsupported PPL operator: ${op}`);
46
+ }
47
+ }
48
+
49
+ return new PPLPipeline(stages);
50
+ }
51
+
52
+ static _parseStats(rest) {
53
+ const byIndex = rest.toLowerCase().indexOf(' by ');
54
+ let aggStr = rest;
55
+ let byField = null;
56
+
57
+ if (byIndex !== -1) {
58
+ aggStr = rest.slice(0, byIndex).trim();
59
+ byField = rest.slice(byIndex + 4).trim();
60
+ }
61
+
62
+ const aggs = [];
63
+ const aggTokens = aggStr.split(',').map((s) => s.trim());
64
+
65
+ for (const token of aggTokens) {
66
+ const funcMatch = /^([a-z0-9_]+)\((.*?)\)(?:\s+as\s+([a-z0-9_]+))?$/i.exec(token);
67
+ if (funcMatch) {
68
+ aggs.push({
69
+ func: funcMatch[1].toLowerCase(),
70
+ field: funcMatch[2].trim() || null,
71
+ alias: funcMatch[3] ? funcMatch[3].trim() : `${funcMatch[1].toLowerCase()}_${funcMatch[2] || 'all'}`,
72
+ });
73
+ }
74
+ }
75
+
76
+ return { type: 'stats', aggs, byField };
77
+ }
78
+
79
+ static _parseEval(rest) {
80
+ const eqIndex = rest.indexOf('=');
81
+ if (eqIndex === -1) {
82
+ throw new Error(`Invalid eval syntax: ${rest}`);
83
+ }
84
+ return { type: 'eval', target: rest.slice(0, eqIndex).trim(), expr: rest.slice(eqIndex + 1).trim() };
85
+ }
86
+
87
+ execute(records = []) {
88
+ let current = Array.isArray(records) ? [...records] : [];
89
+
90
+ for (const stage of this.stages) {
91
+ switch (stage.type) {
92
+ case 'filter':
93
+ current = current.filter((r) => PPLPipeline._evalPredicate(r, stage.expr));
94
+ break;
95
+ case 'stats':
96
+ current = PPLPipeline._execStats(current, stage);
97
+ break;
98
+ case 'eval':
99
+ current = current.map((r) => PPLPipeline._execEval(r, stage));
100
+ break;
101
+ case 'dedup':
102
+ current = PPLPipeline._execDedup(current, stage.field);
103
+ break;
104
+ case 'limit':
105
+ current = current.slice(0, stage.count);
106
+ break;
107
+ default:
108
+ break;
109
+ }
110
+ }
111
+
112
+ return current;
113
+ }
114
+
115
+ static _evalPredicate(record, expr) {
116
+ const tokens = /^([a-z0-9_.]+)\s*(==|!=|>=|<=|>|<|contains)\s*([\s\S]+)$/i.exec(expr);
117
+ if (!tokens) return true;
118
+
119
+ const [, field, op, rawVal] = tokens;
120
+ const actual = PPLPipeline._getProp(record, field);
121
+ let target = rawVal.trim().replace(/^['"]|['"]$/g, '');
122
+
123
+ if (!Number.isNaN(Number(target)) && target !== '') {
124
+ target = Number(target);
125
+ }
126
+
127
+ switch (op) {
128
+ case '==':
129
+ return actual === target;
130
+ case '!=':
131
+ return actual !== target;
132
+ case '>':
133
+ return Number(actual) > Number(target);
134
+ case '>=':
135
+ return Number(actual) >= Number(target);
136
+ case '<':
137
+ return Number(actual) < Number(target);
138
+ case '<=':
139
+ return Number(actual) <= Number(target);
140
+ case 'contains':
141
+ return String(actual || '').includes(String(target));
142
+ default:
143
+ return true;
144
+ }
145
+ }
146
+
147
+ static _execEval(record, stage) {
148
+ const res = { ...record };
149
+ const exprTokens = /^([a-z0-9_.]+)\s*([+\-*/><=!]+)\s*([\s\S]+)$/i.exec(stage.expr);
150
+ if (exprTokens) {
151
+ const [, left, op, right] = exprTokens;
152
+ const leftVal = PPLPipeline._getProp(record, left);
153
+ let rightVal = right.trim().replace(/^['"]|['"]$/g, '');
154
+ if (!Number.isNaN(Number(rightVal))) rightVal = Number(rightVal);
155
+
156
+ if (op === '>') res[stage.target] = Number(leftVal) > Number(rightVal);
157
+ else if (op === '>=') res[stage.target] = Number(leftVal) >= Number(rightVal);
158
+ else if (op === '<') res[stage.target] = Number(leftVal) < Number(rightVal);
159
+ else if (op === '<=') res[stage.target] = Number(leftVal) <= Number(rightVal);
160
+ else if (op === '==') res[stage.target] = leftVal === rightVal;
161
+ else if (op === '+') res[stage.target] = Number(leftVal) + Number(rightVal);
162
+ else if (op === '-') res[stage.target] = Number(leftVal) - Number(rightVal);
163
+ } else {
164
+ res[stage.target] = stage.expr;
165
+ }
166
+ return res;
167
+ }
168
+
169
+ static _execStats(records, stage) {
170
+ const groups = new Map();
171
+
172
+ for (const r of records) {
173
+ const key = stage.byField ? String(PPLPipeline._getProp(r, stage.byField) || 'null') : '__all__';
174
+ if (!groups.has(key)) {
175
+ groups.set(key, []);
176
+ }
177
+ groups.get(key).push(r);
178
+ }
179
+
180
+ const results = [];
181
+
182
+ for (const [key, rows] of groups.entries()) {
183
+ const rowRes = {};
184
+ if (stage.byField) {
185
+ rowRes[stage.byField] = key === 'null' ? null : key;
186
+ }
187
+
188
+ for (const agg of stage.aggs) {
189
+ if (agg.func === 'count') {
190
+ rowRes[agg.alias] = rows.length;
191
+ } else if (agg.func === 'avg') {
192
+ const sum = rows.reduce((s, r) => s + (Number(PPLPipeline._getProp(r, agg.field)) || 0), 0);
193
+ rowRes[agg.alias] = rows.length > 0 ? sum / rows.length : 0;
194
+ } else if (agg.func === 'sum') {
195
+ rowRes[agg.alias] = rows.reduce((s, r) => s + (Number(PPLPipeline._getProp(r, agg.field)) || 0), 0);
196
+ } else if (agg.func === 'p95') {
197
+ const vals = rows
198
+ .map((r) => Number(PPLPipeline._getProp(r, agg.field)))
199
+ .filter((v) => !Number.isNaN(v))
200
+ .sort((a, b) => a - b);
201
+ if (vals.length === 0) {
202
+ rowRes[agg.alias] = 0;
203
+ } else {
204
+ const idx = Math.floor(vals.length * 0.95);
205
+ rowRes[agg.alias] = vals[Math.min(idx, vals.length - 1)];
206
+ }
207
+ }
208
+ }
209
+ results.push(rowRes);
210
+ }
211
+
212
+ return results;
213
+ }
214
+
215
+ static _execDedup(records, field) {
216
+ const seen = new Set();
217
+ const out = [];
218
+ for (const r of records) {
219
+ const val = PPLPipeline._getProp(r, field);
220
+ if (!seen.has(val)) {
221
+ seen.add(val);
222
+ out.push(r);
223
+ }
224
+ }
225
+ return out;
226
+ }
227
+
228
+ static _getProp(obj, pathStr) {
229
+ if (!obj || !pathStr) return undefined;
230
+ const parts = pathStr.split('.');
231
+ let cur = obj;
232
+ for (const p of parts) {
233
+ if (cur == null) return undefined;
234
+ cur = cur[p];
235
+ }
236
+ return cur;
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Unified Alert Manager with deduping window.
242
+ */
243
+ class UnifiedAlertManager {
244
+ constructor({ dedupWindowMs = 60000 } = {}) {
245
+ this.dedupWindowMs = dedupWindowMs;
246
+ this.lastAlertTime = new Map();
247
+ }
248
+
249
+ evaluateAlert(pipeline, records = [], { alertThresholdField, severity = 'high', alertName = 'unnamed-alert' } = {}) {
250
+ const matched = pipeline.execute(records);
251
+ const triggered = [];
252
+
253
+ const now = Date.now();
254
+
255
+ for (const row of matched) {
256
+ const isBreach = alertThresholdField ? Boolean(row[alertThresholdField]) : true;
257
+ if (isBreach) {
258
+ const entityKey = `${alertName}:${JSON.stringify(row)}`;
259
+ const lastSent = this.lastAlertTime.get(entityKey) || 0;
260
+ if (now - lastSent >= this.dedupWindowMs) {
261
+ this.lastAlertTime.set(entityKey, now);
262
+ triggered.push({
263
+ alertName,
264
+ severity,
265
+ timestamp: now,
266
+ data: row,
267
+ });
268
+ }
269
+ }
270
+ }
271
+
272
+ return {
273
+ triggered: triggered.length > 0,
274
+ alerts: triggered,
275
+ evaluatedRows: records.length,
276
+ matchedRows: matched.length,
277
+ };
278
+ }
279
+ }
280
+
281
+ module.exports = {
282
+ PPLPipeline,
283
+ UnifiedAlertManager,
284
+ };
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Rendezvous (Highest Random Weight) Hashing Router
3
+ *
4
+ * Inspired by Cursor Continuity's stateless repository routing (https://cursor.com/blog/git-at-any-scale).
5
+ * Enables deterministic, zero-lock multi-agent worktree and task assignment across
6
+ * distributed agent fleets without central routing tables or relational databases.
7
+ *
8
+ * @module rendezvous-router
9
+ */
10
+
11
+ const crypto = require('node:crypto');
12
+
13
+ /**
14
+ * Computes a 64-bit deterministic hash weight for a (key, node) pair.
15
+ *
16
+ * @param {string} key - The repository path, task ID, or session key
17
+ * @param {string} node - The worker node, worktree path, or agent identity
18
+ * @returns {number} Float weight in range [0, 1)
19
+ */
20
+ function computeRendezvousWeight(key, node) {
21
+ const hash = crypto.createHash('sha256').update(`${key}:${node}`).digest();
22
+ const intVal = hash.readUInt32BE(0);
23
+ return intVal / 0xffffffff;
24
+ }
25
+
26
+ /**
27
+ * Selects the top-ranked node for a given key using Rendezvous (HRW) hashing.
28
+ *
29
+ * @param {string} key - Key to route (e.g. repo path, task id)
30
+ * @param {string[]} nodes - Available candidate nodes/worktrees
31
+ * @returns {string|null} The selected node, or null if nodes is empty
32
+ */
33
+ function getRendezvousNode(key, nodes) {
34
+ if (!Array.isArray(nodes) || nodes.length === 0) return null;
35
+ if (nodes.length === 1) return nodes[0];
36
+
37
+ let bestNode = null;
38
+ let maxWeight = -1;
39
+
40
+ for (const node of nodes) {
41
+ const weight = computeRendezvousWeight(key, node);
42
+ if (weight > maxWeight) {
43
+ maxWeight = weight;
44
+ bestNode = node;
45
+ }
46
+ }
47
+
48
+ return bestNode;
49
+ }
50
+
51
+ /**
52
+ * Returns a ranked list of all nodes for a given key, ordered from highest to lowest weight.
53
+ * Used for graceful fallback when the primary node is degraded/unhealthy.
54
+ *
55
+ * @param {string} key - Key to route
56
+ * @param {string[]} nodes - Available candidate nodes
57
+ * @returns {string[]} Ordered list of nodes
58
+ */
59
+ function getRankedRendezvousNodes(key, nodes) {
60
+ if (!Array.isArray(nodes) || nodes.length === 0) return [];
61
+ if (nodes.length === 1) return [...nodes];
62
+
63
+ return [...nodes]
64
+ .map((node) => ({ node, weight: computeRendezvousWeight(key, node) }))
65
+ .sort((a, b) => b.weight - a.weight)
66
+ .map((item) => item.node);
67
+ }
68
+
69
+ /**
70
+ * Routes an agent session to an isolated worktree deterministically.
71
+ *
72
+ * @param {string} repoPath - Base repository path
73
+ * @param {string} sessionId - Active agent session ID
74
+ * @param {string[]} availableWorktrees - List of worktree directory names or paths
75
+ * @returns {string|null} Assigned worktree path
76
+ */
77
+ function routeSessionWorktree(repoPath, sessionId, availableWorktrees) {
78
+ if (!repoPath || !sessionId || !Array.isArray(availableWorktrees) || availableWorktrees.length === 0) {
79
+ return null;
80
+ }
81
+ const key = `${repoPath}#${sessionId}`;
82
+ return getRendezvousNode(key, availableWorktrees);
83
+ }
84
+
85
+ module.exports = {
86
+ computeRendezvousWeight,
87
+ getRendezvousNode,
88
+ getRankedRendezvousNodes,
89
+ routeSessionWorktree,
90
+ };