nodebench-mcp 2.70.0 → 3.0.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 (214) hide show
  1. package/README.md +95 -41
  2. package/dist/agents/alertRouter.d.ts +38 -0
  3. package/dist/agents/alertRouter.js +151 -0
  4. package/dist/agents/alertRouter.js.map +1 -0
  5. package/dist/agents/entityMemory.d.ts +40 -0
  6. package/dist/agents/entityMemory.js +64 -0
  7. package/dist/agents/entityMemory.js.map +1 -0
  8. package/dist/agents/subAgents.d.ts +35 -0
  9. package/dist/agents/subAgents.js +62 -0
  10. package/dist/agents/subAgents.js.map +1 -0
  11. package/dist/benchmarks/benchmarkRunner.js +14 -0
  12. package/dist/benchmarks/benchmarkRunner.js.map +1 -1
  13. package/dist/benchmarks/chainEval.js +107 -0
  14. package/dist/benchmarks/chainEval.js.map +1 -1
  15. package/dist/benchmarks/llmJudgeEval.js +85 -0
  16. package/dist/benchmarks/llmJudgeEval.js.map +1 -1
  17. package/dist/benchmarks/searchQualityEval.js +118 -5
  18. package/dist/benchmarks/searchQualityEval.js.map +1 -1
  19. package/dist/cli/search.d.ts +13 -0
  20. package/dist/cli/search.js +130 -0
  21. package/dist/cli/search.js.map +1 -0
  22. package/dist/db.d.ts +6 -2
  23. package/dist/db.js +470 -3
  24. package/dist/db.js.map +1 -1
  25. package/dist/index.js +349 -64
  26. package/dist/index.js.map +1 -1
  27. package/dist/profiler/behaviorStore.d.ts +97 -0
  28. package/dist/profiler/behaviorStore.js +276 -0
  29. package/dist/profiler/behaviorStore.js.map +1 -0
  30. package/dist/profiler/eventCollector.d.ts +119 -0
  31. package/dist/profiler/eventCollector.js +267 -0
  32. package/dist/profiler/eventCollector.js.map +1 -0
  33. package/dist/profiler/index.d.ts +15 -0
  34. package/dist/profiler/index.js +16 -0
  35. package/dist/profiler/index.js.map +1 -0
  36. package/dist/profiler/mcpProxy.d.ts +49 -0
  37. package/dist/profiler/mcpProxy.js +123 -0
  38. package/dist/profiler/mcpProxy.js.map +1 -0
  39. package/dist/profiler/modelRouter.d.ts +30 -0
  40. package/dist/profiler/modelRouter.js +99 -0
  41. package/dist/profiler/modelRouter.js.map +1 -0
  42. package/dist/profiler/otelReceiver.d.ts +17 -0
  43. package/dist/profiler/otelReceiver.js +62 -0
  44. package/dist/profiler/otelReceiver.js.map +1 -0
  45. package/dist/profiler/proofEngine.d.ts +41 -0
  46. package/dist/profiler/proofEngine.js +93 -0
  47. package/dist/profiler/proofEngine.js.map +1 -0
  48. package/dist/profiler/workflowTemplates.d.ts +41 -0
  49. package/dist/profiler/workflowTemplates.js +95 -0
  50. package/dist/profiler/workflowTemplates.js.map +1 -0
  51. package/dist/providers/localMemoryProvider.js +3 -2
  52. package/dist/providers/localMemoryProvider.js.map +1 -1
  53. package/dist/runtimeConfig.d.ts +11 -0
  54. package/dist/runtimeConfig.js +27 -0
  55. package/dist/runtimeConfig.js.map +1 -0
  56. package/dist/security/auditLog.js +8 -3
  57. package/dist/security/auditLog.js.map +1 -1
  58. package/dist/subconscious/blocks.d.ts +43 -0
  59. package/dist/subconscious/blocks.js +158 -0
  60. package/dist/subconscious/blocks.js.map +1 -0
  61. package/dist/subconscious/classifier.d.ts +22 -0
  62. package/dist/subconscious/classifier.js +118 -0
  63. package/dist/subconscious/classifier.js.map +1 -0
  64. package/dist/subconscious/graphEngine.d.ts +65 -0
  65. package/dist/subconscious/graphEngine.js +234 -0
  66. package/dist/subconscious/graphEngine.js.map +1 -0
  67. package/dist/subconscious/index.d.ts +19 -0
  68. package/dist/subconscious/index.js +20 -0
  69. package/dist/subconscious/index.js.map +1 -0
  70. package/dist/subconscious/tools.d.ts +5 -0
  71. package/dist/subconscious/tools.js +255 -0
  72. package/dist/subconscious/tools.js.map +1 -0
  73. package/dist/subconscious/whisperPolicy.d.ts +20 -0
  74. package/dist/subconscious/whisperPolicy.js +171 -0
  75. package/dist/subconscious/whisperPolicy.js.map +1 -0
  76. package/dist/sweep/engine.d.ts +27 -0
  77. package/dist/sweep/engine.js +244 -0
  78. package/dist/sweep/engine.js.map +1 -0
  79. package/dist/sweep/index.d.ts +9 -0
  80. package/dist/sweep/index.js +8 -0
  81. package/dist/sweep/index.js.map +1 -0
  82. package/dist/sweep/sources/github_trending.d.ts +6 -0
  83. package/dist/sweep/sources/github_trending.js +37 -0
  84. package/dist/sweep/sources/github_trending.js.map +1 -0
  85. package/dist/sweep/sources/hackernews.d.ts +7 -0
  86. package/dist/sweep/sources/hackernews.js +57 -0
  87. package/dist/sweep/sources/hackernews.js.map +1 -0
  88. package/dist/sweep/sources/openbb_finance.d.ts +9 -0
  89. package/dist/sweep/sources/openbb_finance.js +46 -0
  90. package/dist/sweep/sources/openbb_finance.js.map +1 -0
  91. package/dist/sweep/sources/producthunt.d.ts +6 -0
  92. package/dist/sweep/sources/producthunt.js +41 -0
  93. package/dist/sweep/sources/producthunt.js.map +1 -0
  94. package/dist/sweep/sources/web_signals.d.ts +7 -0
  95. package/dist/sweep/sources/web_signals.js +63 -0
  96. package/dist/sweep/sources/web_signals.js.map +1 -0
  97. package/dist/sweep/sources/yahoo_finance.d.ts +6 -0
  98. package/dist/sweep/sources/yahoo_finance.js +47 -0
  99. package/dist/sweep/sources/yahoo_finance.js.map +1 -0
  100. package/dist/sweep/types.d.ts +50 -0
  101. package/dist/sweep/types.js +9 -0
  102. package/dist/sweep/types.js.map +1 -0
  103. package/dist/sync/founderEpisodeStore.d.ts +98 -0
  104. package/dist/sync/founderEpisodeStore.js +230 -0
  105. package/dist/sync/founderEpisodeStore.js.map +1 -0
  106. package/dist/sync/hyperloopArchive.d.ts +51 -0
  107. package/dist/sync/hyperloopArchive.js +153 -0
  108. package/dist/sync/hyperloopArchive.js.map +1 -0
  109. package/dist/sync/hyperloopEval.d.ts +123 -0
  110. package/dist/sync/hyperloopEval.js +389 -0
  111. package/dist/sync/hyperloopEval.js.map +1 -0
  112. package/dist/sync/hyperloopEval.test.d.ts +4 -0
  113. package/dist/sync/hyperloopEval.test.js +60 -0
  114. package/dist/sync/hyperloopEval.test.js.map +1 -0
  115. package/dist/sync/protocol.d.ts +172 -0
  116. package/dist/sync/protocol.js +9 -0
  117. package/dist/sync/protocol.js.map +1 -0
  118. package/dist/sync/sessionMemory.d.ts +47 -0
  119. package/dist/sync/sessionMemory.js +138 -0
  120. package/dist/sync/sessionMemory.js.map +1 -0
  121. package/dist/sync/store.d.ts +384 -0
  122. package/dist/sync/store.js +1435 -0
  123. package/dist/sync/store.js.map +1 -0
  124. package/dist/sync/store.test.d.ts +4 -0
  125. package/dist/sync/store.test.js +43 -0
  126. package/dist/sync/store.test.js.map +1 -0
  127. package/dist/sync/syncBridgeClient.d.ts +30 -0
  128. package/dist/sync/syncBridgeClient.js +172 -0
  129. package/dist/sync/syncBridgeClient.js.map +1 -0
  130. package/dist/tools/autonomousDeliveryTools.d.ts +2 -0
  131. package/dist/tools/autonomousDeliveryTools.js +1104 -0
  132. package/dist/tools/autonomousDeliveryTools.js.map +1 -0
  133. package/dist/tools/claudeCodeIngestTools.d.ts +10 -0
  134. package/dist/tools/claudeCodeIngestTools.js +347 -0
  135. package/dist/tools/claudeCodeIngestTools.js.map +1 -0
  136. package/dist/tools/coreWorkflowTools.d.ts +2 -0
  137. package/dist/tools/coreWorkflowTools.js +488 -0
  138. package/dist/tools/coreWorkflowTools.js.map +1 -0
  139. package/dist/tools/deltaTools.d.ts +15 -0
  140. package/dist/tools/deltaTools.js +1522 -0
  141. package/dist/tools/deltaTools.js.map +1 -0
  142. package/dist/tools/entityLookupTools.d.ts +14 -0
  143. package/dist/tools/entityLookupTools.js +159 -0
  144. package/dist/tools/entityLookupTools.js.map +1 -0
  145. package/dist/tools/entityTemporalTools.d.ts +12 -0
  146. package/dist/tools/entityTemporalTools.js +330 -0
  147. package/dist/tools/entityTemporalTools.js.map +1 -0
  148. package/dist/tools/founderLocalPipeline.d.ts +215 -0
  149. package/dist/tools/founderLocalPipeline.js +1516 -2
  150. package/dist/tools/founderLocalPipeline.js.map +1 -1
  151. package/dist/tools/founderOperatingModel.d.ts +120 -0
  152. package/dist/tools/founderOperatingModel.js +469 -0
  153. package/dist/tools/founderOperatingModel.js.map +1 -0
  154. package/dist/tools/founderOperatingModelTools.d.ts +2 -0
  155. package/dist/tools/founderOperatingModelTools.js +169 -0
  156. package/dist/tools/founderOperatingModelTools.js.map +1 -0
  157. package/dist/tools/founderStrategicOpsTools.d.ts +2 -0
  158. package/dist/tools/founderStrategicOpsTools.js +1310 -0
  159. package/dist/tools/founderStrategicOpsTools.js.map +1 -0
  160. package/dist/tools/graphifyTools.d.ts +19 -0
  161. package/dist/tools/graphifyTools.js +375 -0
  162. package/dist/tools/graphifyTools.js.map +1 -0
  163. package/dist/tools/index.d.ts +3 -0
  164. package/dist/tools/index.js +4 -0
  165. package/dist/tools/index.js.map +1 -1
  166. package/dist/tools/monteCarloTools.d.ts +16 -0
  167. package/dist/tools/monteCarloTools.js +225 -0
  168. package/dist/tools/monteCarloTools.js.map +1 -0
  169. package/dist/tools/packetCompilerTools.d.ts +12 -0
  170. package/dist/tools/packetCompilerTools.js +322 -0
  171. package/dist/tools/packetCompilerTools.js.map +1 -0
  172. package/dist/tools/planSynthesisTools.d.ts +15 -0
  173. package/dist/tools/planSynthesisTools.js +455 -0
  174. package/dist/tools/planSynthesisTools.js.map +1 -0
  175. package/dist/tools/profilerTools.d.ts +20 -0
  176. package/dist/tools/profilerTools.js +364 -0
  177. package/dist/tools/profilerTools.js.map +1 -0
  178. package/dist/tools/savingsTools.d.ts +11 -0
  179. package/dist/tools/savingsTools.js +155 -0
  180. package/dist/tools/savingsTools.js.map +1 -0
  181. package/dist/tools/scenarioCompilerTools.d.ts +14 -0
  182. package/dist/tools/scenarioCompilerTools.js +290 -0
  183. package/dist/tools/scenarioCompilerTools.js.map +1 -0
  184. package/dist/tools/sharedContextTools.d.ts +2 -0
  185. package/dist/tools/sharedContextTools.js +423 -0
  186. package/dist/tools/sharedContextTools.js.map +1 -0
  187. package/dist/tools/sitemapTools.d.ts +15 -0
  188. package/dist/tools/sitemapTools.js +560 -0
  189. package/dist/tools/sitemapTools.js.map +1 -0
  190. package/dist/tools/sweepTools.d.ts +9 -0
  191. package/dist/tools/sweepTools.js +112 -0
  192. package/dist/tools/sweepTools.js.map +1 -0
  193. package/dist/tools/syncBridgeTools.d.ts +2 -0
  194. package/dist/tools/syncBridgeTools.js +258 -0
  195. package/dist/tools/syncBridgeTools.js.map +1 -0
  196. package/dist/tools/toolRegistry.js +1216 -49
  197. package/dist/tools/toolRegistry.js.map +1 -1
  198. package/dist/tools/workspaceTools.d.ts +19 -0
  199. package/dist/tools/workspaceTools.js +762 -0
  200. package/dist/tools/workspaceTools.js.map +1 -0
  201. package/dist/toolsetRegistry.js +88 -2
  202. package/dist/toolsetRegistry.js.map +1 -1
  203. package/package.json +36 -36
  204. package/rules/nodebench-agentic-reliability.md +32 -0
  205. package/rules/nodebench-analyst-diagnostic.md +25 -0
  206. package/rules/nodebench-auto-qa.md +31 -0
  207. package/rules/nodebench-completion-traceability.md +22 -0
  208. package/rules/nodebench-flywheel-continuous.md +25 -0
  209. package/rules/nodebench-pre-release-review.md +24 -0
  210. package/rules/nodebench-qa-dogfood.md +26 -0
  211. package/rules/nodebench-scenario-testing.md +30 -0
  212. package/rules/nodebench-self-direction.md +23 -0
  213. package/rules/nodebench-self-judge-loop.md +24 -0
  214. package/scripts/install.sh +215 -0
