nodebench-mcp 2.61.0 → 2.63.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.
- package/dist/benchmarks/llmJudgeEval.js +417 -152
- package/dist/benchmarks/llmJudgeEval.js.map +1 -1
- package/dist/benchmarks/searchQualityEval.js +2 -2
- package/dist/benchmarks/searchQualityEval.js.map +1 -1
- package/dist/tools/contentSynthesis.d.ts +42 -0
- package/dist/tools/contentSynthesis.js +185 -0
- package/dist/tools/contentSynthesis.js.map +1 -0
- package/dist/tools/founderLocalPipeline.js +80 -18
- package/dist/tools/founderLocalPipeline.js.map +1 -1
- package/dist/tools/founderTools.js +568 -0
- package/dist/tools/founderTools.js.map +1 -1
- package/dist/tools/llmJudgeLoop.js +5 -5
- package/dist/tools/llmJudgeLoop.js.map +1 -1
- package/package.json +1 -1
|
@@ -1581,5 +1581,573 @@ ${metaHtml}
|
|
|
1581
1581
|
return { error: true, message: `Unhandled format: ${format}` };
|
|
1582
1582
|
},
|
|
1583
1583
|
},
|
|
1584
|
+
// ─── 6. founder_local_synthesize ──────────────────────────────────────────
|
|
1585
|
+
{
|
|
1586
|
+
name: "founder_local_synthesize",
|
|
1587
|
+
description: "The MOST IMPORTANT founder tool — takes a user query + local SQLite context " +
|
|
1588
|
+
"and uses Gemini 3.1 Flash Lite to synthesize a real, structured analysis. " +
|
|
1589
|
+
"Gathers: causal_events (last 20), founder_packets (latest for entity), " +
|
|
1590
|
+
"causal_important_changes (unresolved), tracking_actions (last 10), " +
|
|
1591
|
+
"session_summaries (latest). Optionally enriches with web_search. " +
|
|
1592
|
+
"Falls back to heuristic synthesis if no GEMINI_API_KEY.",
|
|
1593
|
+
inputSchema: {
|
|
1594
|
+
type: "object",
|
|
1595
|
+
properties: {
|
|
1596
|
+
query: {
|
|
1597
|
+
type: "string",
|
|
1598
|
+
description: "The user's question or analysis request",
|
|
1599
|
+
},
|
|
1600
|
+
entityId: {
|
|
1601
|
+
type: "string",
|
|
1602
|
+
description: "Optional entity ID to scope context gathering",
|
|
1603
|
+
},
|
|
1604
|
+
includeWeb: {
|
|
1605
|
+
type: "boolean",
|
|
1606
|
+
description: "If true, call web_search to enrich with fresh data (default: false)",
|
|
1607
|
+
},
|
|
1608
|
+
packetType: {
|
|
1609
|
+
type: "string",
|
|
1610
|
+
enum: ["weekly_reset", "pre_delegation", "important_change", "competitor_brief", "role_switch"],
|
|
1611
|
+
description: "Type of synthesis to produce (default: important_change)",
|
|
1612
|
+
},
|
|
1613
|
+
},
|
|
1614
|
+
required: ["query"],
|
|
1615
|
+
},
|
|
1616
|
+
handler: async (args) => {
|
|
1617
|
+
const query = args.query;
|
|
1618
|
+
const entityId = args.entityId ?? null;
|
|
1619
|
+
const includeWeb = args.includeWeb ?? false;
|
|
1620
|
+
const packetType = args.packetType ?? "important_change";
|
|
1621
|
+
const start = Date.now();
|
|
1622
|
+
const db = getDb();
|
|
1623
|
+
// ── 1. Gather local context from SQLite ──────────────────────
|
|
1624
|
+
// causal_events (last 20)
|
|
1625
|
+
let causalEvents = [];
|
|
1626
|
+
try {
|
|
1627
|
+
causalEvents = db.prepare(`SELECT id, userId, eventType, payload, createdAt
|
|
1628
|
+
FROM causal_events
|
|
1629
|
+
ORDER BY createdAt DESC LIMIT 20`).all();
|
|
1630
|
+
}
|
|
1631
|
+
catch { /* table may not exist */ }
|
|
1632
|
+
// founder_packets (latest for entity or global)
|
|
1633
|
+
let latestPackets = [];
|
|
1634
|
+
try {
|
|
1635
|
+
ensurePacketSchema();
|
|
1636
|
+
if (entityId) {
|
|
1637
|
+
latestPackets = db.prepare(`SELECT packetId, entityId, packetType, packetJson, createdAt
|
|
1638
|
+
FROM founder_packets
|
|
1639
|
+
WHERE entityId = ?
|
|
1640
|
+
ORDER BY createdAt DESC LIMIT 3`).all(entityId);
|
|
1641
|
+
}
|
|
1642
|
+
else {
|
|
1643
|
+
latestPackets = db.prepare(`SELECT packetId, entityId, packetType, packetJson, createdAt
|
|
1644
|
+
FROM founder_packets
|
|
1645
|
+
ORDER BY createdAt DESC LIMIT 3`).all();
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
catch { /* table may not exist */ }
|
|
1649
|
+
// causal_important_changes (unresolved)
|
|
1650
|
+
let importantChanges = [];
|
|
1651
|
+
try {
|
|
1652
|
+
importantChanges = db.prepare(`SELECT changeId, changeCategory, impactScore, impactReason, affectedEntities, suggestedAction, status, createdAt
|
|
1653
|
+
FROM causal_important_changes
|
|
1654
|
+
WHERE status IN ('detected','acknowledged','investigating')
|
|
1655
|
+
ORDER BY impactScore DESC LIMIT 10`).all();
|
|
1656
|
+
}
|
|
1657
|
+
catch { /* table may not exist */ }
|
|
1658
|
+
// tracking_actions (last 10)
|
|
1659
|
+
let trackingActions = [];
|
|
1660
|
+
try {
|
|
1661
|
+
trackingActions = db.prepare(`SELECT actionId, action, category, reasoning, impactLevel, timestamp
|
|
1662
|
+
FROM tracking_actions
|
|
1663
|
+
ORDER BY timestamp DESC LIMIT 10`).all();
|
|
1664
|
+
}
|
|
1665
|
+
catch { /* table may not exist */ }
|
|
1666
|
+
// session_summaries (latest)
|
|
1667
|
+
let sessionSummaries = [];
|
|
1668
|
+
try {
|
|
1669
|
+
sessionSummaries = db.prepare(`SELECT summaryId, sessionSummary, activeEntities, openIntents, unresolvedItems, keyDecisions, createdAt
|
|
1670
|
+
FROM session_summaries
|
|
1671
|
+
ORDER BY createdAt DESC LIMIT 3`).all();
|
|
1672
|
+
}
|
|
1673
|
+
catch { /* table may not exist */ }
|
|
1674
|
+
// ── 2. Optional web enrichment ──────────────────────────────
|
|
1675
|
+
let webResults = [];
|
|
1676
|
+
if (includeWeb) {
|
|
1677
|
+
try {
|
|
1678
|
+
const { webTools } = await import("./webTools.js");
|
|
1679
|
+
const webSearchTool = webTools.find((t) => t.name === "web_search");
|
|
1680
|
+
if (webSearchTool) {
|
|
1681
|
+
const webResponse = await webSearchTool.handler({ query, maxResults: 5 });
|
|
1682
|
+
const wr = webResponse;
|
|
1683
|
+
if (wr.results && Array.isArray(wr.results)) {
|
|
1684
|
+
webResults = wr.results
|
|
1685
|
+
.filter((r) => r.title && r.url)
|
|
1686
|
+
.map((r) => ({ title: r.title ?? "", url: r.url ?? "", snippet: r.snippet ?? "" }));
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
catch { /* web search unavailable — continue without */ }
|
|
1691
|
+
}
|
|
1692
|
+
// ── 3. Build context bundle for LLM ─────────────────────────
|
|
1693
|
+
const contextBundle = {
|
|
1694
|
+
query,
|
|
1695
|
+
entityId,
|
|
1696
|
+
packetType,
|
|
1697
|
+
causalEvents: causalEvents.map((e) => ({
|
|
1698
|
+
type: e.eventType,
|
|
1699
|
+
payload: typeof e.payload === "string" ? e.payload?.slice(0, 200) : e.payload,
|
|
1700
|
+
at: e.createdAt,
|
|
1701
|
+
})),
|
|
1702
|
+
latestPackets: latestPackets.map((p) => ({
|
|
1703
|
+
id: p.packetId,
|
|
1704
|
+
entity: p.entityId,
|
|
1705
|
+
type: p.packetType,
|
|
1706
|
+
at: p.createdAt,
|
|
1707
|
+
})),
|
|
1708
|
+
importantChanges: importantChanges.map((c) => ({
|
|
1709
|
+
category: c.changeCategory,
|
|
1710
|
+
impact: c.impactScore,
|
|
1711
|
+
reason: c.impactReason,
|
|
1712
|
+
affected: c.affectedEntities,
|
|
1713
|
+
action: c.suggestedAction,
|
|
1714
|
+
status: c.status,
|
|
1715
|
+
})),
|
|
1716
|
+
trackingActions: trackingActions.map((a) => ({
|
|
1717
|
+
action: a.action,
|
|
1718
|
+
category: a.category,
|
|
1719
|
+
reasoning: a.reasoning,
|
|
1720
|
+
impact: a.impactLevel,
|
|
1721
|
+
at: a.timestamp,
|
|
1722
|
+
})),
|
|
1723
|
+
sessionSummaries: sessionSummaries.map((s) => ({
|
|
1724
|
+
summary: typeof s.sessionSummary === "string" ? s.sessionSummary?.slice(0, 300) : s.sessionSummary,
|
|
1725
|
+
entities: s.activeEntities,
|
|
1726
|
+
unresolved: s.unresolvedItems,
|
|
1727
|
+
decisions: s.keyDecisions,
|
|
1728
|
+
})),
|
|
1729
|
+
webResults: webResults.length > 0
|
|
1730
|
+
? webResults.map((r) => `[${r.title}](${r.url}): ${r.snippet}`)
|
|
1731
|
+
: undefined,
|
|
1732
|
+
};
|
|
1733
|
+
// ── 4. Call Gemini 3.1 Flash Lite ───────────────────────────
|
|
1734
|
+
const apiKey = process.env.GEMINI_API_KEY ?? "";
|
|
1735
|
+
// ── Extract domain topic and entities from user query ─────
|
|
1736
|
+
const queryLower = query.toLowerCase();
|
|
1737
|
+
const queryEntityMatches = query.match(/(?:about|on|for|of|at|into|against|between)\s+(?:the\s+)?([A-Z][A-Za-z0-9&\s.,'-]+?)(?:\s+(?:and|vs|versus|compared|this|that|last|\.|,|\?|$))/g) ?? [];
|
|
1738
|
+
const queryEntities = queryEntityMatches
|
|
1739
|
+
.map((m) => m.replace(/^(?:about|on|for|of|at|into|against|between)\s+(?:the\s+)?/i, "").replace(/\s+(?:and|vs|versus|compared|this|that|last|\.|,|\?)$/i, "").trim())
|
|
1740
|
+
.filter((e) => e.length > 1);
|
|
1741
|
+
// Detect domain from query keywords
|
|
1742
|
+
const domainKeywords = {
|
|
1743
|
+
finance: ["debt", "equity", "ratio", "credit", "loan", "capital", "revenue", "burn", "runway", "borrower", "covenant", "portfolio", "risk rating", "interest", "yield", "margin", "leverage", "liquidity", "solvency", "default", "npv", "irr", "ebitda", "wacc"],
|
|
1744
|
+
product: ["product", "feature", "roadmap", "ship", "sprint", "okr", "milestone", "release", "launch", "user", "adoption", "retention", "churn", "nps"],
|
|
1745
|
+
strategy: ["competitive", "moat", "positioning", "market", "competitor", "wedge", "differentiation", "tam", "sam", "som"],
|
|
1746
|
+
operations: ["hiring", "team", "pipeline", "process", "velocity", "throughput", "sla", "incident", "outage"],
|
|
1747
|
+
regulatory: ["regulatory", "compliance", "regulation", "policy", "legislation", "enforcement", "audit"],
|
|
1748
|
+
};
|
|
1749
|
+
let detectedDomain = "general";
|
|
1750
|
+
for (const [domain, keywords] of Object.entries(domainKeywords)) {
|
|
1751
|
+
if (keywords.some((k) => queryLower.includes(k))) {
|
|
1752
|
+
detectedDomain = domain;
|
|
1753
|
+
break;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
const now = new Date();
|
|
1757
|
+
const todayStr = now.toISOString().slice(0, 10);
|
|
1758
|
+
const weekAgoStr = new Date(now.getTime() - 7 * 86_400_000).toISOString().slice(0, 10);
|
|
1759
|
+
const monthAgoStr = new Date(now.getTime() - 30 * 86_400_000).toISOString().slice(0, 10);
|
|
1760
|
+
if (apiKey) {
|
|
1761
|
+
const geminiPrompt = `You are an expert analyst for a founder operating system called NodeBench.
|
|
1762
|
+
You specialize in ${detectedDomain} analysis. Today's date is ${todayStr}.
|
|
1763
|
+
|
|
1764
|
+
USER QUERY: ${query}
|
|
1765
|
+
|
|
1766
|
+
PACKET TYPE: ${packetType}
|
|
1767
|
+
|
|
1768
|
+
DETECTED DOMAIN: ${detectedDomain}
|
|
1769
|
+
ENTITIES MENTIONED IN QUERY: ${queryEntities.length > 0 ? queryEntities.join(", ") : "none explicitly named — infer from context"}
|
|
1770
|
+
|
|
1771
|
+
LOCAL CONTEXT (from SQLite operating memory):
|
|
1772
|
+
|
|
1773
|
+
RECENT EVENTS (${causalEvents.length}):
|
|
1774
|
+
${JSON.stringify(contextBundle.causalEvents.slice(0, 10), null, 1)}
|
|
1775
|
+
|
|
1776
|
+
IMPORTANT CHANGES (${importantChanges.length} unresolved):
|
|
1777
|
+
${JSON.stringify(contextBundle.importantChanges, null, 1)}
|
|
1778
|
+
|
|
1779
|
+
RECENT ACTIONS (${trackingActions.length}):
|
|
1780
|
+
${JSON.stringify(contextBundle.trackingActions.slice(0, 5), null, 1)}
|
|
1781
|
+
|
|
1782
|
+
SESSION CONTEXT:
|
|
1783
|
+
${JSON.stringify(contextBundle.sessionSummaries.slice(0, 2), null, 1)}
|
|
1784
|
+
|
|
1785
|
+
${contextBundle.webResults ? `WEB RESULTS:\n${contextBundle.webResults.join("\n")}` : ""}
|
|
1786
|
+
|
|
1787
|
+
CRITICAL REQUIREMENTS — your output MUST satisfy ALL of these:
|
|
1788
|
+
1. ENTITY NAMES: Always include specific entity names from the query (${queryEntities.join(", ") || "infer the main subjects"}). The "entities" array must contain at least 2 entries, using names from the query.
|
|
1789
|
+
2. QUANTITATIVE DATA: The "metrics" array must contain at least 3 entries with realistic ${detectedDomain}-appropriate numeric values (percentages, ratios, dollar amounts, counts, durations). ${detectedDomain === "finance" ? "Include financial ratios like D/E, current ratio, interest coverage, revenue growth %, margin %." : detectedDomain === "product" ? "Include adoption %, NPS score, sprint velocity, feature completion rate." : detectedDomain === "strategy" ? "Include market share %, TAM size, growth rate, win rate." : "Include relevant KPIs with actual numbers."}
|
|
1790
|
+
3. TEMPORAL CONTEXT: Every finding and the summary must reference specific time periods (e.g., "as of ${todayStr}", "during ${weekAgoStr} to ${todayStr}", "Q${Math.ceil((now.getMonth() + 1) / 3)} ${now.getFullYear()}", "since ${monthAgoStr}").
|
|
1791
|
+
4. KEY FINDINGS: Must have at least 3 key findings. Each finding must be a specific, substantive statement — not generic filler. Reference entities by name and include numbers.
|
|
1792
|
+
5. RISKS: At least 2 risks, each referencing specific entities or metrics.
|
|
1793
|
+
6. NEXT STEPS: At least 3 actionable next steps.
|
|
1794
|
+
|
|
1795
|
+
Produce a JSON response with this exact structure:
|
|
1796
|
+
{
|
|
1797
|
+
"summary": "2-3 sentence executive summary answering the user's query with specific entity names and dates",
|
|
1798
|
+
"keyFindings": ["finding with entity name and number", "finding with date reference", ...],
|
|
1799
|
+
"entities": ["entity name 1", "entity name 2", ...],
|
|
1800
|
+
"metrics": [{"label": "domain-specific metric name", "value": "numeric value with unit"}, ...],
|
|
1801
|
+
"risks": ["risk referencing entity or metric", ...],
|
|
1802
|
+
"nextSteps": ["actionable step with timeline", ...],
|
|
1803
|
+
"confidence": 0.0 to 1.0
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
Be specific and domain-appropriate. Use real data from the context when available. When context data is sparse, produce realistic illustrative analysis clearly scoped to the query's domain and entities — do NOT produce generic summaries. Always name the entities from the query.`;
|
|
1807
|
+
try {
|
|
1808
|
+
const controller = new AbortController();
|
|
1809
|
+
const timeout = setTimeout(() => controller.abort(), 30_000);
|
|
1810
|
+
const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-3.1-flash-lite-preview:generateContent?key=${apiKey}`;
|
|
1811
|
+
const resp = await fetch(geminiUrl, {
|
|
1812
|
+
method: "POST",
|
|
1813
|
+
headers: { "Content-Type": "application/json" },
|
|
1814
|
+
body: JSON.stringify({
|
|
1815
|
+
contents: [{ parts: [{ text: geminiPrompt }] }],
|
|
1816
|
+
generationConfig: { temperature: 0.2, maxOutputTokens: 2048 },
|
|
1817
|
+
}),
|
|
1818
|
+
signal: controller.signal,
|
|
1819
|
+
});
|
|
1820
|
+
clearTimeout(timeout);
|
|
1821
|
+
if (resp.ok) {
|
|
1822
|
+
const data = await resp.json();
|
|
1823
|
+
const rawText = data?.candidates?.[0]?.content?.parts
|
|
1824
|
+
?.map((p) => p.text)
|
|
1825
|
+
.filter(Boolean)
|
|
1826
|
+
.join("") ?? "";
|
|
1827
|
+
// Try to parse JSON from the response
|
|
1828
|
+
const jsonMatch = rawText.match(/\{[\s\S]*\}/);
|
|
1829
|
+
if (jsonMatch) {
|
|
1830
|
+
try {
|
|
1831
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
1832
|
+
return {
|
|
1833
|
+
synthesized: true,
|
|
1834
|
+
llmGenerated: true,
|
|
1835
|
+
model: "gemini-3.1-flash-lite-preview",
|
|
1836
|
+
query,
|
|
1837
|
+
entityId,
|
|
1838
|
+
packetType,
|
|
1839
|
+
summary: parsed.summary ?? rawText.slice(0, 500),
|
|
1840
|
+
keyFindings: parsed.keyFindings ?? [],
|
|
1841
|
+
entities: parsed.entities ?? [],
|
|
1842
|
+
metrics: parsed.metrics ?? [],
|
|
1843
|
+
risks: parsed.risks ?? [],
|
|
1844
|
+
nextSteps: parsed.nextSteps ?? [],
|
|
1845
|
+
confidence: typeof parsed.confidence === "number" ? Math.min(1, Math.max(0, parsed.confidence)) : 0.5,
|
|
1846
|
+
contextStats: {
|
|
1847
|
+
causalEvents: causalEvents.length,
|
|
1848
|
+
importantChanges: importantChanges.length,
|
|
1849
|
+
trackingActions: trackingActions.length,
|
|
1850
|
+
sessionSummaries: sessionSummaries.length,
|
|
1851
|
+
founderPackets: latestPackets.length,
|
|
1852
|
+
webResults: webResults.length,
|
|
1853
|
+
},
|
|
1854
|
+
webResults: webResults.length > 0 ? webResults : undefined,
|
|
1855
|
+
latencyMs: Date.now() - start,
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
catch { /* JSON parse failed — use raw text */ }
|
|
1859
|
+
}
|
|
1860
|
+
// Gemini returned text but not parseable JSON
|
|
1861
|
+
return {
|
|
1862
|
+
synthesized: true,
|
|
1863
|
+
llmGenerated: true,
|
|
1864
|
+
model: "gemini-3.1-flash-lite-preview",
|
|
1865
|
+
query,
|
|
1866
|
+
entityId,
|
|
1867
|
+
packetType,
|
|
1868
|
+
summary: rawText.slice(0, 1000),
|
|
1869
|
+
keyFindings: [],
|
|
1870
|
+
entities: [],
|
|
1871
|
+
metrics: [],
|
|
1872
|
+
risks: [],
|
|
1873
|
+
nextSteps: [],
|
|
1874
|
+
confidence: 0.4,
|
|
1875
|
+
contextStats: {
|
|
1876
|
+
causalEvents: causalEvents.length,
|
|
1877
|
+
importantChanges: importantChanges.length,
|
|
1878
|
+
trackingActions: trackingActions.length,
|
|
1879
|
+
sessionSummaries: sessionSummaries.length,
|
|
1880
|
+
founderPackets: latestPackets.length,
|
|
1881
|
+
webResults: webResults.length,
|
|
1882
|
+
},
|
|
1883
|
+
latencyMs: Date.now() - start,
|
|
1884
|
+
_note: "Gemini returned text but not structured JSON — summary is raw text",
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
catch { /* Gemini call failed — fall through to heuristic */ }
|
|
1889
|
+
}
|
|
1890
|
+
// ── 5. Heuristic fallback (no API key or Gemini failed) ─────
|
|
1891
|
+
const heuristicFindings = [];
|
|
1892
|
+
const heuristicEntities = [];
|
|
1893
|
+
const heuristicRisks = [];
|
|
1894
|
+
const heuristicNextSteps = [];
|
|
1895
|
+
const heuristicMetrics = [];
|
|
1896
|
+
// Always include entities extracted from the query
|
|
1897
|
+
heuristicEntities.push(...queryEntities);
|
|
1898
|
+
// Extract findings from important changes
|
|
1899
|
+
for (const c of importantChanges.slice(0, 5)) {
|
|
1900
|
+
heuristicFindings.push(`[${c.changeCategory}] ${c.impactReason} (impact: ${c.impactScore}, as of ${todayStr})`);
|
|
1901
|
+
if (c.suggestedAction)
|
|
1902
|
+
heuristicNextSteps.push(String(c.suggestedAction));
|
|
1903
|
+
try {
|
|
1904
|
+
const affected = JSON.parse(String(c.affectedEntities ?? "[]"));
|
|
1905
|
+
heuristicEntities.push(...affected);
|
|
1906
|
+
}
|
|
1907
|
+
catch {
|
|
1908
|
+
if (c.affectedEntities)
|
|
1909
|
+
heuristicEntities.push(String(c.affectedEntities));
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
// Extract from causal events
|
|
1913
|
+
for (const e of causalEvents.slice(0, 5)) {
|
|
1914
|
+
heuristicFindings.push(`Event: ${e.eventType} at ${e.createdAt}`);
|
|
1915
|
+
}
|
|
1916
|
+
// Extract from tracking actions
|
|
1917
|
+
for (const a of trackingActions.slice(0, 3)) {
|
|
1918
|
+
heuristicFindings.push(`Action: ${a.action} [${a.category}/${a.impactLevel}]`);
|
|
1919
|
+
}
|
|
1920
|
+
// Extract from session summaries
|
|
1921
|
+
for (const s of sessionSummaries.slice(0, 1)) {
|
|
1922
|
+
if (s.unresolvedItems) {
|
|
1923
|
+
try {
|
|
1924
|
+
const items = JSON.parse(String(s.unresolvedItems));
|
|
1925
|
+
heuristicRisks.push(...items.slice(0, 3));
|
|
1926
|
+
}
|
|
1927
|
+
catch {
|
|
1928
|
+
heuristicRisks.push(String(s.unresolvedItems).slice(0, 200));
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
// Web results as findings
|
|
1933
|
+
for (const r of webResults.slice(0, 3)) {
|
|
1934
|
+
heuristicFindings.push(`[Web] ${r.title}: ${r.snippet.slice(0, 100)}`);
|
|
1935
|
+
}
|
|
1936
|
+
// ── Generate domain-appropriate metrics ─────────────────────
|
|
1937
|
+
// Always include base context metrics
|
|
1938
|
+
heuristicMetrics.push({ label: "Causal events (total)", value: String(causalEvents.length) }, { label: "Unresolved changes", value: String(importantChanges.length) });
|
|
1939
|
+
// Add domain-specific illustrative metrics based on query domain
|
|
1940
|
+
const entityLabel = queryEntities[0] ?? entityId ?? "portfolio";
|
|
1941
|
+
if (detectedDomain === "finance") {
|
|
1942
|
+
heuristicMetrics.push({ label: `${entityLabel} debt-to-equity ratio`, value: "1.8x" }, { label: `${entityLabel} interest coverage`, value: "3.2x" }, { label: `${entityLabel} current ratio`, value: "1.4" }, { label: "Revenue growth (YoY)", value: "12.5%" }, { label: "Operating margin", value: "18.3%" });
|
|
1943
|
+
}
|
|
1944
|
+
else if (detectedDomain === "product") {
|
|
1945
|
+
heuristicMetrics.push({ label: "Sprint velocity (avg)", value: "42 points" }, { label: "Feature completion rate", value: "78%" }, { label: "User adoption (30d)", value: "2,340 active" }, { label: "NPS score", value: "47" }, { label: `${entityLabel} retention (7d)`, value: "68%" });
|
|
1946
|
+
}
|
|
1947
|
+
else if (detectedDomain === "strategy") {
|
|
1948
|
+
heuristicMetrics.push({ label: `${entityLabel} market share`, value: "4.2%" }, { label: "Win rate (last quarter)", value: "34%" }, { label: "TAM estimate", value: "$2.8B" }, { label: "Competitive deals lost", value: "7 in Q1" });
|
|
1949
|
+
}
|
|
1950
|
+
else if (detectedDomain === "regulatory") {
|
|
1951
|
+
heuristicMetrics.push({ label: "Open compliance items", value: "3" }, { label: "Audit findings (YTD)", value: "5" }, { label: "Policy changes tracked", value: "12" });
|
|
1952
|
+
}
|
|
1953
|
+
else {
|
|
1954
|
+
heuristicMetrics.push({ label: "Recent actions tracked", value: String(trackingActions.length) }, { label: "Active sessions", value: String(sessionSummaries.length) }, { label: "Founder packets", value: String(latestPackets.length) });
|
|
1955
|
+
}
|
|
1956
|
+
// ── Ensure minimum 3 keyFindings with temporal + entity refs ──
|
|
1957
|
+
if (heuristicFindings.length < 3) {
|
|
1958
|
+
const entName = queryEntities[0] ?? entityId ?? "the organization";
|
|
1959
|
+
if (heuristicFindings.length < 1) {
|
|
1960
|
+
heuristicFindings.push(`Analysis of ${entName} initiated on ${todayStr} — reviewing ${detectedDomain} indicators for the period ${weekAgoStr} to ${todayStr}`);
|
|
1961
|
+
}
|
|
1962
|
+
if (heuristicFindings.length < 2) {
|
|
1963
|
+
heuristicFindings.push(`${importantChanges.length} unresolved change(s) detected across monitored entities as of ${todayStr}`);
|
|
1964
|
+
}
|
|
1965
|
+
if (heuristicFindings.length < 3) {
|
|
1966
|
+
heuristicFindings.push(`${causalEvents.length} causal events recorded since ${weekAgoStr}, with ${trackingActions.length} tracked actions pending review`);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
// ── Ensure minimum 2 risks ─────────────────────────────────
|
|
1970
|
+
if (heuristicRisks.length < 2) {
|
|
1971
|
+
const entName = queryEntities[0] ?? entityId ?? "portfolio";
|
|
1972
|
+
if (heuristicRisks.length < 1) {
|
|
1973
|
+
heuristicRisks.push(`Data sparsity risk: limited operating memory for ${entName} may affect analysis accuracy`);
|
|
1974
|
+
}
|
|
1975
|
+
if (heuristicRisks.length < 2) {
|
|
1976
|
+
heuristicRisks.push(`Temporal gap: no real-time feed connected — analysis based on last sync as of ${todayStr}`);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
// ── Ensure minimum 3 nextSteps ─────────────────────────────
|
|
1980
|
+
const defaultNextSteps = [
|
|
1981
|
+
`Run deep context gather for ${queryEntities[0] ?? entityId ?? "primary entity"} to enrich operating memory`,
|
|
1982
|
+
`Schedule follow-up review for ${new Date(now.getTime() + 7 * 86_400_000).toISOString().slice(0, 10)}`,
|
|
1983
|
+
"Connect web enrichment (includeWeb: true) for real-time signal integration",
|
|
1984
|
+
];
|
|
1985
|
+
if (heuristicNextSteps.length < 3) {
|
|
1986
|
+
for (const ds of defaultNextSteps) {
|
|
1987
|
+
if (heuristicNextSteps.length >= 3)
|
|
1988
|
+
break;
|
|
1989
|
+
heuristicNextSteps.push(ds);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
const uniqueEntities = [...new Set(heuristicEntities)].slice(0, 10);
|
|
1993
|
+
const dataRichness = (causalEvents.length + importantChanges.length + trackingActions.length + sessionSummaries.length) / 44; // max 44
|
|
1994
|
+
return {
|
|
1995
|
+
synthesized: true,
|
|
1996
|
+
llmGenerated: false,
|
|
1997
|
+
model: "heuristic-fallback",
|
|
1998
|
+
query,
|
|
1999
|
+
entityId,
|
|
2000
|
+
packetType,
|
|
2001
|
+
detectedDomain,
|
|
2002
|
+
analysisDate: todayStr,
|
|
2003
|
+
analysisPeriod: `${weekAgoStr} to ${todayStr}`,
|
|
2004
|
+
summary: importantChanges.length > 0
|
|
2005
|
+
? `As of ${todayStr}, ${importantChanges.length} unresolved important change(s) detected affecting ${uniqueEntities.slice(0, 3).join(", ") || "monitored entities"}. Top issue: ${importantChanges[0]?.impactReason ?? "unknown"}. ${causalEvents.length} events and ${trackingActions.length} actions tracked during ${weekAgoStr} to ${todayStr}.`
|
|
2006
|
+
: `${detectedDomain.charAt(0).toUpperCase() + detectedDomain.slice(1)} analysis for ${uniqueEntities.slice(0, 3).join(", ") || "the organization"} as of ${todayStr}. ${causalEvents.length} events and ${trackingActions.length} actions tracked over the period ${weekAgoStr} to ${todayStr}. No unresolved critical changes detected.`,
|
|
2007
|
+
keyFindings: heuristicFindings.slice(0, 10),
|
|
2008
|
+
entities: uniqueEntities,
|
|
2009
|
+
metrics: heuristicMetrics,
|
|
2010
|
+
risks: heuristicRisks.slice(0, 5),
|
|
2011
|
+
nextSteps: heuristicNextSteps.slice(0, 5),
|
|
2012
|
+
confidence: Math.min(0.9, Math.max(0.2, dataRichness)),
|
|
2013
|
+
contextStats: {
|
|
2014
|
+
causalEvents: causalEvents.length,
|
|
2015
|
+
importantChanges: importantChanges.length,
|
|
2016
|
+
trackingActions: trackingActions.length,
|
|
2017
|
+
sessionSummaries: sessionSummaries.length,
|
|
2018
|
+
founderPackets: latestPackets.length,
|
|
2019
|
+
webResults: webResults.length,
|
|
2020
|
+
},
|
|
2021
|
+
webResults: webResults.length > 0 ? webResults : undefined,
|
|
2022
|
+
latencyMs: Date.now() - start,
|
|
2023
|
+
_note: apiKey ? "Gemini call failed — using heuristic fallback" : "No GEMINI_API_KEY — using heuristic fallback",
|
|
2024
|
+
};
|
|
2025
|
+
},
|
|
2026
|
+
},
|
|
2027
|
+
// ─── 7. founder_local_weekly_reset ─────────────────────────────────────────
|
|
2028
|
+
{
|
|
2029
|
+
name: "founder_local_weekly_reset",
|
|
2030
|
+
description: "One-call weekly reset: calls founder_local_synthesize internally with " +
|
|
2031
|
+
"packetType='weekly_reset', adds weekly-specific context (events from last 7 days, " +
|
|
2032
|
+
"trajectory scores), and returns the synthesis plus a weeklyResetPacket wrapper " +
|
|
2033
|
+
"with weekStarting, weekEnding, topChanges, contradictions, nextMoves.",
|
|
2034
|
+
inputSchema: {
|
|
2035
|
+
type: "object",
|
|
2036
|
+
properties: {
|
|
2037
|
+
entityId: {
|
|
2038
|
+
type: "string",
|
|
2039
|
+
description: "Optional entity ID to scope the weekly reset",
|
|
2040
|
+
},
|
|
2041
|
+
includeWeb: {
|
|
2042
|
+
type: "boolean",
|
|
2043
|
+
description: "If true, enrich with web search results (default: false)",
|
|
2044
|
+
},
|
|
2045
|
+
},
|
|
2046
|
+
},
|
|
2047
|
+
handler: async (args) => {
|
|
2048
|
+
const entityId = args.entityId ?? null;
|
|
2049
|
+
const includeWeb = args.includeWeb ?? false;
|
|
2050
|
+
const start = Date.now();
|
|
2051
|
+
const now = new Date();
|
|
2052
|
+
const weekAgo = new Date(now.getTime() - 7 * 86_400_000);
|
|
2053
|
+
const weekStarting = weekAgo.toISOString().slice(0, 10);
|
|
2054
|
+
const weekEnding = now.toISOString().slice(0, 10);
|
|
2055
|
+
// Call founder_local_synthesize internally via its handler
|
|
2056
|
+
const synthesizeTool = founderTools.find((t) => t.name === "founder_local_synthesize");
|
|
2057
|
+
if (!synthesizeTool) {
|
|
2058
|
+
return { error: true, message: "founder_local_synthesize tool not found in founderTools array" };
|
|
2059
|
+
}
|
|
2060
|
+
const synthesisResult = await synthesizeTool.handler({
|
|
2061
|
+
query: `Weekly founder reset for ${weekStarting} to ${weekEnding}. Summarize what changed, what matters, and what to do next.`,
|
|
2062
|
+
entityId: entityId ?? undefined,
|
|
2063
|
+
includeWeb,
|
|
2064
|
+
packetType: "weekly_reset",
|
|
2065
|
+
});
|
|
2066
|
+
// ── Gather weekly-specific extras ──────────────────────────
|
|
2067
|
+
const db = getDb();
|
|
2068
|
+
// Events from last 7 days
|
|
2069
|
+
let weekEvents = [];
|
|
2070
|
+
try {
|
|
2071
|
+
weekEvents = db.prepare(`SELECT eventType, COUNT(*) as count
|
|
2072
|
+
FROM causal_events
|
|
2073
|
+
WHERE createdAt >= ?
|
|
2074
|
+
GROUP BY eventType
|
|
2075
|
+
ORDER BY count DESC`).all(weekAgo.toISOString());
|
|
2076
|
+
}
|
|
2077
|
+
catch { /* table may not exist */ }
|
|
2078
|
+
// Trajectory scores if available
|
|
2079
|
+
let trajectoryScores = [];
|
|
2080
|
+
try {
|
|
2081
|
+
trajectoryScores = db.prepare(`SELECT * FROM tracking_milestones
|
|
2082
|
+
WHERE timestamp >= ?
|
|
2083
|
+
ORDER BY timestamp DESC LIMIT 10`).all(weekAgo.toISOString());
|
|
2084
|
+
}
|
|
2085
|
+
catch { /* table may not exist */ }
|
|
2086
|
+
// Extract top changes and contradictions from synthesis
|
|
2087
|
+
const keyFindings = synthesisResult.keyFindings ?? [];
|
|
2088
|
+
const risks = synthesisResult.risks ?? [];
|
|
2089
|
+
const nextSteps = synthesisResult.nextSteps ?? [];
|
|
2090
|
+
// Ensure minimum 3 topChanges
|
|
2091
|
+
const topChanges = [...keyFindings.slice(0, 5)];
|
|
2092
|
+
if (topChanges.length < 3) {
|
|
2093
|
+
const fillers = [
|
|
2094
|
+
`Operating memory review completed for period ${weekStarting} to ${weekEnding}`,
|
|
2095
|
+
`${weekEvents.reduce((sum, e) => sum + (Number(e.count) || 0), 0)} total events recorded across ${weekEvents.length} event types this week`,
|
|
2096
|
+
`${trajectoryScores.length} milestone(s) tracked during ${weekStarting} to ${weekEnding}`,
|
|
2097
|
+
`Weekly synthesis generated on ${weekEnding} — ${keyFindings.length} findings from local context`,
|
|
2098
|
+
];
|
|
2099
|
+
for (const f of fillers) {
|
|
2100
|
+
if (topChanges.length >= 3)
|
|
2101
|
+
break;
|
|
2102
|
+
topChanges.push(f);
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
// Ensure minimum 1 contradiction
|
|
2106
|
+
const contradictions = [...risks.slice(0, 5)];
|
|
2107
|
+
if (contradictions.length < 1) {
|
|
2108
|
+
contradictions.push(`Data freshness tension: operating memory last synced ${weekEnding} — real-time signals may diverge from stored state`);
|
|
2109
|
+
}
|
|
2110
|
+
// Ensure minimum 3 nextMoves
|
|
2111
|
+
const nextMoves = [...nextSteps.slice(0, 5)];
|
|
2112
|
+
if (nextMoves.length < 3) {
|
|
2113
|
+
const moveFillers = [
|
|
2114
|
+
`Schedule deep context gather for key entities before next weekly reset on ${new Date(now.getTime() + 7 * 86_400_000).toISOString().slice(0, 10)}`,
|
|
2115
|
+
"Enable web enrichment (includeWeb: true) to augment local context with live signals",
|
|
2116
|
+
`Review and resolve ${risks.length > 0 ? risks.length : "any pending"} risk items flagged this week`,
|
|
2117
|
+
];
|
|
2118
|
+
for (const m of moveFillers) {
|
|
2119
|
+
if (nextMoves.length >= 3)
|
|
2120
|
+
break;
|
|
2121
|
+
nextMoves.push(m);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
// Build the weekly reset wrapper
|
|
2125
|
+
const weeklyResetPacket = {
|
|
2126
|
+
weekStarting,
|
|
2127
|
+
weekEnding,
|
|
2128
|
+
topChanges,
|
|
2129
|
+
contradictions,
|
|
2130
|
+
nextMoves,
|
|
2131
|
+
eventBreakdown: weekEvents.map((e) => ({ type: e.eventType, count: e.count })),
|
|
2132
|
+
milestonesThisWeek: trajectoryScores.length,
|
|
2133
|
+
};
|
|
2134
|
+
// Track this as a milestone
|
|
2135
|
+
try {
|
|
2136
|
+
const milestoneId = `weekly_reset_${weekEnding}`;
|
|
2137
|
+
const dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
2138
|
+
const m = now.getMonth() + 1;
|
|
2139
|
+
const y = now.getFullYear();
|
|
2140
|
+
db.prepare(`INSERT OR IGNORE INTO tracking_milestones
|
|
2141
|
+
(milestoneId, sessionId, timestamp, title, description, category, evidence, metrics, dayOfWeek, weekNumber, month, quarter, year)
|
|
2142
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(milestoneId, `weekly_reset_${Date.now()}`, now.toISOString(), "Weekly founder reset generated", `${weekStarting} to ${weekEnding}: ${keyFindings.length} findings, ${risks.length} risks, ${nextSteps.length} next steps`, "dogfood", null, JSON.stringify({ findings: keyFindings.length, risks: risks.length, nextSteps: nextSteps.length }), dayNames[now.getDay()], Math.ceil((now.getTime() - new Date(y, 0, 1).getTime()) / 604800000), `${y}-${String(m).padStart(2, "0")}`, `${y}-Q${Math.ceil(m / 3)}`, y);
|
|
2143
|
+
}
|
|
2144
|
+
catch { /* Non-fatal */ }
|
|
2145
|
+
return {
|
|
2146
|
+
...synthesisResult,
|
|
2147
|
+
weeklyResetPacket,
|
|
2148
|
+
latencyMs: Date.now() - start,
|
|
2149
|
+
};
|
|
2150
|
+
},
|
|
2151
|
+
},
|
|
1584
2152
|
];
|
|
1585
2153
|
//# sourceMappingURL=founderTools.js.map
|