nodebench-mcp 2.43.0 → 2.45.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.
@@ -9,6 +9,117 @@
9
9
  * about to produce a founder artifact, ensuring the agent has gathered
10
10
  * every piece of context it needs.
11
11
  */
12
+ import { getDb } from "../db.js";
13
+ /* ------------------------------------------------------------------ */
14
+ /* founder_packets schema bootstrap (idempotent) */
15
+ /* ------------------------------------------------------------------ */
16
+ let _packetSchemaReady = false;
17
+ function ensurePacketSchema() {
18
+ if (_packetSchemaReady)
19
+ return;
20
+ const db = getDb();
21
+ db.exec(`
22
+ CREATE TABLE IF NOT EXISTS founder_packets (
23
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
24
+ packetId TEXT UNIQUE NOT NULL,
25
+ entityId TEXT NOT NULL,
26
+ packetType TEXT NOT NULL,
27
+ packetJson TEXT NOT NULL,
28
+ createdAt TEXT NOT NULL DEFAULT (datetime('now'))
29
+ );
30
+
31
+ CREATE INDEX IF NOT EXISTS idx_founder_packets_entity ON founder_packets(entityId);
32
+ CREATE INDEX IF NOT EXISTS idx_founder_packets_type ON founder_packets(packetType);
33
+ CREATE INDEX IF NOT EXISTS idx_founder_packets_created ON founder_packets(createdAt);
34
+ `);
35
+ _packetSchemaReady = true;
36
+ }
37
+ /* ------------------------------------------------------------------ */
38
+ /* JSON flatten / diff helpers */
39
+ /* ------------------------------------------------------------------ */
40
+ /** Flatten a nested object into dot-notation paths. Arrays become path.0, path.1, etc. */
41
+ function flattenObject(obj, prefix = "", out = {}) {
42
+ if (obj === null || obj === undefined) {
43
+ if (prefix)
44
+ out[prefix] = obj;
45
+ return out;
46
+ }
47
+ if (Array.isArray(obj)) {
48
+ if (obj.length === 0 && prefix) {
49
+ out[prefix] = obj;
50
+ }
51
+ for (let i = 0; i < obj.length; i++) {
52
+ flattenObject(obj[i], prefix ? `${prefix}.${i}` : String(i), out);
53
+ }
54
+ return out;
55
+ }
56
+ if (typeof obj === "object") {
57
+ const record = obj;
58
+ const keys = Object.keys(record);
59
+ if (keys.length === 0 && prefix) {
60
+ out[prefix] = obj;
61
+ }
62
+ for (const key of keys) {
63
+ flattenObject(record[key], prefix ? `${prefix}.${key}` : key, out);
64
+ }
65
+ return out;
66
+ }
67
+ // Primitive
68
+ if (prefix)
69
+ out[prefix] = obj;
70
+ return out;
71
+ }
72
+ function computePacketDiff(current, prior) {
73
+ const flatCurrent = flattenObject(current);
74
+ const flatPrior = flattenObject(prior);
75
+ const currentKeys = new Set(Object.keys(flatCurrent));
76
+ const priorKeys = new Set(Object.keys(flatPrior));
77
+ const newSinceLastTime = [];
78
+ const resolvedSinceLastTime = [];
79
+ const changedFields = [];
80
+ const stableFields = [];
81
+ // Fields in current but not in prior
82
+ for (const key of currentKeys) {
83
+ if (!priorKeys.has(key)) {
84
+ newSinceLastTime.push(key);
85
+ }
86
+ }
87
+ // Fields in prior but not in current
88
+ for (const key of priorKeys) {
89
+ if (!currentKeys.has(key)) {
90
+ resolvedSinceLastTime.push(key);
91
+ }
92
+ }
93
+ // Fields in both — compare values
94
+ for (const key of currentKeys) {
95
+ if (!priorKeys.has(key))
96
+ continue;
97
+ const cv = JSON.stringify(flatCurrent[key]);
98
+ const pv = JSON.stringify(flatPrior[key]);
99
+ if (cv === pv) {
100
+ stableFields.push(key);
101
+ }
102
+ else {
103
+ changedFields.push({
104
+ path: key,
105
+ previous: flatPrior[key],
106
+ current: flatCurrent[key],
107
+ });
108
+ }
109
+ }
110
+ // Drift score: fraction of all unique keys that differ
111
+ const allKeys = new Set([...currentKeys, ...priorKeys]);
112
+ const totalKeys = allKeys.size;
113
+ const diffCount = newSinceLastTime.length + resolvedSinceLastTime.length + changedFields.length;
114
+ const driftScore = totalKeys > 0 ? Math.round((diffCount / totalKeys) * 1000) / 1000 : 0;
115
+ return {
116
+ newSinceLastTime,
117
+ resolvedSinceLastTime,
118
+ changedFields,
119
+ stableFields,
120
+ driftScore: Math.min(driftScore, 1.0),
121
+ };
122
+ }
12
123
  const GATHER_PROTOCOL = [
13
124
  {
14
125
  id: "company_identity",
@@ -218,6 +329,70 @@ const GATHER_PROTOCOL = [
218
329
  },
219
330
  ];
220
331
  /* ------------------------------------------------------------------ */
332
+ /* Importance scoring for event ranking */
333
+ /* ------------------------------------------------------------------ */
334
+ const EVENT_TYPE_WEIGHTS = {
335
+ "product.phase.completed": 0.9,
336
+ "contradiction.detected": 0.85,
337
+ "important_change.flagged": 0.8,
338
+ "packet.generated": 0.7,
339
+ "state.changed": 0.6,
340
+ "engine.trace.completed": 0.5,
341
+ "action.completed": 0.4,
342
+ };
343
+ const DEFAULT_EVENT_WEIGHT = 0.3;
344
+ const RECENCY_DECAY_PER_DAY = 0.1;
345
+ const THESIS_RELEVANCE_BOOST = 1.3;
346
+ const IMPORTANCE_THRESHOLD = 0.3;
347
+ function scoreEvents(rawEvents, missionKeywords) {
348
+ const now = Date.now();
349
+ const msPerDay = 24 * 60 * 60 * 1000;
350
+ const scored = rawEvents.map((e) => {
351
+ const eventType = e.eventType ?? "";
352
+ const baseWeight = EVENT_TYPE_WEIGHTS[eventType] ?? DEFAULT_EVENT_WEIGHT;
353
+ // Recency decay: today=1.0, yesterday=0.9, ...
354
+ const ts = e.timestamp ?? 0;
355
+ const daysAgo = Math.max(0, Math.floor((now - ts) / msPerDay));
356
+ const recencyMultiplier = Math.max(0.1, 1.0 - daysAgo * RECENCY_DECAY_PER_DAY);
357
+ // Thesis relevance boost
358
+ const summary = (e.summary ?? "").toLowerCase();
359
+ const matchesMission = missionKeywords.length > 0 &&
360
+ missionKeywords.some((kw) => summary.includes(kw));
361
+ const relevanceMultiplier = matchesMission ? THESIS_RELEVANCE_BOOST : 1.0;
362
+ const score = Math.round(baseWeight * recencyMultiplier * relevanceMultiplier * 1000) / 1000;
363
+ return {
364
+ type: e.eventType,
365
+ entity: e.entityId,
366
+ summary: e.summary,
367
+ actor: e.actor,
368
+ timestamp: e.timestamp,
369
+ importanceScore: score,
370
+ };
371
+ });
372
+ // Sort descending by score
373
+ scored.sort((a, b) => b.importanceScore - a.importanceScore);
374
+ const ranked = scored.filter((e) => e.importanceScore >= IMPORTANCE_THRESHOLD);
375
+ const suppressedCount = scored.length - ranked.length;
376
+ const topSignal = ranked[0] ?? null;
377
+ return { ranked, suppressedCount, topSignal };
378
+ }
379
+ /** Extract lowercase keywords from a mission string for fuzzy matching. */
380
+ function extractMissionKeywords(diffs) {
381
+ // Pull keywords from the most recent state diff's reason/fields
382
+ if (diffs.length === 0)
383
+ return [];
384
+ const latest = diffs[0];
385
+ const reason = (latest.reason ?? "").toLowerCase();
386
+ const fields = (latest.changedFields ?? "").toLowerCase();
387
+ const combined = `${reason} ${fields}`;
388
+ // Split on non-alpha, filter short/stop words
389
+ const stopWords = new Set(["the", "and", "for", "was", "that", "with", "from", "are", "this", "has", "its", "not", "but"]);
390
+ return combined
391
+ .split(/[^a-z]+/)
392
+ .filter((w) => w.length > 3 && !stopWords.has(w))
393
+ .slice(0, 20);
394
+ }
395
+ /* ------------------------------------------------------------------ */
221
396
  /* Tools */
222
397
  /* ------------------------------------------------------------------ */
223
398
  export const founderTools = [
@@ -294,12 +469,85 @@ export const founderTools = [
294
469
  }));