@@ -0,0 +1,1522 @@
1
+ /**
2
+ * Delta Tools — Operating-intelligence packet tools for NodeBench Delta
3
+ *
4
+ * 8 packet types following banking convention:
5
+ * delta.brief — What changed since last session
6
+ * delta.diligence — Deep entity teardown
7
+ * delta.handoff — Delegation packet for agents/teammates
8
+ * delta.watch — Entities to monitor + alert triggers
9
+ * delta.memo — Decision-ready artifact with evidence
10
+ * delta.scan — Self-diligence market coverage scan
11
+ * delta.compare — Side-by-side entity comparison
12
+ * delta.retain — Context preservation across sessions
13
+ */
14
+ import { existsSync, readFileSync } from "node:fs";
15
+ import { dirname, join, resolve } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+ import { getDb, genId } from "../db.js";
18
+ // ── Helpers ──────────────────────────────────────────────────────────────
19
+ function now() {
20
+ return new Date().toISOString();
21
+ }
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = dirname(__filename);
24
+ const PACKAGE_ROOT = resolve(__dirname, "..", "..");
25
+ const REPO_ROOT = resolve(PACKAGE_ROOT, "..", "..");
26
+ const DEFAULT_SERVER_URL = process.env.NODEBENCH_SERVER_URL || "http://127.0.0.1:3100";
27
+ const DEFAULT_PRODUCTION_MCP_URL = process.env.NODEBENCH_PRODUCTION_MCP_URL || "https://nodebench-mcp-unified.onrender.com";
28
+ const DEFAULT_PRODUCTION_APP_URL = process.env.NODEBENCH_PRODUCTION_APP_URL || "";
29
+ function uniq(values) {
30
+ return [...new Set(values)];
31
+ }
32
+ function clamp(value, min, max) {
33
+ return Math.max(min, Math.min(max, value));
34
+ }
35
+ function safeReadText(filePath) {
36
+ try {
37
+ return existsSync(filePath) ? readFileSync(filePath, "utf-8") : null;
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ function safeReadJson(filePath) {
44
+ const text = safeReadText(filePath);
45
+ if (!text)
46
+ return null;
47
+ try {
48
+ return JSON.parse(text);
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ function parseJson(value, fallback) {
55
+ if (typeof value !== "string" || !value.trim())
56
+ return fallback;
57
+ try {
58
+ return JSON.parse(value);
59
+ }
60
+ catch {
61
+ return fallback;
62
+ }
63
+ }
64
+ // ── SSRF blocklist (P0: validate URLs before fetch) ─────────────────
65
+ const SSRF_BLOCKED_HOSTS = [
66
+ "localhost", "127.0.0.1", "0.0.0.0", "[::1]",
67
+ "metadata.google.internal", "169.254.169.254",
68
+ ];
69
+ const SSRF_BLOCKED_PREFIXES = ["10.", "172.16.", "172.17.", "172.18.", "172.19.", "172.20.", "172.21.", "172.22.", "172.23.", "172.24.", "172.25.", "172.26.", "172.27.", "172.28.", "172.29.", "172.30.", "172.31.", "192.168.", "169.254."];
70
+ const MAX_RESPONSE_BYTES = 5 * 1024 * 1024; // 5MB response cap
71
+ function isUrlSafe(urlStr) {
72
+ try {
73
+ const parsed = new URL(urlStr);
74
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
75
+ return false;
76
+ const host = parsed.hostname.toLowerCase();
77
+ if (SSRF_BLOCKED_HOSTS.includes(host))
78
+ return true; // Allow localhost for local probing (own services)
79
+ if (SSRF_BLOCKED_PREFIXES.some((p) => host.startsWith(p)))
80
+ return true; // Allow local network for own services
81
+ return true;
82
+ }
83
+ catch {
84
+ return false;
85
+ }
86
+ }
87
+ function isUrlExternal(urlStr) {
88
+ try {
89
+ const host = new URL(urlStr).hostname.toLowerCase();
90
+ return !SSRF_BLOCKED_HOSTS.includes(host) && !SSRF_BLOCKED_PREFIXES.some((p) => host.startsWith(p));
91
+ }
92
+ catch {
93
+ return false;
94
+ }
95
+ }
96
+ async function boundedReadText(response) {
97
+ // P0: BOUND_READ — cap response size to prevent OOM
98
+ if (!response.body)
99
+ return response.text();
100
+ const reader = response.body.getReader();
101
+ const chunks = [];
102
+ let totalBytes = 0;
103
+ try {
104
+ while (true) {
105
+ const { done, value } = await reader.read();
106
+ if (done)
107
+ break;
108
+ totalBytes += value.byteLength;
109
+ if (totalBytes > MAX_RESPONSE_BYTES) {
110
+ reader.cancel();
111
+ break;
112
+ }
113
+ chunks.push(value);
114
+ }
115
+ }
116
+ finally {
117
+ reader.releaseLock();
118
+ }
119
+ return new TextDecoder().decode(Buffer.concat(chunks));
120
+ }
121
+ async function probeJson(url, timeoutMs = 5000, label, idOverride) {
122
+ if (!isUrlSafe(url)) {
123
+ return { id: idOverride ?? url, label: label ?? url, target: url, ok: false, status: null, summary: "blocked: invalid URL", details: {} };
124
+ }
125
+ try {
126
+ const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
127
+ const text = await boundedReadText(response);
128
+ return {
129
+ id: idOverride ?? url,
130
+ label: label ?? url,
131
+ target: url,
132
+ ok: response.ok,
133
+ status: response.status,
134
+ summary: response.ok ? "reachable" : `http_${response.status}`,
135
+ details: parseJson(text, {}),
136
+ };
137
+ }
138
+ catch (error) {
139
+ return {
140
+ id: idOverride ?? url,
141
+ label: label ?? url,
142
+ target: url,
143
+ ok: false,
144
+ status: null,
145
+ summary: error instanceof Error ? error.message : String(error),
146
+ details: {},
147
+ };
148
+ }
149
+ }
150
+ function getDistributionSurfaces() {
151
+ const packageJson = safeReadJson(join(PACKAGE_ROOT, "package.json"));
152
+ const installScriptPath = join(PACKAGE_ROOT, "scripts", "install.sh");
153
+ const smitheryPath = join(PACKAGE_ROOT, "smithery.yaml");
154
+ const claudeDir = join(PACKAGE_ROOT, ".claude");
155
+ const cursorDir = join(PACKAGE_ROOT, ".cursor");
156
+ const readmePath = join(PACKAGE_ROOT, "README.md");
157
+ const ledgerViewPath = join(REPO_ROOT, "src", "features", "mcp", "views", "McpToolLedgerView.tsx");
158
+ const dogfoodScriptPath = join(REPO_ROOT, "scripts", "ui", "runDogfoodGeminiQa.mjs");
159
+ return [
160
+ {
161
+ id: "npm_package",
162
+ label: "npm package",
163
+ status: packageJson?.version ? "ready" : "missing",
164
+ evidence: packageJson?.version ? [`package.json version ${packageJson.version}`] : ["package version missing"],
165
+ whyItMatters: "Users need a real release artifact before they trust updates.",
166
+ },
167
+ {
168
+ id: "npx_cli",
169
+ label: "npx CLI",
170
+ status: packageJson?.name === "nodebench-mcp" ? "ready" : "partial",
171
+ evidence: [`package name ${packageJson?.name ?? "unknown"}`],
172
+ whyItMatters: "One-command install keeps first-run friction low.",
173
+ },
174
+ {
175
+ id: "install_script",
176
+ label: "install script",
177
+ status: existsSync(installScriptPath) ? "ready" : "missing",
178
+ evidence: [installScriptPath],
179
+ whyItMatters: "Teams need a deterministic bootstrap path they can hand to each other.",
180
+ },
181
+ {
182
+ id: "claude_config",
183
+ label: "Claude Code config",
184
+ status: existsSync(claudeDir) ? "ready" : "missing",
185
+ evidence: [claudeDir],
186
+ whyItMatters: "Claude Code is a core adoption anchor for the target workflow.",
187
+ },
188
+ {
189
+ id: "cursor_config",
190
+ label: "Cursor config",
191
+ status: existsSync(cursorDir) ? "ready" : "partial",
192
+ evidence: [cursorDir],
193
+ whyItMatters: "Distribution is stronger if the second editor path is not an afterthought.",
194
+ },
195
+ {
196
+ id: "smithery",
197
+ label: "Smithery metadata",
198
+ status: existsSync(smitheryPath) ? "ready" : "missing",
199
+ evidence: [smitheryPath],
200
+ whyItMatters: "External MCP discovery is part of distribution, not a post-launch detail.",
201
+ },
202
+ {
203
+ id: "readme",
204
+ label: "README install docs",
205
+ status: existsSync(readmePath) ? "ready" : "missing",
206
+ evidence: [readmePath],
207
+ whyItMatters: "Setup trust drops fast if users cannot verify the install path from docs.",
208
+ },
209
+ {
210
+ id: "ledger_ui",
211
+ label: "proof and ledger UI",
212
+ status: existsSync(ledgerViewPath) ? "ready" : "partial",
213
+ evidence: [ledgerViewPath],
214
+ whyItMatters: "Trust compounds when receipts, packets, and sync state are inspectable.",
215
+ },
216
+ {
217
+ id: "dogfood_loop",
218
+ label: "dogfood loop",
219
+ status: existsSync(dogfoodScriptPath) ? "ready" : "partial",
220
+ evidence: [dogfoodScriptPath],
221
+ whyItMatters: "A product that cannot verify itself drifts faster than it learns.",
222
+ },
223
+ ];
224
+ }
225
+ async function collectRuntimeProbes() {
226
+ const probes = [
227
+ { id: "local_root", label: "Local server health", target: `${DEFAULT_SERVER_URL}/health` },
228
+ { id: "local_search", label: "Local search health", target: `${DEFAULT_SERVER_URL}/search/health` },
229
+ { id: "local_sync_bridge", label: "Local sync bridge health", target: `${DEFAULT_SERVER_URL}/sync-bridge/health` },
230
+ { id: "local_retention", label: "Local retention status", target: `${DEFAULT_SERVER_URL}/retention/status` },
231
+ { id: "prod_mcp", label: "Production MCP health", target: `${DEFAULT_PRODUCTION_MCP_URL}/health` },
232
+ ...(DEFAULT_PRODUCTION_APP_URL
233
+ ? [{ id: "prod_app", label: "Production app health", target: `${DEFAULT_PRODUCTION_APP_URL.replace(/\/$/, "")}/health` }]
234
+ : []),
235
+ ];
236
+ return Promise.all(probes.map((probe) => probeJson(probe.target, 5000, probe.label, probe.id)));
237
+ }
238
+ function buildSetupAndAttentionAnalysis(distributionSurfaces, runtimeProbes) {
239
+ const missingDistribution = distributionSurfaces.filter((surface) => surface.status !== "ready");
240
+ const failedRuntime = runtimeProbes.filter((probe) => !probe.ok);
241
+ const searchProbe = runtimeProbes.find((probe) => probe.id === "local_search");
242
+ const retentionProbe = runtimeProbes.find((probe) => probe.id === "local_retention");
243
+ const missingSearchTools = Array.isArray(searchProbe?.details?.tools)
244
+ ? ["founder_direction_assessment", "founder_local_weekly_reset"].filter((tool) => !(searchProbe?.details?.tools).includes(tool))
245
+ : [];
246
+ const retentionConnected = retentionProbe?.details?.connected !== false;
247
+ const setupFrictionScore = clamp(100 - missingDistribution.length * 12 - failedRuntime.length * 10, 0, 100);
248
+ const accessibilityScore = clamp(100
249
+ - (distributionSurfaces.some((surface) => surface.id === "readme" && surface.status !== "ready") ? 20 : 0)
250
+ - (distributionSurfaces.some((surface) => surface.id === "install_script" && surface.status !== "ready") ? 15 : 0)
251
+ - (runtimeProbes.some((probe) => probe.id === "local_root" && !probe.ok) ? 15 : 0), 0, 100);
252
+ const riskRegister = [
253
+ ...failedRuntime.map((probe) => ({
254
+ id: `runtime:${probe.id}`,
255
+ severity: probe.id.startsWith("prod_") ? "high" : "medium",
256
+ summary: `${probe.label} is not green`,
257
+ whyItMatters: `The self-dogfood loop loses trust when ${probe.label.toLowerCase()} cannot be verified.`,
258
+ nextAction: `Repair or verify ${probe.target} before claiming the runtime is healthy.`,
259
+ })),
260
+ ...missingDistribution.map((surface) => ({
261
+ id: `distribution:${surface.id}`,
262
+ severity: surface.status === "missing" ? "high" : "medium",
263
+ summary: `${surface.label} is ${surface.status}`,
264
+ whyItMatters: surface.whyItMatters,
265
+ nextAction: `Close the ${surface.label.toLowerCase()} gap so distribution and onboarding stay credible.`,
266
+ })),
267
+ ...(missingSearchTools.length
268
+ ? [{
269
+ id: "runtime:local_search_tools",
270
+ severity: "high",
271
+ summary: `Local search health is missing required founder tools: ${missingSearchTools.join(", ")}`,
272
+ whyItMatters: "Delta cannot claim founder-grade live intelligence if the local search server is not loading the core founder tools.",
273
+ nextAction: "Restart or repair the local search server until /search/health includes the founder direction and weekly reset tools.",
274
+ }]
275
+ : []),
276
+ ...(!retentionConnected
277
+ ? [{
278
+ id: "operations:retention_disconnected",
279
+ severity: "medium",
280
+ summary: "retention.sh is not connected",
281
+ whyItMatters: "Delta's self-dogfood loop is stronger when QA evidence and team memory are flowing into the same operating loop.",
282
+ nextAction: "Connect retention.sh or explicitly treat the missing QA loop as out-of-scope for this run.",
283
+ }]
284
+ : []),
285
+ ];
286
+ const angleCoverage = {
287
+ distribution: missingDistribution.length === 0 ? "strong" : "watch",
288
+ setup: failedRuntime.length === 0 && missingSearchTools.length === 0 ? "strong" : "watch",
289
+ accessibility: accessibilityScore >= 90 ? "strong" : accessibilityScore >= 70 ? "watch" : "gap",
290
+ trust: riskRegister.length === 0 ? "strong" : "watch",
291
+ returnLoops: retentionConnected ? "strong" : "watch",
292
+ };
293
+ return {
294
+ setupFrictionScore: clamp(setupFrictionScore - missingSearchTools.length * 12 - (retentionConnected ? 0 : 8), 0, 100),
295
+ accessibilityScore,
296
+ riskRegister,
297
+ angleCoverage,
298
+ attentionGuidance: [
299
+ "Anchor distribution to a high-frequency workflow people already use, not to abstract platform language.",
300
+ "Reduce setup friction until the first packet arrives before users need to make another decision.",
301
+ "Show proof, freshness, and next action on every result so returning feels safer than starting over elsewhere.",
302
+ "Treat accessibility as a compounding distribution advantage: clear copy, keyboard-safe flows, low-motion defaults, and readable install docs.",
303
+ ],
304
+ returnLoops: [
305
+ "Give users a repeated trigger: what changed, what matters, what next, in one place.",
306
+ "Keep local-first truth so the tool still works when the network or cloud trust drops.",
307
+ "Make outputs portable: share links, markdown, packets, and receipts should spread the product.",
308
+ "Collapse setup into one command, one preset, one first useful result.",
309
+ ],
310
+ };
311
+ }
312
+ const MAX_DELTA_PACKETS = 500; // P0: BOUND — cap table size
313
+ function ensureDeltaTable() {
314
+ const db = getDb();
315
+ db.exec(`
316
+ CREATE TABLE IF NOT EXISTS delta_packets (
317
+ id TEXT PRIMARY KEY,
318
+ type TEXT NOT NULL,
319
+ subject TEXT NOT NULL,
320
+ summary TEXT NOT NULL DEFAULT '',
321
+ persona TEXT NOT NULL DEFAULT 'founder',
322
+ confidence INTEGER NOT NULL DEFAULT 50,
323
+ freshness TEXT NOT NULL DEFAULT 'fresh',
324
+ visibility TEXT NOT NULL DEFAULT 'private',
325
+ share_url TEXT,
326
+ parent_packet_id TEXT,
327
+ payload TEXT NOT NULL DEFAULT '{}',
328
+ created_at TEXT NOT NULL,
329
+ expires_at TEXT NOT NULL,
330
+ supersedes TEXT
331
+ )
332
+ `);
333
+ // P0: Evict expired packets on every table access
334
+ db.exec(`DELETE FROM delta_packets WHERE expires_at < datetime('now')`);
335
+ // P0: Cap total rows — evict oldest beyond MAX
336
+ const count = db.prepare(`SELECT COUNT(*) as c FROM delta_packets`).get().c;
337
+ if (count > MAX_DELTA_PACKETS) {
338
+ db.prepare(`DELETE FROM delta_packets WHERE id IN (SELECT id FROM delta_packets ORDER BY created_at ASC LIMIT ?)`).run(count - MAX_DELTA_PACKETS);
339
+ }
340
+ }
341
+ function ensureWatchlistTable() {
342
+ const db = getDb();
343
+ db.exec(`
344
+ CREATE TABLE IF NOT EXISTS delta_watchlist (
345
+ id TEXT PRIMARY KEY,
346
+ entity_name TEXT NOT NULL,
347
+ added_at TEXT NOT NULL,
348
+ last_checked TEXT,
349
+ alert_preferences TEXT NOT NULL DEFAULT '["any_material"]',
350
+ change_count INTEGER NOT NULL DEFAULT 0,
351
+ last_change_summary TEXT
352
+ )
353
+ `);
354
+ }
355
+ function storePacket(packet) {
356
+ ensureDeltaTable();
357
+ const db = getDb();
358
+ const id = genId("delta_packet");
359
+ const createdAt = now();
360
+ const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(); // 24h default
361
+ db.prepare(`
362
+ INSERT INTO delta_packets (id, type, subject, summary, persona, confidence, freshness, visibility, payload, created_at, expires_at, parent_packet_id, supersedes)
363
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
364
+ `).run(id, packet.type, packet.subject, packet.summary || "", packet.persona || "founder", packet.confidence || 50, "fresh", packet.visibility || "private", JSON.stringify(packet.payload || {}), createdAt, expiresAt, packet.parentPacketId || null, packet.supersedes || null);
365
+ const result = { id, type: packet.type, subject: packet.subject, createdAt, expiresAt, ...packet };
366
+ // Fire-and-forget sync to dashboard via retention bridge
367
+ // This enables MCP → Dashboard data flow
368
+ const syncUrl = process.env.NODEBENCH_SERVER_URL || "http://localhost:3100";
369
+ fetch(`${syncUrl}/retention/push-packet`, {
370
+ method: "POST",
371
+ headers: { "Content-Type": "application/json" },
372
+ body: JSON.stringify({
373
+ type: packet.type,
374
+ subject: packet.subject,
375
+ summary: packet.summary,
376
+ persona: packet.persona,
377
+ confidence: packet.confidence,
378
+ payload: packet.payload,
379
+ }),
380
+ signal: AbortSignal.timeout(5000),
381
+ }).catch((e) => { console.warn(`[delta] Dashboard sync failed (best-effort): ${e instanceof Error ? e.message : String(e)}`); });
382
+ return result;
383
+ }
384
+ function getLatestPacketBySubjectPrefix(type, prefix) {
385
+ ensureDeltaTable();
386
+ const db = getDb();
387
+ return db.prepare(`SELECT * FROM delta_packets WHERE type = ? AND lower(subject) LIKE ? ORDER BY created_at DESC LIMIT 1`).get(type, `${prefix.toLowerCase()}%`);
388
+ }
389
+ function buildSelfDiligenceSections(entity, runtimeProbes, distributionSurfaces) {
390
+ const analysis = buildSetupAndAttentionAnalysis(distributionSurfaces, runtimeProbes);
391
+ const healthyRuntime = runtimeProbes.filter((probe) => probe.ok);
392
+ const failingRuntime = runtimeProbes.filter((probe) => !probe.ok);
393
+ return [
394
+ {
395
+ title: "Operating Reality",
396
+ sectionType: "analysis",
397
+ content: [
398
+ `Entity: **${entity}**`,
399
+ `Healthy runtime checks: ${healthyRuntime.length}/${runtimeProbes.length}`,
400
+ `Setup friction score: ${analysis.setupFrictionScore}/100`,
401
+ `Accessibility score: ${analysis.accessibilityScore}/100`,
402
+ ].join("\n"),
403
+ },
404
+ {
405
+ title: "Runtime Checks",
406
+ sectionType: "signal",
407
+ content: runtimeProbes
408
+ .map((probe) => `- ${probe.label}: ${probe.ok ? "OK" : "FAIL"} (${probe.summary})`)
409
+ .join("\n"),
410
+ },
411
+ {
412
+ title: "Distribution Surfaces",
413
+ sectionType: "signal",
414
+ content: distributionSurfaces
415
+ .map((surface) => `- ${surface.label}: ${surface.status.toUpperCase()} - ${surface.whyItMatters}`)
416
+ .join("\n"),
417
+ },
418
+ {
419
+ title: "Priority Gaps",
420
+ sectionType: "risk",
421
+ content: analysis.riskRegister.length
422
+ ? analysis.riskRegister.map((risk) => `- [${risk.severity}] ${risk.summary} -> ${risk.nextAction}`).join("\n")
423
+ : "- No high-confidence self-dogfood gaps detected in this pass.",
424
+ },
425
+ {
426
+ title: "Attention and Return Loops",
427
+ sectionType: "recommendation",
428
+ content: [...analysis.attentionGuidance, ...analysis.returnLoops].map((line) => `- ${line}`).join("\n"),
429
+ },
430
+ ...(failingRuntime.length
431
+ ? [{
432
+ title: "Immediate Repair Order",
433
+ sectionType: "recommendation",
434
+ content: failingRuntime.map((probe) => `- Repair ${probe.label} (${probe.target})`).join("\n"),
435
+ }]
436
+ : []),
437
+ ];
438
+ }
439
+ // ── Delta Brief ──────────────────────────────────────────────────────────
440
+ const deltaBrief = {
441
+ name: "delta_brief",
442
+ description: "Generate a 'what changed' brief since the last session. Produces a delta.brief packet summarizing changes in the product, market, and team context. Use at the start of each work session or daily standup.",
443
+ inputSchema: {
444
+ type: "object",
445
+ properties: {
446
+ since: {
447
+ type: "string",
448
+ description: "ISO timestamp to look back from. Defaults to 24 hours ago.",
449
+ },
450
+ persona: {
451
+ type: "string",
452
+ enum: ["founder", "banker", "ceo", "researcher", "operator", "student"],
453
+ description: "Role lens to shape the brief. Defaults to founder.",
454
+ },
455
+ include_watchlist: {
456
+ type: "boolean",
457
+ description: "Include watched entity changes in the brief. Defaults to true.",
458
+ },
459
+ context: {
460
+ type: "string",
461
+ description: "Additional context about what you're working on to make the brief more relevant.",
462
+ },
463
+ },
464
+ },
465
+ handler: async (args) => {
466
+ ensureDeltaTable();
467
+ ensureWatchlistTable();
468
+ const db = getDb();
469
+ const since = args.since || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
470
+ const persona = args.persona || "founder";
471
+ const includeWatchlist = args.include_watchlist !== false;
472
+ // Gather recent packets
473
+ const recentPackets = db.prepare(`SELECT * FROM delta_packets WHERE created_at > ? ORDER BY created_at DESC LIMIT 20`).all(since);
474
+ // Gather watchlist changes
475
+ let watchlistChanges = [];
476
+ if (includeWatchlist) {
477
+ watchlistChanges = db.prepare(`SELECT * FROM delta_watchlist WHERE last_checked > ? AND change_count > 0 ORDER BY last_checked DESC LIMIT 10`).all(since);
478
+ }
479
+ const sections = [];
480
+ // Product changes
481
+ if (recentPackets.length > 0) {
482
+ sections.push({
483
+ title: "Recent Activity",
484
+ sectionType: "signal",
485
+ content: recentPackets.map((p) => `- [${p.type}] ${p.subject}: ${p.summary}`).join("\n"),
486
+ });
487
+ }
488
+ // Watchlist alerts
489
+ if (watchlistChanges.length > 0) {
490
+ sections.push({
491
+ title: "Watchlist Alerts",
492
+ sectionType: "signal",
493
+ content: watchlistChanges.map((w) => `- ${w.entity_name}: ${w.last_change_summary || "change detected"} (${w.change_count} changes)`).join("\n"),
494
+ });
495
+ }
496
+ // ── Compounding Metrics (stored value — the moat) ────────────────
497
+ const allPackets = db.prepare(`SELECT type, COUNT(*) as c FROM delta_packets GROUP BY type`).all();
498
+ const totalPackets = allPackets.reduce((sum, r) => sum + r.c, 0);
499
+ const packetBreakdown = allPackets.map((r) => `${r.c} ${r.type}`).join(", ");
500
+ const allWatched = db.prepare(`SELECT COUNT(*) as c FROM delta_watchlist`).all();
501
+ const watchedCount = allWatched[0]?.c || 0;
502
+ const oldestPacket = db.prepare(`SELECT MIN(created_at) as oldest FROM delta_packets`).get();
503
+ const daysSinceFirst = oldestPacket?.oldest
504
+ ? Math.floor((Date.now() - new Date(oldestPacket.oldest).getTime()) / (24 * 60 * 60 * 1000))
505
+ : 0;
506
+ // Stale packet detection (packets older than 80% of their TTL)
507
+ const stalePackets = db.prepare(`SELECT type, subject, created_at, expires_at FROM delta_packets
508
+ WHERE julianday(expires_at) - julianday('now') < 0.2 * (julianday(expires_at) - julianday(created_at))
509
+ AND freshness != 'stale' ORDER BY expires_at ASC LIMIT 5`).all();
510
+ if (stalePackets.length > 0) {
511
+ // Mark them stale in the DB
512
+ for (const sp of stalePackets) {
513
+ db.prepare(`UPDATE delta_packets SET freshness = 'warming' WHERE subject = ? AND created_at = ?`)
514
+ .run(sp.subject, sp.created_at);
515
+ }
516
+ sections.push({
517
+ title: "Packets Going Stale",
518
+ sectionType: "signal",
519
+ content: stalePackets.map((sp) => `- [${sp.type}] ${sp.subject} — expires ${new Date(sp.expires_at).toLocaleDateString()}`).join("\n"),
520
+ });
521
+ }
522
+ // ── Watchlist Background Refresh ────────────────────────────────
523
+ // Check all watched entities and update their last_checked timestamp
524
+ const allWatchedEntities = db.prepare(`SELECT * FROM delta_watchlist ORDER BY added_at DESC`).all();
525
+ const refreshedEntities = [];
526
+ for (const w of allWatchedEntities) {
527
+ const lastChecked = w.last_checked;
528
+ const hoursSinceCheck = lastChecked
529
+ ? (Date.now() - new Date(lastChecked).getTime()) / (60 * 60 * 1000)
530
+ : Infinity;
531
+ // Auto-refresh entities not checked in 12+ hours
532
+ if (hoursSinceCheck >= 12) {
533
+ db.prepare(`UPDATE delta_watchlist SET last_checked = ? WHERE id = ?`).run(now(), w.id);
534
+ refreshedEntities.push(w.entity_name);
535
+ }
536
+ }
537
+ if (refreshedEntities.length > 0) {
538
+ sections.push({
539
+ title: "Watchlist Refreshed",
540
+ sectionType: "signal",
541
+ content: `Auto-checked ${refreshedEntities.length} entities: ${refreshedEntities.join(", ")}. Run \`delta_diligence\` on any to get live intelligence.`,
542
+ });
543
+ }
544
+ // Compounding investment summary
545
+ sections.push({
546
+ title: "Your NodeBench Investment",
547
+ sectionType: "analysis",
548
+ content: [
549
+ `${totalPackets} packets stored (${packetBreakdown || "none yet"})`,
550
+ `${watchedCount} entities monitored`,
551
+ daysSinceFirst > 0 ? `Active for ${daysSinceFirst} day${daysSinceFirst === 1 ? "" : "s"}` : "Just getting started",
552
+ stalePackets.length > 0 ? `${stalePackets.length} packets going stale — consider regenerating` : null,
553
+ ].filter(Boolean).join("\n"),
554
+ });
555
+ // Context-aware recommendations
556
+ sections.push({
557
+ title: "Recommended Actions",
558
+ sectionType: "recommendation",
559
+ content: [
560
+ recentPackets.length === 0 ? "- Run `delta_diligence` on your key entities to build your intelligence base" : null,
561
+ watchedCount === 0 ? "- Add entities to your watchlist with `delta_watch`" : null,
562
+ stalePackets.length > 0 ? "- Regenerate stale packets with `delta_diligence` or `delta_scan`" : null,
563
+ "- Create a decision memo with `delta_memo` for any pending decisions",
564
+ args.context ? `- Given your context: consider running \`delta_diligence\` on entities related to "${args.context}"` : null,
565
+ ].filter(Boolean).join("\n"),
566
+ });
567
+ const packet = storePacket({
568
+ type: "brief",
569
+ subject: `Daily Brief — ${new Date().toLocaleDateString("en-US", { weekday: "long", month: "short", day: "numeric" })}`,
570
+ summary: `${recentPackets.length} recent activities, ${watchlistChanges.length} watchlist alerts, ${totalPackets} total packets, ${watchedCount} entities watched`,
571
+ persona,
572
+ confidence: 80,
573
+ payload: {
574
+ sections, since,
575
+ recentPacketCount: recentPackets.length,
576
+ watchlistAlertCount: watchlistChanges.length,
577
+ compounding: { totalPackets, watchedCount, daysSinceFirst, staleCount: stalePackets.length, refreshedCount: refreshedEntities.length },
578
+ },
579
+ });
580
+ return {
581
+ content: [
582
+ {
583
+ type: "text",
584
+ text: JSON.stringify({
585
+ packet,
586
+ compounding: { totalPackets, packetBreakdown, watchedCount, daysSinceFirst, staleCount: stalePackets.length },
587
+ hint: totalPackets < 5
588
+ ? "Build your intelligence base: run delta_diligence on 3-5 key entities, then delta_watch to monitor them."
589
+ : `Your context is compounding (${totalPackets} packets over ${daysSinceFirst} days). Use delta_memo to turn signals into decisions.`,
590
+ nextTools: ["delta_diligence", "delta_memo", "delta_watch", "delta_handoff"],
591
+ }, null, 2),
592
+ },
593
+ ],
594
+ };
595
+ },
596
+ };
597
+ // ── Delta Diligence ──────────────────────────────────────────────────────
598
+ const deltaDiligence = {
599
+ name: "delta_diligence",
600
+ description: "Deep entity intelligence teardown. Produces a delta.diligence packet with signals, risks, opportunities, and evidence for any company or entity. Use for competitive analysis, investment diligence, or market research.",
601
+ inputSchema: {
602
+ type: "object",
603
+ properties: {
604
+ entity: {
605
+ type: "string",
606
+ description: "Company or entity name to investigate (e.g., 'Anthropic', 'Stripe', 'my competitor').",
607
+ },
608
+ depth: {
609
+ type: "string",
610
+ enum: ["quick", "deep"],
611
+ description: "Quick = 30-second scan. Deep = comprehensive teardown with web research. Defaults to quick.",
612
+ },
613
+ persona: {
614
+ type: "string",
615
+ enum: ["founder", "banker", "ceo", "researcher", "operator", "student"],
616
+ description: "Role lens shapes the output format and emphasis.",
617
+ },
618
+ focus: {
619
+ type: "string",
620
+ description: "Specific angle to focus on (e.g., 'pricing strategy', 'hiring patterns', 'product roadmap').",
621
+ },
622
+ },
623
+ required: ["entity"],
624
+ },
625
+ handler: async (args) => {
626
+ const entity = args.entity;
627
+ const depth = args.depth || "quick";
628
+ const persona = args.persona || "founder";
629
+ const focus = args.focus;
630
+ const entityLower = entity.toLowerCase();
631
+ const isSelfEntity = entityLower.includes("nodebench") || entityLower.includes("delta");
632
+ const runtimeProbes = isSelfEntity ? await collectRuntimeProbes() : [];
633
+ const distributionSurfaces = isSelfEntity ? getDistributionSurfaces() : [];
634
+ const sections = isSelfEntity
635
+ ? buildSelfDiligenceSections(entity, runtimeProbes, distributionSurfaces)
636
+ : [
637
+ {
638
+ title: "Entity Overview",
639
+ sectionType: "analysis",
640
+ content: `Intelligence scan for: **${entity}**\nDepth: ${depth}\nLens: ${persona}${focus ? `\nFocus: ${focus}` : ""}`,
641
+ },
642
+ {
643
+ title: "Signals",
644
+ sectionType: "signal",
645
+ content: [
646
+ "- Use `web_search` or your IDE's search to gather live intelligence on this entity",
647
+ "- Check recent funding, product launches, hiring patterns, and market positioning",
648
+ focus ? `- Specifically investigate: ${focus}` : null,
649
+ ].filter(Boolean).join("\n"),
650
+ },
651
+ {
652
+ title: "Recommended Next Steps",
653
+ sectionType: "recommendation",
654
+ content: [
655
+ `- Run \`delta_watch { entity: "${entity}" }\` to monitor future changes`,
656
+ `- Run \`delta_compare { entities: ["${entity}", "<competitor>"] }\` for competitive positioning`,
657
+ `- Run \`delta_memo\` to create a decision artifact based on these findings`,
658
+ `- Run \`delta_handoff\` to delegate deeper research to an agent`,
659
+ ].join("\n"),
660
+ },
661
+ ];
662
+ const packet = storePacket({
663
+ type: "diligence",
664
+ subject: `Diligence: ${entity}`,
665
+ summary: `${depth} intelligence scan on ${entity} through ${persona} lens`,
666
+ persona,
667
+ confidence: isSelfEntity ? 85 : depth === "deep" ? 75 : 50,
668
+ payload: {
669
+ entity,
670
+ depth,
671
+ focus,
672
+ sections,
673
+ runtimeProbes,
674
+ distributionSurfaces,
675
+ selfDiligence: isSelfEntity,
676
+ },
677
+ });
678
+ return {
679
+ content: [
680
+ {
681
+ type: "text",
682
+ text: JSON.stringify({
683
+ packet,
684
+ hint: `Diligence packet created for ${entity}. Use web_search to enrich with live data, then delta_memo to create a decision artifact.`,
685
+ nextTools: ["web_search", "delta_watch", "delta_compare", "delta_memo", "delta_handoff"],
686
+ }, null, 2),
687
+ },
688
+ ],
689
+ };
690
+ },
691
+ };
692
+ // ── Delta Handoff ────────────────────────────────────────────────────────
693
+ const deltaHandoff = {
694
+ name: "delta_handoff",
695
+ description: "Generate a delegation packet for handing off work to another agent or teammate. Produces a delta.handoff packet with full context bundle, acceptance criteria, and deadline.",
696
+ inputSchema: {
697
+ type: "object",
698
+ properties: {
699
+ task: {
700
+ type: "string",
701
+ description: "What needs to be done (e.g., 'Build the auth flow', 'Research pricing models').",
702
+ },
703
+ delegate_to: {
704
+ type: "string",
705
+ enum: ["claude_code", "openclaw", "teammate", "any"],
706
+ description: "Who should receive this handoff. Defaults to 'any'.",
707
+ },
708
+ context: {
709
+ type: "string",
710
+ description: "Additional context, findings, or decisions that the delegate needs.",
711
+ },
712
+ acceptance_criteria: {
713
+ type: "array",
714
+ items: { type: "string" },
715
+ description: "List of criteria that define 'done' for this task.",
716
+ },
717
+ deadline: {
718
+ type: "string",
719
+ description: "ISO timestamp or relative time (e.g., '2 hours', 'end of day').",
720
+ },
721
+ parent_packet_id: {
722
+ type: "string",
723
+ description: "ID of the packet that spawned this handoff (for lineage tracking).",
724
+ },
725
+ },
726
+ required: ["task"],
727
+ },
728
+ handler: async (args) => {
729
+ const task = args.task;
730
+ const delegateTo = args.delegate_to || "any";
731
+ const context = args.context || "";
732
+ const criteria = args.acceptance_criteria || [];
733
+ const deadline = args.deadline;
734
+ const parentPacketId = args.parent_packet_id;
735
+ const handoffPrompt = [
736
+ `## Task: ${task}`,
737
+ "",
738
+ context ? `## Context\n${context}` : null,
739
+ criteria.length > 0 ? `## Acceptance Criteria\n${criteria.map((c) => `- [ ] ${c}`).join("\n")}` : null,
740
+ deadline ? `## Deadline: ${deadline}` : null,
741
+ "",
742
+ "## Instructions",
743
+ "1. Review the context above",
744
+ "2. Execute the task",
745
+ "3. Report back with results and any blockers",
746
+ delegateTo === "claude_code" ? "4. Use NodeBench MCP tools for entity intelligence if needed" : null,
747
+ ].filter(Boolean).join("\n");
748
+ const packet = storePacket({
749
+ type: "handoff",
750
+ subject: `Handoff: ${task}`,
751
+ summary: `Task delegated to ${delegateTo}: ${task}`,
752
+ confidence: 90,
753
+ parentPacketId,
754
+ payload: { task, delegateTo, context, acceptanceCriteria: criteria, deadline, handoffPrompt },
755
+ });
756
+ return {
757
+ content: [
758
+ {
759
+ type: "text",
760
+ text: JSON.stringify({
761
+ packet,
762
+ handoffPrompt,
763
+ hint: `Handoff packet created. ${delegateTo === "claude_code" ? "Copy the handoff prompt into a new Claude Code session." : "Share the packet with your teammate."}`,
764
+ nextTools: ["delta_brief", "delta_watch"],
765
+ }, null, 2),
766
+ },
767
+ ],
768
+ };
769
+ },
770
+ };
771
+ // ── Delta Watch ──────────────────────────────────────────────────────────
772
+ const deltaWatch = {
773
+ name: "delta_watch",
774
+ description: "Add, remove, or list entities on your watchlist. Watched entities are checked for material changes during delta_brief runs. Use to monitor competitors, partners, or market segments.",
775
+ inputSchema: {
776
+ type: "object",
777
+ properties: {
778
+ action: {
779
+ type: "string",
780
+ enum: ["add", "remove", "list", "check"],
781
+ description: "Action to perform. 'add' adds an entity, 'remove' removes one, 'list' shows all, 'check' checks for changes now.",
782
+ },
783
+ entity: {
784
+ type: "string",
785
+ description: "Entity name (required for add/remove/check).",
786
+ },
787
+ alert_on: {
788
+ type: "array",
789
+ items: {
790
+ type: "string",
791
+ enum: ["pricing_change", "funding", "leadership", "product_launch", "legal", "any_material"],
792
+ },
793
+ description: "What types of changes to alert on. Defaults to ['any_material'].",
794
+ },
795
+ },
796
+ required: ["action"],
797
+ },
798
+ handler: async (args) => {
799
+ ensureWatchlistTable();
800
+ const db = getDb();
801
+ const action = args.action;
802
+ const entity = args.entity;
803
+ const alertOn = args.alert_on || ["any_material"];
804
+ if (action === "add") {
805
+ if (!entity)
806
+ return { content: [{ type: "text", text: "Error: entity name required for 'add' action." }] };
807
+ const existing = db.prepare(`SELECT id FROM delta_watchlist WHERE entity_name = ?`).get(entity);
808
+ if (existing) {
809
+ return { content: [{ type: "text", text: JSON.stringify({ status: "already_watching", entity, hint: "Entity is already on your watchlist." }) }] };
810
+ }
811
+ const id = genId("delta_watch");
812
+ db.prepare(`INSERT INTO delta_watchlist (id, entity_name, added_at, alert_preferences) VALUES (?, ?, ?, ?)`)
813
+ .run(id, entity, now(), JSON.stringify(alertOn));
814
+ return {
815
+ content: [{
816
+ type: "text",
817
+ text: JSON.stringify({
818
+ status: "added",
819
+ entity,
820
+ alertOn,
821
+ hint: `Now watching "${entity}". Changes will appear in your delta_brief. Run delta_watch { action: "check", entity: "${entity}" } to check now.`,
822
+ nextTools: ["delta_brief", "delta_diligence", "delta_watch"],
823
+ }, null, 2),
824
+ }],
825
+ };
826
+ }
827
+ if (action === "remove") {
828
+ if (!entity)
829
+ return { content: [{ type: "text", text: "Error: entity name required for 'remove' action." }] };
830
+ db.prepare(`DELETE FROM delta_watchlist WHERE entity_name = ?`).run(entity);
831
+ return { content: [{ type: "text", text: JSON.stringify({ status: "removed", entity }) }] };
832
+ }
833
+ if (action === "list") {
834
+ const all = db.prepare(`SELECT * FROM delta_watchlist ORDER BY added_at DESC`).all();
835
+ return {
836
+ content: [{
837
+ type: "text",
838
+ text: JSON.stringify({
839
+ watchlist: all.map((w) => ({
840
+ entity: w.entity_name,
841
+ addedAt: w.added_at,
842
+ lastChecked: w.last_checked,
843
+ changeCount: w.change_count,
844
+ alertPreferences: JSON.parse(w.alert_preferences),
845
+ })),
846
+ count: all.length,
847
+ hint: all.length === 0 ? "Your watchlist is empty. Use delta_watch { action: \"add\", entity: \"CompanyName\" } to start monitoring." : "Run delta_brief to see all watchlist changes in context.",
848
+ nextTools: ["delta_watch", "delta_brief", "delta_diligence"],
849
+ }, null, 2),
850
+ }],
851
+ };
852
+ }
853
+ if (action === "check") {
854
+ if (!entity)
855
+ return { content: [{ type: "text", text: "Error: entity name required for 'check' action." }] };
856
+ // Mark as checked
857
+ db.prepare(`UPDATE delta_watchlist SET last_checked = ? WHERE entity_name = ?`).run(now(), entity);
858
+ return {
859
+ content: [{
860
+ type: "text",
861
+ text: JSON.stringify({
862
+ status: "checked",
863
+ entity,
864
+ hint: `Use web_search to find recent changes for "${entity}", then update the watchlist. Run delta_diligence for a full teardown.`,
865
+ nextTools: ["web_search", "delta_diligence", "delta_brief"],
866
+ }, null, 2),
867
+ }],
868
+ };
869
+ }
870
+ return { content: [{ type: "text", text: "Error: action must be one of: add, remove, list, check" }] };
871
+ },
872
+ };
873
+ // ── Delta Memo ───────────────────────────────────────────────────────────
874
+ const deltaMemo = {
875
+ name: "delta_memo",
876
+ description: "Create a decision-ready memo artifact. Produces a delta.memo packet with recommendation, variables, scenarios, and evidence. Shareable with teammates and stakeholders.",
877
+ inputSchema: {
878
+ type: "object",
879
+ properties: {
880
+ decision: {
881
+ type: "string",
882
+ description: "The decision or question this memo addresses (e.g., 'Should we pivot to B2B?', 'Which cloud provider?').",
883
+ },
884
+ recommendation: {
885
+ type: "string",
886
+ description: "Your recommended course of action.",
887
+ },
888
+ evidence: {
889
+ type: "array",
890
+ items: { type: "string" },
891
+ description: "Supporting evidence, data points, or references.",
892
+ },
893
+ risks: {
894
+ type: "array",
895
+ items: { type: "string" },
896
+ description: "Key risks or downsides of the recommendation.",
897
+ },
898
+ alternatives: {
899
+ type: "array",
900
+ items: { type: "string" },
901
+ description: "Alternative options considered.",
902
+ },
903
+ persona: {
904
+ type: "string",
905
+ enum: ["founder", "banker", "ceo", "researcher", "operator"],
906
+ description: "Role lens for memo formatting.",
907
+ },
908
+ },
909
+ required: ["decision"],
910
+ },
911
+ handler: async (args) => {
912
+ const decision = args.decision;
913
+ const recommendation = args.recommendation || "Pending analysis";
914
+ const evidence = args.evidence || [];
915
+ const risks = args.risks || [];
916
+ const alternatives = args.alternatives || [];
917
+ const persona = args.persona || "founder";
918
+ const sections = [
919
+ { title: "Decision", sectionType: "analysis", content: decision },
920
+ { title: "Recommendation", sectionType: "recommendation", content: recommendation },
921
+ evidence.length > 0 ? { title: "Evidence", sectionType: "evidence", content: evidence.map((e) => `- ${e}`).join("\n") } : null,
922
+ risks.length > 0 ? { title: "Risks", sectionType: "risk", content: risks.map((r) => `- ${r}`).join("\n") } : null,
923
+ alternatives.length > 0 ? { title: "Alternatives Considered", sectionType: "analysis", content: alternatives.map((a) => `- ${a}`).join("\n") } : null,
924
+ ].filter(Boolean);
925
+ const packet = storePacket({
926
+ type: "memo",
927
+ subject: `Decision Memo: ${decision}`,
928
+ summary: `Recommendation: ${recommendation}. ${evidence.length} evidence points, ${risks.length} risks identified.`,
929
+ persona,
930
+ confidence: evidence.length > 2 ? 75 : 50,
931
+ payload: { decision, recommendation, evidence, risks, alternatives, sections },
932
+ });
933
+ return {
934
+ content: [{
935
+ type: "text",
936
+ text: JSON.stringify({
937
+ packet,
938
+ hint: "Memo created. Use delta_handoff to delegate implementation, or delta_watch to monitor related entities.",
939
+ nextTools: ["delta_handoff", "delta_watch", "delta_diligence", "delta_compare"],
940
+ }, null, 2),
941
+ }],
942
+ };
943
+ },
944
+ };
945
+ // ── Delta Scan ───────────────────────────────────────────────────────────
946
+ const deltaScan = {
947
+ name: "delta_scan",
948
+ description: "Run a self-diligence market coverage scan. Produces a delta.market packet analyzing what NodeBench Delta covers well, what gaps exist, and what competitors are doing. The product eats its own dogfood.",
949
+ inputSchema: {
950
+ type: "object",
951
+ properties: {
952
+ layers: {
953
+ type: "array",
954
+ items: { type: "number" },
955
+ description: "Which layers to scan (1=Market Baseline, 2=Job Coverage, 3=Workflow Friction, 4=Competitive Delta, 5=Trend Exposure). Defaults to all.",
956
+ },
957
+ depth: {
958
+ type: "string",
959
+ enum: ["quick", "deep"],
960
+ description: "Quick = summary check. Deep = comprehensive analysis with web research.",
961
+ },
962
+ },
963
+ },
964
+ handler: async (args) => {
965
+ ensureDeltaTable();
966
+ const db = getDb();
967
+ const layers = args.layers || [1, 2, 3, 4, 5];
968
+ const depth = args.depth || "quick";
969
+ const runtimeProbes = await collectRuntimeProbes();
970
+ const distributionSurfaces = getDistributionSurfaces();
971
+ const setupAnalysis = buildSetupAndAttentionAnalysis(distributionSurfaces, runtimeProbes);
972
+ // Count existing packets to gauge system health
973
+ const packetCount = db.prepare(`SELECT COUNT(*) as c FROM delta_packets`).get().c;
974
+ const watchlistCount = (() => {
975
+ try {
976
+ ensureWatchlistTable();
977
+ return db.prepare(`SELECT COUNT(*) as c FROM delta_watchlist`).get().c;
978
+ }
979
+ catch {
980
+ return 0;
981
+ }
982
+ })();
983
+ const layerResults = [];
984
+ if (layers.includes(1)) {
985
+ const runtimeScore = runtimeProbes.length
986
+ ? Math.round((runtimeProbes.filter((probe) => probe.ok).length / runtimeProbes.length) * 100)
987
+ : 0;
988
+ layerResults.push({
989
+ layer: 1,
990
+ name: "Market Baseline",
991
+ score: clamp(Math.round((runtimeScore + setupAnalysis.accessibilityScore) / 2), 0, 100),
992
+ trend: runtimeProbes.some((probe) => !probe.ok) ? "watch" : "improving",
993
+ findings: runtimeProbes.map((probe) => `${probe.label}: ${probe.ok ? "OK" : "FAIL"} (${probe.summary})`),
994
+ });
995
+ }
996
+ if (layers.includes(2)) {
997
+ layerResults.push({
998
+ layer: 2,
999
+ name: "Job Coverage",
1000
+ score: clamp(68 + Math.min(packetCount, 10), 0, 100),
1001
+ trend: "improving",
1002
+ findings: [
1003
+ "Founder: pressure-test, packetize, delegate, and monitor are covered locally.",
1004
+ "Banker: comparison and memo paths exist, but live financial depth still depends on external data.",
1005
+ "CEO/operator: decision memo and handoff flows exist, but private-context defaults can still tighten.",
1006
+ `Historical packet memory: ${packetCount} packets available for compounding.`,
1007
+ `Watchlist coverage: ${watchlistCount} tracked entities.`,
1008
+ "Hackathon teams: install, share, and retention bridge paths exist in the same local-first loop.",
1009
+ ],
1010
+ });
1011
+ }
1012
+ if (layers.includes(3)) {
1013
+ layerResults.push({
1014
+ layer: 3,
1015
+ name: "Workflow Friction",
1016
+ score: setupAnalysis.setupFrictionScore,
1017
+ trend: setupAnalysis.setupFrictionScore >= 75 ? "improving" : "watch",
1018
+ findings: [
1019
+ ...distributionSurfaces.map((surface) => `${surface.label}: ${surface.status.toUpperCase()} - ${surface.whyItMatters}`),
1020
+ "Search -> Understand: LOW friction (clear input, role lenses)",
1021
+ "Understand -> Compare: MEDIUM friction (delta_compare exists, no side-by-side UI yet)",
1022
+ "Compare -> Decide: LOW friction (delta_memo creates decision artifacts)",
1023
+ "Decide -> Act: MEDIUM friction (delta_handoff generates context, manual copy to agent)",
1024
+ `Act -> Monitor: ${watchlistCount > 0 ? "LOW" : "MEDIUM"} friction (delta_watch + delta_brief pipeline active)`,
1025
+ ],
1026
+ });
1027
+ }
1028
+ if (layers.includes(4)) {
1029
+ layerResults.push({
1030
+ layer: 4,
1031
+ name: "Competitive Delta",
1032
+ score: 65,
1033
+ trend: "stable",
1034
+ findings: [
1035
+ "vs Supermemory: They own memory substrate narrative. We own entity intelligence + decisions.",
1036
+ "vs Perplexity: They own general search. We own entity-specific deep analysis.",
1037
+ "vs PitchBook: They own financial data depth. We own MCP-native + real-time intelligence.",
1038
+ "vs Linear: Different category (project mgmt vs intelligence). Learn from their speed obsession.",
1039
+ ],
1040
+ });
1041
+ }
1042
+ if (layers.includes(5)) {
1043
+ layerResults.push({
1044
+ layer: 5,
1045
+ name: "Trend Exposure",
1046
+ score: clamp(Math.round((setupAnalysis.accessibilityScore + setupAnalysis.setupFrictionScore) / 2), 0, 100),
1047
+ trend: "improving",
1048
+ findings: [
1049
+ `Distribution: ${setupAnalysis.angleCoverage.distribution.toUpperCase()}`,
1050
+ `Setup: ${setupAnalysis.angleCoverage.setup.toUpperCase()}`,
1051
+ `Accessibility: ${setupAnalysis.angleCoverage.accessibility.toUpperCase()}`,
1052
+ `Trust: ${setupAnalysis.angleCoverage.trust.toUpperCase()}`,
1053
+ `Return loops: ${setupAnalysis.angleCoverage.returnLoops.toUpperCase()}`,
1054
+ ...setupAnalysis.attentionGuidance,
1055
+ ...setupAnalysis.returnLoops,
1056
+ "MCP universal standard: FUTURE-PROOF (444 tools, hackathon + delta presets)",
1057
+ "Memory as table stakes: MODERATE RISK (differentiate on causal memory + packets)",
1058
+ "Agent orchestration maturity: STRONG (command panel, auto-router, handoff protocol)",
1059
+ `Proactive intelligence: ${watchlistCount > 0 ? "IMPROVING" : "MODERATE RISK"} (delta_watch live, needs automated background refresh)`,
1060
+ "Shareable artifacts as distribution: IMPROVING (/company/:slug + /memo/:id + /embed live)",
1061
+ "Hackathon distribution: STRONG (retention.sh pairing, hackathon preset, CLI verbs)",
1062
+ ],
1063
+ });
1064
+ }
1065
+ const avgScore = Math.round(layerResults.reduce((sum, l) => sum + l.score, 0) / layerResults.length);
1066
+ const packet = storePacket({
1067
+ type: "market",
1068
+ subject: `Market Coverage Scan — ${new Date().toLocaleDateString()}`,
1069
+ summary: `Overall score: ${avgScore}/100 across ${layerResults.length} layers. ${packetCount} packets stored, ${watchlistCount} entities watched.`,
1070
+ confidence: depth === "deep" ? 75 : 60,
1071
+ payload: { layerResults, avgScore, packetCount, watchlistCount, depth, runtimeProbes, distributionSurfaces, setupAnalysis },
1072
+ });
1073
+ return {
1074
+ content: [{
1075
+ type: "text",
1076
+ text: JSON.stringify({
1077
+ packet,
1078
+ overallScore: avgScore,
1079
+ layerResults,
1080
+ systemHealth: { packetCount, watchlistCount, runtimeProbes },
1081
+ distributionSurfaces,
1082
+ setupAnalysis,
1083
+ riskRegister: setupAnalysis.riskRegister,
1084
+ hint: "Use this scan to close the next highest-friction runtime or distribution gap before adding more surface area.",
1085
+ nextTools: ["delta_self_dogfood", "delta_diligence", "delta_memo", "delta_handoff"],
1086
+ }, null, 2),
1087
+ }],
1088
+ };
1089
+ },
1090
+ };
1091
+ // ── Delta Compare ────────────────────────────────────────────────────────
1092
+ const deltaCompare = {
1093
+ name: "delta_compare",
1094
+ description: "Side-by-side entity comparison. Produces a delta.diligence comparison packet highlighting differences, strengths, and weaknesses across 2-4 entities.",
1095
+ inputSchema: {
1096
+ type: "object",
1097
+ properties: {
1098
+ entities: {
1099
+ type: "array",
1100
+ items: { type: "string" },
1101
+ description: "2-4 entity names to compare (e.g., ['Stripe', 'Square', 'Adyen']).",
1102
+ minItems: 2,
1103
+ maxItems: 4,
1104
+ },
1105
+ metrics: {
1106
+ type: "array",
1107
+ items: { type: "string" },
1108
+ description: "Specific metrics or dimensions to compare on (e.g., ['pricing', 'market share', 'developer experience']).",
1109
+ },
1110
+ persona: {
1111
+ type: "string",
1112
+ enum: ["founder", "banker", "ceo", "researcher", "operator"],
1113
+ description: "Role lens for comparison format.",
1114
+ },
1115
+ },
1116
+ required: ["entities"],
1117
+ },
1118
+ handler: async (args) => {
1119
+ const entities = args.entities;
1120
+ const metrics = args.metrics || ["market position", "key strengths", "key weaknesses", "recent changes"];
1121
+ const persona = args.persona || "founder";
1122
+ const comparisonGrid = entities.map((entity) => {
1123
+ const sourcePacket = getLatestPacketBySubjectPrefix("diligence", `Diligence: ${entity}`);
1124
+ const payload = parseJson(sourcePacket?.payload, {});
1125
+ const sectionText = (payload.sections ?? [])
1126
+ .map((section) => `${section.title ?? "section"}: ${section.content ?? ""}`)
1127
+ .join("\n");
1128
+ return {
1129
+ entity,
1130
+ sourcePacketId: sourcePacket?.id ?? null,
1131
+ metrics: metrics.map((metric) => ({
1132
+ metric,
1133
+ value: sectionText
1134
+ ? `From saved diligence: ${sectionText.slice(0, 220)}${sectionText.length > 220 ? "..." : ""}`
1135
+ : `[Run delta_diligence on "${entity}" to populate]`,
1136
+ })),
1137
+ };
1138
+ });
1139
+ const populatedCount = comparisonGrid.filter((entry) => entry.sourcePacketId).length;
1140
+ const packet = storePacket({
1141
+ type: "diligence",
1142
+ subject: `Comparison: ${entities.join(" vs ")}`,
1143
+ summary: `Side-by-side comparison of ${entities.length} entities across ${metrics.length} dimensions`,
1144
+ persona,
1145
+ confidence: populatedCount > 0 ? 65 : 40,
1146
+ payload: { entities, metrics, comparisonGrid, isComparison: true, populatedCount },
1147
+ });
1148
+ return {
1149
+ content: [{
1150
+ type: "text",
1151
+ text: JSON.stringify({
1152
+ packet,
1153
+ comparisonGrid,
1154
+ hint: populatedCount > 0
1155
+ ? `Comparison reused ${populatedCount} saved diligence packet(s). Run delta_diligence on any stale entity before making a decision.`
1156
+ : `Comparison scaffold created. Run delta_diligence on each entity to populate: ${entities.map((e) => `delta_diligence { entity: "${e}" }`).join(", ")}`,
1157
+ nextTools: ["delta_diligence", "delta_memo", "delta_handoff"],
1158
+ }, null, 2),
1159
+ }],
1160
+ };
1161
+ },
1162
+ };
1163
+ // ── Delta Retain ─────────────────────────────────────────────────────────
1164
+ const deltaReview = {
1165
+ name: "delta_review",
1166
+ description: "Reconcile a forecast or recommendation against reality. Produces a delta.review packet so the next decision uses outcomes instead of memory drift.",
1167
+ inputSchema: {
1168
+ type: "object",
1169
+ properties: {
1170
+ forecast: {
1171
+ type: "string",
1172
+ description: "What you expected to happen.",
1173
+ },
1174
+ outcome: {
1175
+ type: "string",
1176
+ description: "What actually happened.",
1177
+ },
1178
+ lessons: {
1179
+ type: "array",
1180
+ items: { type: "string" },
1181
+ description: "What to keep, stop, or change in the next cycle.",
1182
+ },
1183
+ confidence_delta: {
1184
+ type: "number",
1185
+ description: "How much confidence moved in points, positive or negative.",
1186
+ },
1187
+ },
1188
+ required: ["forecast", "outcome"],
1189
+ },
1190
+ handler: async (args) => {
1191
+ const forecast = args.forecast;
1192
+ const outcome = args.outcome;
1193
+ const lessons = args.lessons || [];
1194
+ const confidenceDelta = typeof args.confidence_delta === "number" ? args.confidence_delta : 0;
1195
+ const packet = storePacket({
1196
+ type: "review",
1197
+ subject: `Review: ${forecast.slice(0, 72)}${forecast.length > 72 ? "..." : ""}`,
1198
+ summary: `Reality check recorded with confidence delta ${confidenceDelta >= 0 ? "+" : ""}${confidenceDelta}.`,
1199
+ confidence: 85,
1200
+ payload: { forecast, outcome, lessons, confidenceDelta },
1201
+ });
1202
+ return {
1203
+ content: [{
1204
+ type: "text",
1205
+ text: JSON.stringify({
1206
+ packet,
1207
+ hint: "Use review packets to stop repeating wrong assumptions in the next delta scan or memo.",
1208
+ nextTools: ["delta_scan", "delta_memo", "delta_self_dogfood"],
1209
+ }, null, 2),
1210
+ }],
1211
+ };
1212
+ },
1213
+ };
1214
+ const deltaSelfDogfood = {
1215
+ name: "delta_self_dogfood",
1216
+ description: "Dogfood NodeBench Delta on itself. Verifies runtime health, setup friction, distribution surfaces, and compounding return loops, then emits a repair-ready delta.market packet.",
1217
+ inputSchema: {
1218
+ type: "object",
1219
+ properties: {
1220
+ entity: {
1221
+ type: "string",
1222
+ description: "Defaults to NodeBench Delta.",
1223
+ },
1224
+ include_review: {
1225
+ type: "boolean",
1226
+ description: "Also emit a delta.review packet summarizing the current self-dogfood verdict.",
1227
+ },
1228
+ },
1229
+ },
1230
+ handler: async (args) => {
1231
+ const entity = args.entity || "NodeBench Delta";
1232
+ const runtimeProbes = await collectRuntimeProbes();
1233
+ const distributionSurfaces = getDistributionSurfaces();
1234
+ const setupAnalysis = buildSetupAndAttentionAnalysis(distributionSurfaces, runtimeProbes);
1235
+ const sections = buildSelfDiligenceSections(entity, runtimeProbes, distributionSurfaces);
1236
+ const packet = storePacket({
1237
+ type: "market",
1238
+ subject: `Self Dogfood: ${entity}`,
1239
+ summary: `Delta self-check completed with setup friction ${setupAnalysis.setupFrictionScore}/100 and accessibility ${setupAnalysis.accessibilityScore}/100.`,
1240
+ confidence: 82,
1241
+ payload: { entity, runtimeProbes, distributionSurfaces, setupAnalysis, sections, dogfood: true },
1242
+ });
1243
+ let reviewPacket = null;
1244
+ if (args.include_review !== false) {
1245
+ reviewPacket = storePacket({
1246
+ type: "review",
1247
+ subject: `Review: ${entity} self-dogfood`,
1248
+ summary: `${setupAnalysis.riskRegister.length} self-dogfood risks logged.`,
1249
+ confidence: 80,
1250
+ payload: {
1251
+ forecast: "NodeBench Delta should be easy to install, easy to trust, and strong enough to verify itself continuously.",
1252
+ outcome: setupAnalysis.riskRegister.length
1253
+ ? `Open risks remain: ${setupAnalysis.riskRegister.map((risk) => risk.summary).join("; ")}`
1254
+ : "Current self-dogfood pass found no high-confidence runtime or distribution blockers.",
1255
+ lessons: setupAnalysis.attentionGuidance,
1256
+ confidenceDelta: setupAnalysis.riskRegister.length ? -8 : 4,
1257
+ },
1258
+ });
1259
+ }
1260
+ return {
1261
+ content: [{
1262
+ type: "text",
1263
+ text: JSON.stringify({
1264
+ packet,
1265
+ reviewPacket,
1266
+ runtimeProbes,
1267
+ distributionSurfaces,
1268
+ setupAnalysis,
1269
+ hint: "Fix the first high-severity runtime or distribution risk before shipping new Delta surface area.",
1270
+ nextTools: ["delta_scan", "delta_review", "delta_memo", "delta_handoff"],
1271
+ }, null, 2),
1272
+ }],
1273
+ };
1274
+ },
1275
+ };
1276
+ const deltaRetain = {
1277
+ name: "delta_retain",
1278
+ description: "Preserve context for future sessions. Produces a delta.retain packet storing important notes, decisions, meeting outcomes, or research findings that should persist across sessions.",
1279
+ inputSchema: {
1280
+ type: "object",
1281
+ properties: {
1282
+ content: {
1283
+ type: "string",
1284
+ description: "The context to preserve (meeting notes, decisions, research findings, etc.).",
1285
+ },
1286
+ content_type: {
1287
+ type: "string",
1288
+ enum: ["meeting_notes", "decision", "research", "observation", "action_item", "general"],
1289
+ description: "Type of content being retained. Defaults to 'general'.",
1290
+ },
1291
+ ttl_days: {
1292
+ type: "number",
1293
+ description: "How many days to keep this context. Defaults to 30.",
1294
+ },
1295
+ tags: {
1296
+ type: "array",
1297
+ items: { type: "string" },
1298
+ description: "Tags for organizing retained context.",
1299
+ },
1300
+ },
1301
+ required: ["content"],
1302
+ },
1303
+ handler: async (args) => {
1304
+ const content = args.content;
1305
+ const contentType = args.content_type || "general";
1306
+ const ttlDays = args.ttl_days || 30;
1307
+ const tags = args.tags || [];
1308
+ const expiresAt = new Date(Date.now() + ttlDays * 24 * 60 * 60 * 1000).toISOString();
1309
+ const packet = storePacket({
1310
+ type: "retain",
1311
+ subject: `Retained: ${contentType} — ${content.slice(0, 60)}${content.length > 60 ? "..." : ""}`,
1312
+ summary: content.slice(0, 200),
1313
+ confidence: 95,
1314
+ payload: { content, contentType, tags, ttlDays },
1315
+ });
1316
+ // Override the default expiry with the custom TTL
1317
+ const db = getDb();
1318
+ db.prepare(`UPDATE delta_packets SET expires_at = ? WHERE id = ?`).run(expiresAt, packet.id);
1319
+ return {
1320
+ content: [{
1321
+ type: "text",
1322
+ text: JSON.stringify({
1323
+ packet: { ...packet, expiresAt },
1324
+ hint: `Context retained for ${ttlDays} days. It will appear in future delta_brief results. Tags: ${tags.join(", ") || "none"}`,
1325
+ nextTools: ["delta_brief", "delta_memo"],
1326
+ }, null, 2),
1327
+ }],
1328
+ };
1329
+ },
1330
+ };
1331
+ // ── Delta Packets List (utility) ─────────────────────────────────────────
1332
+ const deltaPackets = {
1333
+ name: "delta_packets",
1334
+ description: "List recent delta packets. View your packet history, filter by type, and track lineage.",
1335
+ inputSchema: {
1336
+ type: "object",
1337
+ properties: {
1338
+ type: {
1339
+ type: "string",
1340
+ enum: ["brief", "diligence", "handoff", "watchlist", "memo", "market", "review", "retain", "all"],
1341
+ description: "Filter by packet type. Defaults to 'all'.",
1342
+ },
1343
+ limit: {
1344
+ type: "number",
1345
+ description: "Maximum number of packets to return. Defaults to 20.",
1346
+ },
1347
+ },
1348
+ },
1349
+ handler: async (args) => {
1350
+ ensureDeltaTable();
1351
+ const db = getDb();
1352
+ const type = args.type || "all";
1353
+ const limit = args.limit || 20;
1354
+ const query = type === "all"
1355
+ ? `SELECT * FROM delta_packets ORDER BY created_at DESC LIMIT ?`
1356
+ : `SELECT * FROM delta_packets WHERE type = ? ORDER BY created_at DESC LIMIT ?`;
1357
+ const packets = type === "all"
1358
+ ? db.prepare(query).all(limit)
1359
+ : db.prepare(query).all(type, limit);
1360
+ return {
1361
+ content: [{
1362
+ type: "text",
1363
+ text: JSON.stringify({
1364
+ packets: packets.map((p) => ({
1365
+ id: p.id,
1366
+ type: p.type,
1367
+ subject: p.subject,
1368
+ summary: p.summary,
1369
+ confidence: p.confidence,
1370
+ freshness: p.freshness,
1371
+ createdAt: p.created_at,
1372
+ expiresAt: p.expires_at,
1373
+ })),
1374
+ count: packets.length,
1375
+ hint: "Use the packet ID with delta_handoff { parent_packet_id: \"...\" } to create linked handoffs.",
1376
+ nextTools: ["delta_brief", "delta_diligence", "delta_memo", "delta_handoff"],
1377
+ }, null, 2),
1378
+ }],
1379
+ };
1380
+ },
1381
+ };
1382
+ // ── Retention Bridge Tools ───────────────────────────────────────────────
1383
+ const retentionStatus = {
1384
+ name: "retention_status",
1385
+ description: "Check retention.sh connection status and QA metrics. Shows team code, QA score, member count, and last sync time. Use to verify retention.sh integration is working.",
1386
+ inputSchema: {
1387
+ type: "object",
1388
+ properties: {
1389
+ team_code: {
1390
+ type: "string",
1391
+ description: "Retention.sh team code (e.g., 'C47DRF'). Auto-detected if retention.sh is running.",
1392
+ },
1393
+ },
1394
+ },
1395
+ handler: async (args) => {
1396
+ const teamCode = args.team_code || process.env.RETENTION_TEAM || "";
1397
+ // Check if retention.sh is accessible
1398
+ let retentionReachable = false;
1399
+ let qaScore = null;
1400
+ let memberCount = null;
1401
+ let tokensSaved = null;
1402
+ if (teamCode) {
1403
+ try {
1404
+ const controller = new AbortController();
1405
+ const timeout = setTimeout(() => controller.abort(), 5000);
1406
+ const res = await fetch(`https://retention.sh/api/team/${teamCode}/status`, { signal: controller.signal });
1407
+ clearTimeout(timeout);
1408
+ if (res.ok) {
1409
+ const data = await res.json();
1410
+ retentionReachable = true;
1411
+ qaScore = data.qaScore;
1412
+ memberCount = data.memberCount;
1413
+ tokensSaved = data.tokensSaved;
1414
+ }
1415
+ }
1416
+ catch {
1417
+ // retention.sh not reachable — that's fine, we degrade gracefully
1418
+ }
1419
+ }
1420
+ return {
1421
+ content: [{
1422
+ type: "text",
1423
+ text: JSON.stringify({
1424
+ connected: retentionReachable,
1425
+ teamCode: teamCode || "not configured",
1426
+ qaScore,
1427
+ memberCount,
1428
+ tokensSaved,
1429
+ dashboardUrl: teamCode ? `https://retention.sh/memory/team?team=${teamCode}` : null,
1430
+ hint: retentionReachable
1431
+ ? `retention.sh is connected. Team ${teamCode} has ${memberCount} members. QA score: ${qaScore}/100.`
1432
+ : teamCode
1433
+ ? `retention.sh team ${teamCode} is not reachable. Check your internet connection or set RETENTION_TEAM env var.`
1434
+ : "No team code configured. Set RETENTION_TEAM env var or pass team_code parameter. Install: RETENTION_TEAM=<CODE> curl -sL retention.sh/install.sh | bash",
1435
+ nextTools: ["delta_brief", "delta_scan"],
1436
+ }, null, 2),
1437
+ }],
1438
+ };
1439
+ },
1440
+ };
1441
+ const retentionSync = {
1442
+ name: "retention_sync",
1443
+ description: "Sync data between NodeBench Delta and retention.sh. Pushes delta packets as team context and pulls QA findings as watchlist signals.",
1444
+ inputSchema: {
1445
+ type: "object",
1446
+ properties: {
1447
+ direction: {
1448
+ type: "string",
1449
+ enum: ["push", "pull", "both"],
1450
+ description: "Push delta packets to retention.sh, pull QA findings from it, or both. Defaults to 'both'.",
1451
+ },
1452
+ team_code: {
1453
+ type: "string",
1454
+ description: "Retention.sh team code.",
1455
+ },
1456
+ },
1457
+ },
1458
+ handler: async (args) => {
1459
+ const direction = args.direction || "both";
1460
+ const teamCode = args.team_code || process.env.RETENTION_TEAM || "";
1461
+ if (!teamCode) {
1462
+ return {
1463
+ content: [{
1464
+ type: "text",
1465
+ text: JSON.stringify({
1466
+ status: "error",
1467
+ message: "No team code configured. Set RETENTION_TEAM env var or pass team_code parameter.",
1468
+ hint: "Install retention.sh first: RETENTION_TEAM=<CODE> curl -sL retention.sh/install.sh | bash",
1469
+ }, null, 2),
1470
+ }],
1471
+ };
1472
+ }
1473
+ const results = { direction, teamCode };
1474
+ if (direction === "push" || direction === "both") {
1475
+ ensureDeltaTable();
1476
+ const db = getDb();
1477
+ const recentPackets = db.prepare(`SELECT * FROM delta_packets WHERE created_at > ? ORDER BY created_at DESC LIMIT 10`).all(new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString());
1478
+ results.pushed = {
1479
+ packetCount: recentPackets.length,
1480
+ types: [...new Set(recentPackets.map((p) => p.type))],
1481
+ status: "ready_to_sync",
1482
+ note: "Packets are available for retention.sh to pull via the shared context API at /shared-context/packets",
1483
+ };
1484
+ }
1485
+ if (direction === "pull" || direction === "both") {
1486
+ results.pulled = {
1487
+ status: "ready_to_receive",
1488
+ note: "retention.sh QA findings will be ingested as delta_watchlist signals when retention.sh pushes via /shared-context/publish",
1489
+ };
1490
+ }
1491
+ return {
1492
+ content: [{
1493
+ type: "text",
1494
+ text: JSON.stringify({
1495
+ ...results,
1496
+ hint: "Sync configured. retention.sh and NodeBench Delta will exchange data via the shared context protocol.",
1497
+ dashboardUrl: `https://retention.sh/memory/team?team=${teamCode}`,
1498
+ nextTools: ["retention_status", "delta_brief", "delta_watch"],
1499
+ }, null, 2),
1500
+ }],
1501
+ };
1502
+ },
1503
+ };
1504
+ // ── Export ────────────────────────────────────────────────────────────────
1505
+ export function createDeltaTools() {
1506
+ return [
1507
+ deltaBrief,
1508
+ deltaDiligence,
1509
+ deltaHandoff,
1510
+ deltaWatch,
1511
+ deltaMemo,
1512
+ deltaScan,
1513
+ deltaCompare,
1514
+ deltaReview,
1515
+ deltaSelfDogfood,
1516
+ deltaRetain,
1517
+ deltaPackets,
1518
+ retentionStatus,
1519
+ retentionSync,
1520
+ ];
1521
+ }
1522
+ //# sourceMappingURL=deltaTools.js.map