295
470
  const requiredSteps = steps.filter((s) => s.required);
296
471
  const optionalSteps = steps.filter((s) => !s.required);
472
+ // ── Auto-hydrate from causal memory if available ──────────────────
473
+ let sessionMemory = null;
474
+ try {
475
+ const db = getDb();
476
+ const now = Date.now();
477
+ const weekAgo = now - 7 * 24 * 60 * 60 * 1000;
478
+ // Recent events (last 7 days)
479
+ const recentEvents = db
480
+ .prepare("SELECT * FROM causal_events WHERE timestamp > ? ORDER BY timestamp DESC LIMIT 20")
481
+ .all(weekAgo);
482
+ // Important changes (unresolved)
483
+ const importantChanges = db
484
+ .prepare("SELECT * FROM causal_important_changes WHERE status IN ('detected','acknowledged','investigating') ORDER BY impactScore DESC LIMIT 10")
485
+ .all();
486
+ // State diffs (last 7 days)
487
+ const stateDiffs = db
488
+ .prepare("SELECT * FROM causal_state_diffs WHERE createdAt > ? ORDER BY createdAt DESC LIMIT 10")
489
+ .all(weekAgo);
490
+ // Weekly action summary
491
+ const weeklyActions = db
492
+ .prepare("SELECT category, COUNT(*) as count, AVG(impactLevel) as avgImpact FROM tracking_actions WHERE timestamp > ? GROUP BY category ORDER BY count DESC")
493
+ .all(weekAgo);
494
+ // Milestones this week
495
+ const milestones = db
496
+ .prepare("SELECT * FROM tracking_milestones WHERE timestamp > ? ORDER BY timestamp DESC LIMIT 5")
497
+ .all(weekAgo);
498
+ // Trajectory scores
499
+ const trajectory = db
500
+ .prepare("SELECT * FROM causal_trajectory_scores ORDER BY createdAt DESC LIMIT 1")
501
+ .all();
502
+ if (recentEvents.length > 0 || importantChanges.length > 0 || stateDiffs.length > 0) {
503
+ // Extract mission keywords from most recent state diff for thesis relevance
504
+ const missionKeywords = extractMissionKeywords(stateDiffs);
505
+ // Score and rank events by importance
506
+ const { ranked, suppressedCount, topSignal } = scoreEvents(recentEvents, missionKeywords);
507
+ sessionMemory = {
508
+ source: "causal_memory_auto_hydrate",
509
+ period: "last_7_days",
510
+ recentEvents: recentEvents.length,
511
+ importantChanges: importantChanges.length,
512
+ stateDiffs: stateDiffs.length,
513
+ weeklyActions,
514
+ milestones,
515
+ trajectory: trajectory[0] ?? null,
516
+ topSignal,
517
+ suppressedCount,
518
+ events: ranked,
519
+ changes: importantChanges.map((c) => ({
520
+ category: c.changeCategory,
521
+ impact: c.impactScore,
522
+ reason: c.impactReason,
523
+ status: c.status,
524
+ })),
525
+ diffs: stateDiffs.map((d) => ({
526
+ entity: d.entityId,
527
+ fields: d.changedFields,
528
+ reason: d.reason,
529
+ })),
530
+ };
531
+ }
532
+ }
533
+ catch {
534
+ // causal memory tables may not exist yet — graceful fallback
535
+ sessionMemory = null;
536
+ }
297
537
  const protocol = {
298
- protocolVersion: "1.0",
538
+ protocolVersion: "1.1",
299
539
  packetType,
300
540
  totalSteps: steps.length,
301
541
  requiredSteps: requiredSteps.length,
542
+ // Pre-hydrated session memory (if available)
543
+ ...(sessionMemory ? { sessionMemory } : {}),
302
544
  instructions: [
545
+ ...(sessionMemory
546
+ ? [
547
+ "Session memory has been auto-hydrated from causal memory. Use the sessionMemory block as your starting context — do NOT ask the user to restate what happened.",
548
+ "Cross-reference sessionMemory events, changes, and diffs against the gather steps below.",
549
+ ]
550
+ : []),
303
551
  "You MUST complete ALL required gather steps before generating the artifact packet.",
304
552
  "For each step, search the listed sources using the provided search patterns.",
305
553
  "Do NOT skip a step because it seems redundant — redundancy catches blind spots.",
@@ -591,5 +839,642 @@ export const founderTools = [
591
839
  };
592
840
  },
593
841
  },
842
+ // ─── 4. founder_packet_history_diff ──────────────────────────────
843
+ {
844
+ name: "founder_packet_history_diff",
845
+ description: "Compares the most recent Founder Artifact Packet for an entity against " +
846
+ "prior packets stored in the founder_packets SQLite table. Returns a " +
847
+ "structured diff: newSinceLastTime, resolvedSinceLastTime, changedFields, " +
848
+ "stableFields, and a driftScore (0.0–1.0). If only one packet exists, " +
849
+ "returns it as a baseline. If none exist, suggests running " +
850
+ "founder_deep_context_gather first.",
851
+ inputSchema: {
852
+ type: "object",
853
+ properties: {
854
+ entityId: {
855
+ type: "string",
856
+ description: "The entity ID to look up packets for.",
857
+ },
858
+ packetType: {
859
+ type: "string",
860
+ description: "Optional packet type filter (e.g. weekly_reset, pre_delegation, important_change).",
861
+ },
862
+ limit: {
863
+ type: "number",
864
+ description: "Max number of recent packets to retrieve for comparison (default 2, max 10).",
865
+ },
866
+ },
867
+ required: ["entityId"],
868
+ },
869
+ annotations: { readOnlyHint: true },
870
+ handler: async (args) => {
871
+ ensurePacketSchema();
872
+ const db = getDb();
873
+ const entityId = args.entityId;
874
+ const packetType = args.packetType ?? null;
875
+ const limit = Math.min(Math.max(args.limit ?? 2, 1), 10);
876
+ // Query recent packets for this entity
877
+ let rows;
878
+ if (packetType) {
879
+ rows = db
880
+ .prepare(`SELECT packetId, entityId, packetType, packetJson, createdAt
881
+ FROM founder_packets
882
+ WHERE entityId = ? AND packetType = ?
883
+ ORDER BY createdAt DESC
884
+ LIMIT ?`)
885
+ .all(entityId, packetType, limit);
886
+ }
887
+ else {
888
+ rows = db
889
+ .prepare(`SELECT packetId, entityId, packetType, packetJson, createdAt
890
+ FROM founder_packets
891
+ WHERE entityId = ?
892
+ ORDER BY createdAt DESC
893
+ LIMIT ?`)
894
+ .all(entityId, limit);
895
+ }
896
+ // No packets found
897
+ if (rows.length === 0) {
898
+ return {
899
+ noPackets: true,
900
+ entityId,
901
+ suggestion: "Run founder_deep_context_gather first to generate and store a packet.",
902
+ };
903
+ }
904
+ // Parse packet JSON safely
905
+ const packets = rows.map((row) => {
906
+ let parsed = {};
907
+ try {
908
+ parsed = JSON.parse(row.packetJson);
909
+ }
910
+ catch {
911
+ parsed = { _parseError: true, raw: row.packetJson };
912
+ }
913
+ return {
914
+ packetId: row.packetId,
915
+ entityId: row.entityId,
916
+ packetType: row.packetType,
917
+ createdAt: row.createdAt,
918
+ data: parsed,
919
+ };
920
+ });
921
+ // Only one packet — return as baseline
922
+ if (packets.length === 1) {
923
+ return {
924
+ isFirstPacket: true,
925
+ entityId,
926
+ packet: {
927
+ packetId: packets[0].packetId,
928
+ packetType: packets[0].packetType,
929
+ createdAt: packets[0].createdAt,
930
+ },
931
+ note: "First packet for this entity. Future calls will produce diffs.",
932
+ };
933
+ }
934
+ // 2+ packets — diff the most recent against the one before it
935
+ const latest = packets[0];
936
+ const prior = packets[1];
937
+ const diff = computePacketDiff(latest.data, prior.data);
938
+ return {
939
+ entityId,
940
+ latest: {
941
+ packetId: latest.packetId,
942
+ packetType: latest.packetType,
943
+ createdAt: latest.createdAt,
944
+ },
945
+ prior: {
946
+ packetId: prior.packetId,
947
+ packetType: prior.packetType,
948
+ createdAt: prior.createdAt,
949
+ },
950
+ diff: {
951
+ newSinceLastTime: diff.newSinceLastTime,
952
+ resolvedSinceLastTime: diff.resolvedSinceLastTime,
953
+ changedFields: diff.changedFields,
954
+ stableFields: diff.stableFields,
955
+ driftScore: diff.driftScore,
956
+ },
957
+ summary: {
958
+ totalFieldsCompared: diff.newSinceLastTime.length +
959
+ diff.resolvedSinceLastTime.length +
960
+ diff.changedFields.length +
961
+ diff.stableFields.length,
962
+ newCount: diff.newSinceLastTime.length,
963
+ resolvedCount: diff.resolvedSinceLastTime.length,
964
+ changedCount: diff.changedFields.length,
965
+ stableCount: diff.stableFields.length,
966
+ driftScore: diff.driftScore,
967
+ driftLevel: diff.driftScore < 0.1
968
+ ? "minimal"
969
+ : diff.driftScore < 0.3
970
+ ? "low"
971
+ : diff.driftScore < 0.6
972
+ ? "moderate"
973
+ : diff.driftScore < 0.85
974
+ ? "high"
975
+ : "extreme",
976
+ },
977
+ packetsAvailable: packets.length,
978
+ };
979
+ },
980
+ },
981
+ // ─── 5. export_artifact_packet ──────────────────────────────────
982
+ {
983
+ name: "export_artifact_packet",
984
+ description: "Formats a Founder Artifact Packet or memo for export to a specific audience and format. " +
985
+ "Applies audience-specific framing (founder, investor, banker, developer, teammate) and " +
986
+ "renders into the requested format (markdown, html, json, plaintext). Always includes " +
987
+ "provenance metadata (timestamp, version, exportId) for traceability.",
988
+ inputSchema: {
989
+ type: "object",
990
+ properties: {
991
+ content: {
992
+ type: "object",
993
+ description: "The raw packet/memo content to format for export.",
994
+ },
995
+ format: {
996
+ type: "string",
997
+ enum: ["markdown", "html", "json", "plaintext"],
998
+ description: "Output format for the exported artifact.",
999
+ },
1000
+ audience: {
1001
+ type: "string",
1002
+ enum: ["founder", "investor", "banker", "developer", "teammate"],
1003
+ description: "Target audience — controls tone, ordering, and which sections are emphasized.",
1004
+ },
1005
+ title: {
1006
+ type: "string",
1007
+ description: "Override title for the exported artifact. Defaults to packet title or 'Artifact Packet'.",
1008
+ },
1009
+ includeMetadata: {
1010
+ type: "boolean",
1011
+ description: "Include generation timestamp, tool version, and provenance block. Defaults to true.",
1012
+ },
1013
+ },
1014
+ required: ["content", "format", "audience"],
1015
+ },
1016
+ handler: async (args) => {
1017
+ const content = args.content;
1018
+ const format = args.format;
1019
+ const audience = args.audience;
1020
+ const titleOverride = args.title ?? null;
1021
+ const includeMetadata = args.includeMetadata ?? true;
1022
+ const validFormats = ["markdown", "html", "json", "plaintext"];
1023
+ const validAudiences = ["founder", "investor", "banker", "developer", "teammate"];
1024
+ if (!validFormats.includes(format)) {
1025
+ return {
1026
+ error: true,
1027
+ message: `Invalid format: ${format}. Must be one of: ${validFormats.join(", ")}.`,
1028
+ };
1029
+ }
1030
+ if (!validAudiences.includes(audience)) {
1031
+ return {
1032
+ error: true,
1033
+ message: `Invalid audience: ${audience}. Must be one of: ${validAudiences.join(", ")}.`,
1034
+ };
1035
+ }
1036
+ const NODEBENCH_VERSION = "1.0.0";
1037
+ const exportId = `exp_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
1038
+ const generatedAt = new Date().toISOString();
1039
+ // ── Extract common fields from content ──────────────────────────
1040
+ const entity = content.canonicalEntity ?? {};
1041
+ const companyName = entity.name ?? content.companyName ?? "Unknown Company";
1042
+ const mission = entity.mission ?? content.mission ?? "";
1043
+ const wedge = entity.wedge ?? content.wedge ?? "";
1044
+ const identityConfidence = entity.identityConfidence ?? null;
1045
+ const contradictions = content.contradictions ?? [];
1046
+ const nextActions = content.nextActions ?? [];
1047
+ const operatingMemo = content.operatingMemo ?? "";
1048
+ const whatChanged = content.whatChanged ?? [];
1049
+ const nearbyEntities = content.nearbyEntities ?? [];
1050
+ const keyEvidence = content.keyEvidence ?? [];
1051
+ const agentInstructions = content.agentInstructions ?? null;
1052
+ const title = titleOverride ?? content.title ?? "Artifact Packet";
1053
+ const audienceConfigs = {
1054
+ founder: {
1055
+ tone: "informal",
1056
+ sectionOrder: [
1057
+ "summary",
1058
+ "whatChanged",
1059
+ "contradictions",
1060
+ "nextMoves",
1061
+ "initiatives",
1062
+ "agents",
1063
+ "operatingMemo",
1064
+ "evidence",
1065
+ ],
1066
+ emphasisSections: ["contradictions", "nextMoves"],
1067
+ excludeSections: [],
1068
+ headerPrefix: "",
1069
+ },
1070
+ investor: {
1071
+ tone: "formal",
1072
+ sectionOrder: [
1073
+ "metrics",
1074
+ "summary",
1075
+ "marketContext",
1076
+ "risks",
1077
+ "initiatives",
1078
+ "evidence",
1079
+ ],
1080
+ emphasisSections: ["metrics", "risks"],
1081
+ excludeSections: ["agents", "agentInstructions"],
1082
+ headerPrefix: "Investment Memo: ",
1083
+ },
1084
+ banker: {
1085
+ tone: "formal",
1086
+ sectionOrder: [
1087
+ "companySnapshot",
1088
+ "financialSignals",
1089
+ "riskFactors",
1090
+ "comparables",
1091
+ "summary",
1092
+ "evidence",
1093
+ ],
1094
+ emphasisSections: ["companySnapshot", "financialSignals", "riskFactors"],
1095
+ excludeSections: ["agents", "agentInstructions", "operatingMemo"],
1096
+ headerPrefix: "Memo: ",
1097
+ },
1098
+ developer: {
1099
+ tone: "technical",
1100
+ sectionOrder: [
1101
+ "architectureChanges",
1102
+ "technicalDecisions",
1103
+ "apiChanges",
1104
+ "whatChanged",
1105
+ "nextMoves",
1106
+ "agents",
1107
+ ],
1108
+ emphasisSections: ["architectureChanges", "apiChanges"],
1109
+ excludeSections: ["nearbyEntities", "operatingMemo"],
1110
+ headerPrefix: "",
1111
+ },
1112
+ teammate: {
1113
+ tone: "conversational",
1114
+ sectionOrder: ["delegationBrief", "actionItems", "whatChanged", "context"],
1115
+ emphasisSections: ["delegationBrief", "actionItems"],
1116
+ excludeSections: ["evidence", "nearbyEntities"],
1117
+ headerPrefix: "",
1118
+ },
1119
+ };
1120
+ const config = audienceConfigs[audience];
1121
+ const sections = [];
1122
+ // Summary / company snapshot
1123
+ if (audience === "banker") {
1124
+ sections.push({
1125
+ key: "companySnapshot",
1126
+ heading: "Company Snapshot",
1127
+ body: [
1128
+ companyName ? `Company: ${companyName}` : "",
1129
+ mission ? `Mission: ${mission}` : "",
1130
+ wedge ? `Wedge: ${wedge}` : "",
1131
+ identityConfidence !== null
1132
+ ? `Identity Confidence: ${Math.round(identityConfidence * 100)}%`
1133
+ : "",
1134
+ ]
1135
+ .filter(Boolean)
1136
+ .join("\n"),
1137
+ });
1138
+ }
1139
+ else if (audience === "investor") {
1140
+ sections.push({
1141
+ key: "metrics",
1142
+ heading: "Key Metrics & Traction",
1143
+ body: [
1144
+ companyName ? `Company: ${companyName}` : "",
1145
+ identityConfidence !== null
1146
+ ? `Identity Confidence: ${Math.round(identityConfidence * 100)}%`
1147
+ : "",
1148
+ `Active Initiatives: ${(content.initiatives ?? []).length}`,
1149
+ `Open Contradictions: ${contradictions.length}`,
1150
+ `Pending Actions: ${nextActions.length}`,
1151
+ ]
1152
+ .filter(Boolean)
1153
+ .join("\n"),
1154
+ });
1155
+ }
1156
+ sections.push({
1157
+ key: "summary",
1158
+ heading: audience === "teammate" ? "Context" : "Summary",
1159
+ body: operatingMemo || `${companyName} — ${mission}`,
1160
+ });
1161
+ // What changed
1162
+ if (!config.excludeSections.includes("whatChanged") &&
1163
+ whatChanged.length > 0) {
1164
+ sections.push({
1165
+ key: "whatChanged",
1166
+ heading: audience === "developer" ? "Recent Changes" : "What Changed",
1167
+ items: whatChanged.map((c) => ({
1168
+ text: c.description ??
1169
+ c.summary ??
1170
+ JSON.stringify(c),
1171
+ meta: c.type ?? undefined,
1172
+ })),
1173
+ body: "",
1174
+ });
1175
+ }
1176
+ // Contradictions / risks
1177
+ if (!config.excludeSections.includes("contradictions") &&
1178
+ contradictions.length > 0) {
1179
+ const heading = audience === "investor" || audience === "banker"
1180
+ ? "Risk Factors"
1181
+ : "Contradictions & Tensions";
1182
+ sections.push({
1183
+ key: audience === "investor" || audience === "banker"
1184
+ ? "riskFactors"
1185
+ : "contradictions",
1186
+ heading,
1187
+ items: contradictions.map((c) => ({
1188
+ text: c.title ??
1189
+ c.description ??
1190
+ JSON.stringify(c),
1191
+ meta: c.severity ?? undefined,
1192
+ })),
1193
+ body: "",
1194
+ });
1195
+ }
1196
+ // Next actions / moves
1197
+ if (nextActions.length > 0) {
1198
+ const heading = audience === "teammate"
1199
+ ? "Action Items"
1200
+ : audience === "founder"
1201
+ ? "Next 3 Moves"
1202
+ : audience === "developer"
1203
+ ? "Technical Decisions & Next Steps"
1204
+ : "Recommended Actions";
1205
+ const items = (audience === "founder" ? nextActions.slice(0, 3) : nextActions).map((a) => ({
1206
+ text: a.label ?? a.title ?? JSON.stringify(a),
1207
+ meta: [
1208
+ a.priority ? `priority: ${a.priority}` : "",
1209
+ a.owner ? `owner: ${a.owner}` : "",
1210
+ ]
1211
+ .filter(Boolean)
1212
+ .join(", ") || undefined,
1213
+ }));
1214
+ sections.push({
1215
+ key: audience === "teammate" ? "actionItems" : "nextMoves",
1216
+ heading,
1217
+ items,
1218
+ body: "",
1219
+ });
1220
+ }
1221
+ // Delegation brief (teammate only)
1222
+ if (audience === "teammate") {
1223
+ sections.push({
1224
+ key: "delegationBrief",
1225
+ heading: "Delegation Brief",
1226
+ body: agentInstructions
1227
+ ? `Focus: ${agentInstructions.focus ?? "See action items"}\nScope: ${agentInstructions.scope ?? "As assigned"}`
1228
+ : "No specific delegation instructions provided. See action items above.",
1229
+ });
1230
+ }
1231
+ // Market context / comparables
1232
+ if (nearbyEntities.length > 0 &&
1233
+ !config.excludeSections.includes("nearbyEntities")) {
1234
+ const heading = audience === "banker"
1235
+ ? "Comparables"
1236
+ : audience === "investor"
1237
+ ? "Market Context"
1238
+ : "Nearby Entities";
1239
+ sections.push({
1240
+ key: audience === "banker" ? "comparables" : "marketContext",
1241
+ heading,
1242
+ items: nearbyEntities.map((e) => ({
1243
+ text: e.name ?? JSON.stringify(e),
1244
+ meta: e.relationship ?? undefined,
1245
+ })),
1246
+ body: "",
1247
+ });
1248
+ }
1249
+ // Financial signals (banker)
1250
+ if (audience === "banker") {
1251
+ const signals = content.financialSignals ?? [];
1252
+ sections.push({
1253
+ key: "financialSignals",
1254
+ heading: "Financial Signals",
1255
+ body: signals.length > 0
1256
+ ? signals
1257
+ .map((s) => `${s.label ?? "Signal"}: ${s.value ?? "N/A"}`)
1258
+ .join("\n")
1259
+ : "No financial signals available in this packet.",
1260
+ });
1261
+ }
1262
+ // Architecture / API changes (developer)
1263
+ if (audience === "developer") {
1264
+ const archChanges = content.architectureChanges ?? [];
1265
+ const apiChanges = content.apiChanges ?? [];
1266
+ if (archChanges.length > 0 ||
1267
+ whatChanged.some((c) => (c.type ?? "").includes("arch"))) {
1268
+ sections.push({
1269
+ key: "architectureChanges",
1270
+ heading: "Architecture Changes",
1271
+ items: archChanges.length > 0
1272
+ ? archChanges.map((a) => ({
1273
+ text: a.description ?? JSON.stringify(a),
1274
+ }))
1275
+ : [
1276
+ {
1277
+ text: "See recent changes for architecture-related updates.",
1278
+ },
1279
+ ],
1280
+ body: "",
1281
+ });
1282
+ }
1283
+ if (apiChanges.length > 0) {
1284
+ sections.push({
1285
+ key: "apiChanges",
1286
+ heading: "API Changes",
1287
+ items: apiChanges.map((a) => ({
1288
+ text: a.description ?? JSON.stringify(a),
1289
+ })),
1290
+ body: "",
1291
+ });
1292
+ }
1293
+ }
1294
+ // Evidence
1295
+ if (!config.excludeSections.includes("evidence") &&
1296
+ keyEvidence.length > 0) {
1297
+ sections.push({
1298
+ key: "evidence",
1299
+ heading: "Key Evidence",
1300
+ items: keyEvidence.map((e) => ({
1301
+ text: e.claim ??
1302
+ e.description ??
1303
+ JSON.stringify(e),
1304
+ meta: e.source ?? undefined,
1305
+ })),
1306
+ body: "",
1307
+ });
1308
+ }
1309
+ // ── Sort sections by audience config order ──────────────────────
1310
+ const orderMap = new Map(config.sectionOrder.map((key, i) => [key, i]));
1311
+ sections.sort((a, b) => {
1312
+ const aOrder = orderMap.get(a.key) ?? 999;
1313
+ const bOrder = orderMap.get(b.key) ?? 999;
1314
+ return aOrder - bOrder;
1315
+ });
1316
+ // Filter out excluded sections
1317
+ const filteredSections = sections.filter((s) => !config.excludeSections.includes(s.key));
1318
+ // ── Metadata block ──────────────────────────────────────────────
1319
+ const metadataBlock = includeMetadata
1320
+ ? {
1321
+ generatedAt,
1322
+ nodebenchVersion: NODEBENCH_VERSION,
1323
+ exportId,
1324
+ audience,
1325
+ format,
1326
+ }
1327
+ : null;
1328
+ // ── Render helpers ──────────────────────────────────────────────
1329
+ function renderSectionMarkdown(s, emphasized) {
1330
+ const marker = emphasized ? " **[KEY]**" : "";
1331
+ let out = `## ${s.heading}${marker}\n\n`;
1332
+ if (s.body)
1333
+ out += `${s.body}\n\n`;
1334
+ if (s.items) {
1335
+ for (const item of s.items) {
1336
+ out += item.meta
1337
+ ? `- **${item.text}** _(${item.meta})_\n`
1338
+ : `- ${item.text}\n`;
1339
+ }
1340
+ out += "\n";
1341
+ }
1342
+ return out;
1343
+ }
1344
+ function renderSectionPlaintext(s) {
1345
+ let out = `${s.heading.toUpperCase()}\n${"=".repeat(s.heading.length)}\n\n`;
1346
+ if (s.body)
1347
+ out += `${s.body}\n\n`;
1348
+ if (s.items) {
1349
+ for (const item of s.items) {
1350
+ out += item.meta
1351
+ ? ` - ${item.text} (${item.meta})\n`
1352
+ : ` - ${item.text}\n`;
1353
+ }
1354
+ out += "\n";
1355
+ }
1356
+ return out;
1357
+ }
1358
+ const escapeHtml = (str) => str
1359
+ .replace(/&/g, "&amp;")
1360
+ .replace(/</g, "&lt;")
1361
+ .replace(/>/g, "&gt;")
1362
+ .replace(/"/g, "&quot;");
1363
+ // ── Render by format ────────────────────────────────────────────
1364
+ if (format === "json") {
1365
+ const jsonOutput = {
1366
+ title: `${config.headerPrefix}${title}`,
1367
+ audience,
1368
+ tone: config.tone,
1369
+ sections: Object.fromEntries(filteredSections.map((s) => [
1370
+ s.key,
1371
+ {
1372
+ heading: s.heading,
1373
+ ...(s.body ? { body: s.body } : {}),
1374
+ ...(s.items ? { items: s.items } : {}),
1375
+ emphasized: config.emphasisSections.includes(s.key),
1376
+ },
1377
+ ])),
1378
+ };
1379
+ if (metadataBlock)
1380
+ jsonOutput._metadata = metadataBlock;
1381
+ return { format: "json", exported: jsonOutput };
1382
+ }
1383
+ if (format === "markdown") {
1384
+ let md = `# ${config.headerPrefix}${title}\n\n`;
1385
+ if (config.tone === "formal")
1386
+ md += `> Prepared for ${audience} audience\n\n`;
1387
+ md += "---\n\n";
1388
+ for (const s of filteredSections) {
1389
+ md += renderSectionMarkdown(s, config.emphasisSections.includes(s.key));
1390
+ }
1391
+ if (metadataBlock) {
1392
+ md += "---\n\n";
1393
+ md += `_Generated: ${generatedAt} | NodeBench v${NODEBENCH_VERSION} | Export ID: ${exportId}_\n`;
1394
+ }
1395
+ return { format: "markdown", exported: md };
1396
+ }
1397
+ if (format === "plaintext") {
1398
+ let txt = `${config.headerPrefix}${title}\n${"=".repeat((config.headerPrefix + title).length)}\n\n`;
1399
+ for (const s of filteredSections) {
1400
+ txt += renderSectionPlaintext(s);
1401
+ }
1402
+ if (metadataBlock) {
1403
+ txt += `${"—".repeat(40)}\n`;
1404
+ txt += `Generated: ${generatedAt}\nNodeBench v${NODEBENCH_VERSION}\nExport ID: ${exportId}\n`;
1405
+ }
1406
+ return { format: "plaintext", exported: txt };
1407
+ }
1408
+ // HTML format — self-contained with inline CSS matching glass card DNA
1409
+ if (format === "html") {
1410
+ let body = "";
1411
+ for (const s of filteredSections) {
1412
+ const isKey = config.emphasisSections.includes(s.key);
1413
+ body += `<section class="card${isKey ? " emphasized" : ""}">\n`;
1414
+ body += ` <h2>${escapeHtml(s.heading)}${isKey ? ' <span class="badge">KEY</span>' : ""}</h2>\n`;
1415
+ if (s.body)
1416
+ body += ` <p>${escapeHtml(s.body).replace(/\n/g, "<br>")}</p>\n`;
1417
+ if (s.items) {
1418
+ body += " <ul>\n";
1419
+ for (const item of s.items) {
1420
+ body += item.meta
1421
+ ? ` <li><strong>${escapeHtml(item.text)}</strong> <span class="meta">(${escapeHtml(item.meta)})</span></li>\n`
1422
+ : ` <li>${escapeHtml(item.text)}</li>\n`;
1423
+ }
1424
+ body += " </ul>\n";
1425
+ }
1426
+ body += "</section>\n";
1427
+ }
1428
+ let metaHtml = "";
1429
+ if (metadataBlock) {
1430
+ metaHtml = `<footer class="meta-footer">Generated: ${escapeHtml(generatedAt)} &middot; NodeBench v${escapeHtml(NODEBENCH_VERSION)} &middot; Export ID: ${escapeHtml(exportId)}</footer>`;
1431
+ }
1432
+ const html = `<!DOCTYPE html>
1433
+ <html lang="en">
1434
+ <head>
1435
+ <meta charset="UTF-8">
1436
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1437
+ <title>${escapeHtml(config.headerPrefix + title)}</title>
1438
+ <style>
1439
+ @import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&display=swap');
1440
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
1441
+ body {
1442
+ font-family: 'Manrope', system-ui, sans-serif;
1443
+ background: #09090b; color: #fafafa;
1444
+ padding: 2rem; max-width: 800px; margin: 0 auto;
1445
+ line-height: 1.6;
1446
+ }
1447
+ h1 { font-size: 1.5rem; font-weight: 700; margin-bottom: 0.25rem; }
1448
+ .subtitle { color: #a1a1aa; font-size: 0.85rem; margin-bottom: 1.5rem; }
1449
+ .card {
1450
+ background: rgba(255,255,255,0.02);
1451
+ border: 1px solid rgba(255,255,255,0.06);
1452
+ border-radius: 12px; padding: 1.25rem;
1453
+ margin-bottom: 1rem;
1454
+ }
1455
+ .card.emphasized { border-color: #d97757; }
1456
+ .card h2 { font-size: 0.95rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.12em; margin-bottom: 0.75rem; color: #e4e4e7; }
1457
+ .badge { background: #d97757; color: #09090b; font-size: 0.65rem; padding: 2px 6px; border-radius: 4px; vertical-align: middle; letter-spacing: 0.05em; }
1458
+ .card p { color: #d4d4d8; font-size: 0.9rem; margin-bottom: 0.5rem; }
1459
+ .card ul { list-style: none; padding: 0; }
1460
+ .card li { padding: 0.3rem 0; border-bottom: 1px solid rgba(255,255,255,0.04); font-size: 0.9rem; color: #d4d4d8; }
1461
+ .card li:last-child { border-bottom: none; }
1462
+ .card li strong { color: #fafafa; }
1463
+ .meta { color: #71717a; font-size: 0.8rem; }
1464
+ .meta-footer { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid rgba(255,255,255,0.06); color: #71717a; font-size: 0.75rem; text-align: center; }
1465
+ </style>
1466
+ </head>
1467
+ <body>
1468
+ <h1>${escapeHtml(config.headerPrefix + title)}</h1>
1469
+ <div class="subtitle">Prepared for ${escapeHtml(audience)} audience</div>
1470
+ ${body}
1471
+ ${metaHtml}
1472
+ </body>
1473
+ </html>`;
1474
+ return { format: "html", exported: html };
1475
+ }
1476
+ return { error: true, message: `Unhandled format: ${format}` };
1477
+ },
1478
+ },
594
1479
  ];
595
1480
  //# sourceMappingURL=founderTools.js.map