nodebench-mcp 2.44.0 → 2.46.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.
@@ -10,6 +10,116 @@
10
10
  * every piece of context it needs.
11
11
  */
12
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
+ }
13
123
  const GATHER_PROTOCOL = [
14
124
  {
15
125
  id: "company_identity",
@@ -219,6 +329,70 @@ const GATHER_PROTOCOL = [
219
329
  },
220
330
  ];
221
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
+ /* ------------------------------------------------------------------ */
222
396
  /* Tools */
223
397
  /* ------------------------------------------------------------------ */
224
398
  export const founderTools = [
@@ -244,6 +418,10 @@ export const founderTools = [
244
418
  type: "string",
245
419
  description: "Optional summary of the most recent prior packet, for temporal comparison.",
246
420
  },
421
+ entityId: {
422
+ type: "string",
423
+ description: "Optional entity ID to scope the gather (and prior-brief lookup) to a specific company/entity.",
424
+ },
247
425
  focusAreas: {
248
426
  type: "array",
249
427
  items: { type: "string" },
@@ -326,6 +504,10 @@ export const founderTools = [
326
504
  .prepare("SELECT * FROM causal_trajectory_scores ORDER BY createdAt DESC LIMIT 1")
327
505
  .all();
328
506
  if (recentEvents.length > 0 || importantChanges.length > 0 || stateDiffs.length > 0) {
507
+ // Extract mission keywords from most recent state diff for thesis relevance
508
+ const missionKeywords = extractMissionKeywords(stateDiffs);
509
+ // Score and rank events by importance
510
+ const { ranked, suppressedCount, topSignal } = scoreEvents(recentEvents, missionKeywords);
329
511
  sessionMemory = {
330
512
  source: "causal_memory_auto_hydrate",
331
513
  period: "last_7_days",
@@ -335,13 +517,9 @@ export const founderTools = [
335
517
  weeklyActions,
336
518
  milestones,
337
519
  trajectory: trajectory[0] ?? null,
338
- events: recentEvents.map((e) => ({
339
- type: e.eventType,
340
- entity: e.entityId,
341
- summary: e.summary,
342
- actor: e.actor,
343
- timestamp: e.timestamp,
344
- })),
520
+ topSignal,
521
+ suppressedCount,
522
+ events: ranked,
345
523
  changes: importantChanges.map((c) => ({
346
524
  category: c.changeCategory,
347
525
  impact: c.impactScore,
@@ -360,6 +538,100 @@ export const founderTools = [
360
538
  // causal memory tables may not exist yet — graceful fallback
361
539
  sessionMemory = null;
362
540
  }
541
+ // ── Prior-brief cross-referencing for weekly_reset ──────────────
542
+ let priorBriefComparison = null;
543
+ if (packetType === "weekly_reset") {
544
+ try {
545
+ ensurePacketSchema();
546
+ const db = getDb();
547
+ const entityId = args.entityId ?? null;
548
+ const priorRow = entityId
549
+ ? db
550
+ .prepare("SELECT * FROM founder_packets WHERE entityId = ? ORDER BY createdAt DESC LIMIT 1")
551
+ .get(entityId)
552
+ : db
553
+ .prepare("SELECT * FROM founder_packets ORDER BY createdAt DESC LIMIT 1")
554
+ .get();
555
+ if (priorRow) {
556
+ const lastPacketDate = priorRow.createdAt;
557
+ const lastPacketMs = new Date(lastPacketDate).getTime();
558
+ const daysSinceLastPacket = Math.max(0, Math.round((Date.now() - lastPacketMs) / (24 * 60 * 60 * 1000)));
559
+ let priorPacketData = {};
560
+ try {
561
+ priorPacketData = JSON.parse(priorRow.packetJson);
562
+ }
563
+ catch {
564
+ // malformed JSON — treat as empty
565
+ }
566
+ // Gather changes/events from sessionMemory that are AFTER the prior packet
567
+ const newSinceLastPacket = [];
568
+ const stillUnresolved = [];
569
+ const resolvedSinceLastPacket = [];
570
+ // Classify current important changes against prior packet
571
+ if (sessionMemory) {
572
+ const currentChanges = (sessionMemory.changes ?? []);
573
+ const currentEvents = (sessionMemory.events ?? []);
574
+ // Events that occurred after the prior packet
575
+ for (const evt of currentEvents) {
576
+ const evtTs = evt.timestamp ?? 0;
577
+ if (evtTs > lastPacketMs) {
578
+ newSinceLastPacket.push(evt);
579
+ }
580
+ }
581
+ // Classify important changes: still unresolved vs resolved
582
+ const priorChanges = (priorPacketData.whatChanged ?? priorPacketData.changes ?? []);
583
+ const priorChangeIds = new Set(priorChanges.map((c) => c.id ?? c.description ?? ""));
584
+ for (const change of currentChanges) {
585
+ const changeKey = change.category ?? change.reason ?? "";
586
+ if (change.status === "detected" || change.status === "acknowledged" || change.status === "investigating") {
587
+ stillUnresolved.push(change);
588
+ }
589
+ }
590
+ // Prior changes no longer in current unresolved set
591
+ const unresolvedKeys = new Set(stillUnresolved.map((c) => c.category ?? c.reason ?? ""));
592
+ for (const pc of priorChanges) {
593
+ const pcKey = pc.id ?? pc.description ?? pc.category ?? "";
594
+ if (pcKey && !unresolvedKeys.has(pcKey)) {
595
+ resolvedSinceLastPacket.push(pc);
596
+ }
597
+ }
598
+ }
599
+ // Recommended focus: top 3 from (new + unresolved) ranked by impact
600
+ const focusCandidates = [
601
+ ...newSinceLastPacket.map((e) => ({
602
+ item: e.summary ?? e.type ?? "unknown event",
603
+ source: "new",
604
+ impact: e.importanceScore ?? 0.5,
605
+ })),
606
+ ...stillUnresolved.map((c) => ({
607
+ item: c.reason ?? c.category ?? "unresolved change",
608
+ source: "unresolved",
609
+ impact: c.impact ?? 0.5,
610
+ })),
611
+ ];
612
+ focusCandidates.sort((a, b) => b.impact - a.impact);
613
+ const recommendedFocus = focusCandidates.slice(0, 3);
614
+ priorBriefComparison = {
615
+ lastPacketDate,
616
+ daysSinceLastPacket,
617
+ priorPacketId: priorRow.packetId ?? null,
618
+ priorPacketType: priorRow.packetType ?? null,
619
+ newSinceLastPacket,
620
+ stillUnresolved,
621
+ resolvedSinceLastPacket,
622
+ recommendedFocus,
623
+ };
624
+ // Attach to sessionMemory if it exists
625
+ if (sessionMemory) {
626
+ sessionMemory.priorBriefComparison = priorBriefComparison;
627
+ }
628
+ }
629
+ }
630
+ catch {
631
+ // founder_packets table may not exist yet — graceful fallback
632
+ priorBriefComparison = null;
633
+ }
634
+ }
363
635
  const protocol = {
364
636
  protocolVersion: "1.1",
365
637
  packetType,
@@ -367,6 +639,8 @@ export const founderTools = [
367
639
  requiredSteps: requiredSteps.length,
368
640
  // Pre-hydrated session memory (if available)
369
641
  ...(sessionMemory ? { sessionMemory } : {}),
642
+ // Prior-brief cross-reference (weekly_reset only, null if no prior packet)
643
+ ...(packetType === "weekly_reset" ? { priorBriefComparison } : {}),
370
644
  instructions: [
371
645
  ...(sessionMemory
372
646
  ? [
@@ -374,6 +648,11 @@ export const founderTools = [
374
648
  "Cross-reference sessionMemory events, changes, and diffs against the gather steps below.",
375
649
  ]
376
650
  : []),
651
+ ...(priorBriefComparison
652
+ ? [
653
+ "Cross-reference findings against priorBriefComparison. Highlight what's NEW vs what was already known. Do not repeat resolved items.",
654
+ ]
655
+ : []),
377
656
  "You MUST complete ALL required gather steps before generating the artifact packet.",
378
657
  "For each step, search the listed sources using the provided search patterns.",
379
658
  "Do NOT skip a step because it seems redundant — redundancy catches blind spots.",
@@ -665,5 +944,642 @@ export const founderTools = [
665
944
  };
666
945
  },
667
946
  },
947
+ // ─── 4. founder_packet_history_diff ──────────────────────────────
948
+ {
949
+ name: "founder_packet_history_diff",
950
+ description: "Compares the most recent Founder Artifact Packet for an entity against " +
951
+ "prior packets stored in the founder_packets SQLite table. Returns a " +
952
+ "structured diff: newSinceLastTime, resolvedSinceLastTime, changedFields, " +
953
+ "stableFields, and a driftScore (0.0–1.0). If only one packet exists, " +
954
+ "returns it as a baseline. If none exist, suggests running " +
955
+ "founder_deep_context_gather first.",
956
+ inputSchema: {
957
+ type: "object",
958
+ properties: {
959
+ entityId: {
960
+ type: "string",
961
+ description: "The entity ID to look up packets for.",
962
+ },
963
+ packetType: {
964
+ type: "string",
965
+ description: "Optional packet type filter (e.g. weekly_reset, pre_delegation, important_change).",
966
+ },
967
+ limit: {
968
+ type: "number",
969
+ description: "Max number of recent packets to retrieve for comparison (default 2, max 10).",
970
+ },
971
+ },
972
+ required: ["entityId"],
973
+ },
974
+ annotations: { readOnlyHint: true },
975
+ handler: async (args) => {
976
+ ensurePacketSchema();
977
+ const db = getDb();
978
+ const entityId = args.entityId;
979
+ const packetType = args.packetType ?? null;
980
+ const limit = Math.min(Math.max(args.limit ?? 2, 1), 10);
981
+ // Query recent packets for this entity
982
+ let rows;
983
+ if (packetType) {
984
+ rows = db
985
+ .prepare(`SELECT packetId, entityId, packetType, packetJson, createdAt
986
+ FROM founder_packets
987
+ WHERE entityId = ? AND packetType = ?
988
+ ORDER BY createdAt DESC
989
+ LIMIT ?`)
990
+ .all(entityId, packetType, limit);
991
+ }
992
+ else {
993
+ rows = db
994
+ .prepare(`SELECT packetId, entityId, packetType, packetJson, createdAt
995
+ FROM founder_packets
996
+ WHERE entityId = ?
997
+ ORDER BY createdAt DESC
998
+ LIMIT ?`)
999
+ .all(entityId, limit);
1000
+ }
1001
+ // No packets found
1002
+ if (rows.length === 0) {
1003
+ return {
1004
+ noPackets: true,
1005
+ entityId,
1006
+ suggestion: "Run founder_deep_context_gather first to generate and store a packet.",
1007
+ };
1008
+ }
1009
+ // Parse packet JSON safely
1010
+ const packets = rows.map((row) => {
1011
+ let parsed = {};
1012
+ try {
1013
+ parsed = JSON.parse(row.packetJson);
1014
+ }
1015
+ catch {
1016
+ parsed = { _parseError: true, raw: row.packetJson };
1017
+ }
1018
+ return {
1019
+ packetId: row.packetId,
1020
+ entityId: row.entityId,
1021
+ packetType: row.packetType,
1022
+ createdAt: row.createdAt,
1023
+ data: parsed,
1024
+ };
1025
+ });
1026
+ // Only one packet — return as baseline
1027
+ if (packets.length === 1) {
1028
+ return {
1029
+ isFirstPacket: true,
1030
+ entityId,
1031
+ packet: {
1032
+ packetId: packets[0].packetId,
1033
+ packetType: packets[0].packetType,
1034
+ createdAt: packets[0].createdAt,
1035
+ },
1036
+ note: "First packet for this entity. Future calls will produce diffs.",
1037
+ };
1038
+ }
1039
+ // 2+ packets — diff the most recent against the one before it
1040
+ const latest = packets[0];
1041
+ const prior = packets[1];
1042
+ const diff = computePacketDiff(latest.data, prior.data);
1043
+ return {
1044
+ entityId,
1045
+ latest: {
1046
+ packetId: latest.packetId,
1047
+ packetType: latest.packetType,
1048
+ createdAt: latest.createdAt,
1049
+ },
1050
+ prior: {
1051
+ packetId: prior.packetId,
1052
+ packetType: prior.packetType,
1053
+ createdAt: prior.createdAt,
1054
+ },
1055
+ diff: {
1056
+ newSinceLastTime: diff.newSinceLastTime,
1057
+ resolvedSinceLastTime: diff.resolvedSinceLastTime,
1058
+ changedFields: diff.changedFields,
1059
+ stableFields: diff.stableFields,
1060
+ driftScore: diff.driftScore,
1061
+ },
1062
+ summary: {
1063
+ totalFieldsCompared: diff.newSinceLastTime.length +
1064
+ diff.resolvedSinceLastTime.length +
1065
+ diff.changedFields.length +
1066
+ diff.stableFields.length,
1067
+ newCount: diff.newSinceLastTime.length,
1068
+ resolvedCount: diff.resolvedSinceLastTime.length,
1069
+ changedCount: diff.changedFields.length,
1070
+ stableCount: diff.stableFields.length,
1071
+ driftScore: diff.driftScore,
1072
+ driftLevel: diff.driftScore < 0.1
1073
+ ? "minimal"
1074
+ : diff.driftScore < 0.3
1075
+ ? "low"
1076
+ : diff.driftScore < 0.6
1077
+ ? "moderate"
1078
+ : diff.driftScore < 0.85
1079
+ ? "high"
1080
+ : "extreme",
1081
+ },
1082
+ packetsAvailable: packets.length,
1083
+ };
1084
+ },
1085
+ },
1086
+ // ─── 5. export_artifact_packet ──────────────────────────────────
1087
+ {
1088
+ name: "export_artifact_packet",
1089
+ description: "Formats a Founder Artifact Packet or memo for export to a specific audience and format. " +
1090
+ "Applies audience-specific framing (founder, investor, banker, developer, teammate) and " +
1091
+ "renders into the requested format (markdown, html, json, plaintext). Always includes " +
1092
+ "provenance metadata (timestamp, version, exportId) for traceability.",
1093
+ inputSchema: {
1094
+ type: "object",
1095
+ properties: {
1096
+ content: {
1097
+ type: "object",
1098
+ description: "The raw packet/memo content to format for export.",
1099
+ },
1100
+ format: {
1101
+ type: "string",
1102
+ enum: ["markdown", "html", "json", "plaintext"],
1103
+ description: "Output format for the exported artifact.",
1104
+ },
1105
+ audience: {
1106
+ type: "string",
1107
+ enum: ["founder", "investor", "banker", "developer", "teammate"],
1108
+ description: "Target audience — controls tone, ordering, and which sections are emphasized.",
1109
+ },
1110
+ title: {
1111
+ type: "string",
1112
+ description: "Override title for the exported artifact. Defaults to packet title or 'Artifact Packet'.",
1113
+ },
1114
+ includeMetadata: {
1115
+ type: "boolean",
1116
+ description: "Include generation timestamp, tool version, and provenance block. Defaults to true.",
1117
+ },
1118
+ },
1119
+ required: ["content", "format", "audience"],
1120
+ },
1121
+ handler: async (args) => {
1122
+ const content = args.content;
1123
+ const format = args.format;
1124
+ const audience = args.audience;
1125
+ const titleOverride = args.title ?? null;
1126
+ const includeMetadata = args.includeMetadata ?? true;
1127
+ const validFormats = ["markdown", "html", "json", "plaintext"];
1128
+ const validAudiences = ["founder", "investor", "banker", "developer", "teammate"];
1129
+ if (!validFormats.includes(format)) {
1130
+ return {
1131
+ error: true,
1132
+ message: `Invalid format: ${format}. Must be one of: ${validFormats.join(", ")}.`,
1133
+ };
1134
+ }
1135
+ if (!validAudiences.includes(audience)) {
1136
+ return {
1137
+ error: true,
1138
+ message: `Invalid audience: ${audience}. Must be one of: ${validAudiences.join(", ")}.`,
1139
+ };
1140
+ }
1141
+ const NODEBENCH_VERSION = "1.0.0";
1142
+ const exportId = `exp_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
1143
+ const generatedAt = new Date().toISOString();
1144
+ // ── Extract common fields from content ──────────────────────────
1145
+ const entity = content.canonicalEntity ?? {};
1146
+ const companyName = entity.name ?? content.companyName ?? "Unknown Company";
1147
+ const mission = entity.mission ?? content.mission ?? "";
1148
+ const wedge = entity.wedge ?? content.wedge ?? "";
1149
+ const identityConfidence = entity.identityConfidence ?? null;
1150
+ const contradictions = content.contradictions ?? [];
1151
+ const nextActions = content.nextActions ?? [];
1152
+ const operatingMemo = content.operatingMemo ?? "";
1153
+ const whatChanged = content.whatChanged ?? [];
1154
+ const nearbyEntities = content.nearbyEntities ?? [];
1155
+ const keyEvidence = content.keyEvidence ?? [];
1156
+ const agentInstructions = content.agentInstructions ?? null;
1157
+ const title = titleOverride ?? content.title ?? "Artifact Packet";
1158
+ const audienceConfigs = {
1159
+ founder: {
1160
+ tone: "informal",
1161
+ sectionOrder: [
1162
+ "summary",
1163
+ "whatChanged",
1164
+ "contradictions",
1165
+ "nextMoves",
1166
+ "initiatives",
1167
+ "agents",
1168
+ "operatingMemo",
1169
+ "evidence",
1170
+ ],
1171
+ emphasisSections: ["contradictions", "nextMoves"],
1172
+ excludeSections: [],
1173
+ headerPrefix: "",
1174
+ },
1175
+ investor: {
1176
+ tone: "formal",
1177
+ sectionOrder: [
1178
+ "metrics",
1179
+ "summary",
1180
+ "marketContext",
1181
+ "risks",
1182
+ "initiatives",
1183
+ "evidence",
1184
+ ],
1185
+ emphasisSections: ["metrics", "risks"],
1186
+ excludeSections: ["agents", "agentInstructions"],
1187
+ headerPrefix: "Investment Memo: ",
1188
+ },
1189
+ banker: {
1190
+ tone: "formal",
1191
+ sectionOrder: [
1192
+ "companySnapshot",
1193
+ "financialSignals",
1194
+ "riskFactors",
1195
+ "comparables",
1196
+ "summary",
1197
+ "evidence",
1198
+ ],
1199
+ emphasisSections: ["companySnapshot", "financialSignals", "riskFactors"],
1200
+ excludeSections: ["agents", "agentInstructions", "operatingMemo"],
1201
+ headerPrefix: "Memo: ",
1202
+ },
1203
+ developer: {
1204
+ tone: "technical",
1205
+ sectionOrder: [
1206
+ "architectureChanges",
1207
+ "technicalDecisions",
1208
+ "apiChanges",
1209
+ "whatChanged",
1210
+ "nextMoves",
1211
+ "agents",
1212
+ ],
1213
+ emphasisSections: ["architectureChanges", "apiChanges"],
1214
+ excludeSections: ["nearbyEntities", "operatingMemo"],
1215
+ headerPrefix: "",
1216
+ },
1217
+ teammate: {
1218
+ tone: "conversational",
1219
+ sectionOrder: ["delegationBrief", "actionItems", "whatChanged", "context"],
1220
+ emphasisSections: ["delegationBrief", "actionItems"],
1221
+ excludeSections: ["evidence", "nearbyEntities"],
1222
+ headerPrefix: "",
1223
+ },
1224
+ };
1225
+ const config = audienceConfigs[audience];
1226
+ const sections = [];
1227
+ // Summary / company snapshot
1228
+ if (audience === "banker") {
1229
+ sections.push({
1230
+ key: "companySnapshot",
1231
+ heading: "Company Snapshot",
1232
+ body: [
1233
+ companyName ? `Company: ${companyName}` : "",
1234
+ mission ? `Mission: ${mission}` : "",
1235
+ wedge ? `Wedge: ${wedge}` : "",
1236
+ identityConfidence !== null
1237
+ ? `Identity Confidence: ${Math.round(identityConfidence * 100)}%`
1238
+ : "",
1239
+ ]
1240
+ .filter(Boolean)
1241
+ .join("\n"),
1242
+ });
1243
+ }
1244
+ else if (audience === "investor") {
1245
+ sections.push({
1246
+ key: "metrics",
1247
+ heading: "Key Metrics & Traction",
1248
+ body: [
1249
+ companyName ? `Company: ${companyName}` : "",
1250
+ identityConfidence !== null
1251
+ ? `Identity Confidence: ${Math.round(identityConfidence * 100)}%`
1252
+ : "",
1253
+ `Active Initiatives: ${(content.initiatives ?? []).length}`,
1254
+ `Open Contradictions: ${contradictions.length}`,
1255
+ `Pending Actions: ${nextActions.length}`,
1256
+ ]
1257
+ .filter(Boolean)
1258
+ .join("\n"),
1259
+ });
1260
+ }
1261
+ sections.push({
1262
+ key: "summary",
1263
+ heading: audience === "teammate" ? "Context" : "Summary",
1264
+ body: operatingMemo || `${companyName} — ${mission}`,
1265
+ });
1266
+ // What changed
1267
+ if (!config.excludeSections.includes("whatChanged") &&
1268
+ whatChanged.length > 0) {
1269
+ sections.push({
1270
+ key: "whatChanged",
1271
+ heading: audience === "developer" ? "Recent Changes" : "What Changed",
1272
+ items: whatChanged.map((c) => ({
1273
+ text: c.description ??
1274
+ c.summary ??
1275
+ JSON.stringify(c),
1276
+ meta: c.type ?? undefined,
1277
+ })),
1278
+ body: "",
1279
+ });
1280
+ }
1281
+ // Contradictions / risks
1282
+ if (!config.excludeSections.includes("contradictions") &&
1283
+ contradictions.length > 0) {
1284
+ const heading = audience === "investor" || audience === "banker"
1285
+ ? "Risk Factors"
1286
+ : "Contradictions & Tensions";
1287
+ sections.push({
1288
+ key: audience === "investor" || audience === "banker"
1289
+ ? "riskFactors"
1290
+ : "contradictions",
1291
+ heading,
1292
+ items: contradictions.map((c) => ({
1293
+ text: c.title ??
1294
+ c.description ??
1295
+ JSON.stringify(c),
1296
+ meta: c.severity ?? undefined,
1297
+ })),
1298
+ body: "",
1299
+ });
1300
+ }
1301
+ // Next actions / moves
1302
+ if (nextActions.length > 0) {
1303
+ const heading = audience === "teammate"
1304
+ ? "Action Items"
1305
+ : audience === "founder"
1306
+ ? "Next 3 Moves"
1307
+ : audience === "developer"
1308
+ ? "Technical Decisions & Next Steps"
1309
+ : "Recommended Actions";
1310
+ const items = (audience === "founder" ? nextActions.slice(0, 3) : nextActions).map((a) => ({
1311
+ text: a.label ?? a.title ?? JSON.stringify(a),
1312
+ meta: [
1313
+ a.priority ? `priority: ${a.priority}` : "",
1314
+ a.owner ? `owner: ${a.owner}` : "",
1315
+ ]
1316
+ .filter(Boolean)
1317
+ .join(", ") || undefined,
1318
+ }));
1319
+ sections.push({
1320
+ key: audience === "teammate" ? "actionItems" : "nextMoves",
1321
+ heading,
1322
+ items,
1323
+ body: "",
1324
+ });
1325
+ }
1326
+ // Delegation brief (teammate only)
1327
+ if (audience === "teammate") {
1328
+ sections.push({
1329
+ key: "delegationBrief",
1330
+ heading: "Delegation Brief",
1331
+ body: agentInstructions
1332
+ ? `Focus: ${agentInstructions.focus ?? "See action items"}\nScope: ${agentInstructions.scope ?? "As assigned"}`
1333
+ : "No specific delegation instructions provided. See action items above.",
1334
+ });
1335
+ }
1336
+ // Market context / comparables
1337
+ if (nearbyEntities.length > 0 &&
1338
+ !config.excludeSections.includes("nearbyEntities")) {
1339
+ const heading = audience === "banker"
1340
+ ? "Comparables"
1341
+ : audience === "investor"
1342
+ ? "Market Context"
1343
+ : "Nearby Entities";
1344
+ sections.push({
1345
+ key: audience === "banker" ? "comparables" : "marketContext",
1346
+ heading,
1347
+ items: nearbyEntities.map((e) => ({
1348
+ text: e.name ?? JSON.stringify(e),
1349
+ meta: e.relationship ?? undefined,
1350
+ })),
1351
+ body: "",
1352
+ });
1353
+ }
1354
+ // Financial signals (banker)
1355
+ if (audience === "banker") {
1356
+ const signals = content.financialSignals ?? [];
1357
+ sections.push({
1358
+ key: "financialSignals",
1359
+ heading: "Financial Signals",
1360
+ body: signals.length > 0
1361
+ ? signals
1362
+ .map((s) => `${s.label ?? "Signal"}: ${s.value ?? "N/A"}`)
1363
+ .join("\n")
1364
+ : "No financial signals available in this packet.",
1365
+ });
1366
+ }
1367
+ // Architecture / API changes (developer)
1368
+ if (audience === "developer") {
1369
+ const archChanges = content.architectureChanges ?? [];
1370
+ const apiChanges = content.apiChanges ?? [];
1371
+ if (archChanges.length > 0 ||
1372
+ whatChanged.some((c) => (c.type ?? "").includes("arch"))) {
1373
+ sections.push({
1374
+ key: "architectureChanges",
1375
+ heading: "Architecture Changes",
1376
+ items: archChanges.length > 0
1377
+ ? archChanges.map((a) => ({
1378
+ text: a.description ?? JSON.stringify(a),
1379
+ }))
1380
+ : [
1381
+ {
1382
+ text: "See recent changes for architecture-related updates.",
1383
+ },
1384
+ ],
1385
+ body: "",
1386
+ });
1387
+ }
1388
+ if (apiChanges.length > 0) {
1389
+ sections.push({
1390
+ key: "apiChanges",
1391
+ heading: "API Changes",
1392
+ items: apiChanges.map((a) => ({
1393
+ text: a.description ?? JSON.stringify(a),
1394
+ })),
1395
+ body: "",
1396
+ });
1397
+ }
1398
+ }
1399
+ // Evidence
1400
+ if (!config.excludeSections.includes("evidence") &&
1401
+ keyEvidence.length > 0) {
1402
+ sections.push({
1403
+ key: "evidence",
1404
+ heading: "Key Evidence",
1405
+ items: keyEvidence.map((e) => ({
1406
+ text: e.claim ??
1407
+ e.description ??
1408
+ JSON.stringify(e),
1409
+ meta: e.source ?? undefined,
1410
+ })),
1411
+ body: "",
1412
+ });
1413
+ }
1414
+ // ── Sort sections by audience config order ──────────────────────
1415
+ const orderMap = new Map(config.sectionOrder.map((key, i) => [key, i]));
1416
+ sections.sort((a, b) => {
1417
+ const aOrder = orderMap.get(a.key) ?? 999;
1418
+ const bOrder = orderMap.get(b.key) ?? 999;
1419
+ return aOrder - bOrder;
1420
+ });
1421
+ // Filter out excluded sections
1422
+ const filteredSections = sections.filter((s) => !config.excludeSections.includes(s.key));
1423
+ // ── Metadata block ──────────────────────────────────────────────
1424
+ const metadataBlock = includeMetadata
1425
+ ? {
1426
+ generatedAt,
1427
+ nodebenchVersion: NODEBENCH_VERSION,
1428
+ exportId,
1429
+ audience,
1430
+ format,
1431
+ }
1432
+ : null;
1433
+ // ── Render helpers ──────────────────────────────────────────────
1434
+ function renderSectionMarkdown(s, emphasized) {
1435
+ const marker = emphasized ? " **[KEY]**" : "";
1436
+ let out = `## ${s.heading}${marker}\n\n`;
1437
+ if (s.body)
1438
+ out += `${s.body}\n\n`;
1439
+ if (s.items) {
1440
+ for (const item of s.items) {
1441
+ out += item.meta
1442
+ ? `- **${item.text}** _(${item.meta})_\n`
1443
+ : `- ${item.text}\n`;
1444
+ }
1445
+ out += "\n";
1446
+ }
1447
+ return out;
1448
+ }
1449
+ function renderSectionPlaintext(s) {
1450
+ let out = `${s.heading.toUpperCase()}\n${"=".repeat(s.heading.length)}\n\n`;
1451
+ if (s.body)
1452
+ out += `${s.body}\n\n`;
1453
+ if (s.items) {
1454
+ for (const item of s.items) {
1455
+ out += item.meta
1456
+ ? ` - ${item.text} (${item.meta})\n`
1457
+ : ` - ${item.text}\n`;
1458
+ }
1459
+ out += "\n";
1460
+ }
1461
+ return out;
1462
+ }
1463
+ const escapeHtml = (str) => str
1464
+ .replace(/&/g, "&amp;")
1465
+ .replace(/</g, "&lt;")
1466
+ .replace(/>/g, "&gt;")
1467
+ .replace(/"/g, "&quot;");
1468
+ // ── Render by format ────────────────────────────────────────────
1469
+ if (format === "json") {
1470
+ const jsonOutput = {
1471
+ title: `${config.headerPrefix}${title}`,
1472
+ audience,
1473
+ tone: config.tone,
1474
+ sections: Object.fromEntries(filteredSections.map((s) => [
1475
+ s.key,
1476
+ {
1477
+ heading: s.heading,
1478
+ ...(s.body ? { body: s.body } : {}),
1479
+ ...(s.items ? { items: s.items } : {}),
1480
+ emphasized: config.emphasisSections.includes(s.key),
1481
+ },
1482
+ ])),
1483
+ };
1484
+ if (metadataBlock)
1485
+ jsonOutput._metadata = metadataBlock;
1486
+ return { format: "json", exported: jsonOutput };
1487
+ }
1488
+ if (format === "markdown") {
1489
+ let md = `# ${config.headerPrefix}${title}\n\n`;
1490
+ if (config.tone === "formal")
1491
+ md += `> Prepared for ${audience} audience\n\n`;
1492
+ md += "---\n\n";
1493
+ for (const s of filteredSections) {
1494
+ md += renderSectionMarkdown(s, config.emphasisSections.includes(s.key));
1495
+ }
1496
+ if (metadataBlock) {
1497
+ md += "---\n\n";
1498
+ md += `_Generated: ${generatedAt} | NodeBench v${NODEBENCH_VERSION} | Export ID: ${exportId}_\n`;
1499
+ }
1500
+ return { format: "markdown", exported: md };
1501
+ }
1502
+ if (format === "plaintext") {
1503
+ let txt = `${config.headerPrefix}${title}\n${"=".repeat((config.headerPrefix + title).length)}\n\n`;
1504
+ for (const s of filteredSections) {
1505
+ txt += renderSectionPlaintext(s);
1506
+ }
1507
+ if (metadataBlock) {
1508
+ txt += `${"—".repeat(40)}\n`;
1509
+ txt += `Generated: ${generatedAt}\nNodeBench v${NODEBENCH_VERSION}\nExport ID: ${exportId}\n`;
1510
+ }
1511
+ return { format: "plaintext", exported: txt };
1512
+ }
1513
+ // HTML format — self-contained with inline CSS matching glass card DNA
1514
+ if (format === "html") {
1515
+ let body = "";
1516
+ for (const s of filteredSections) {
1517
+ const isKey = config.emphasisSections.includes(s.key);
1518
+ body += `<section class="card${isKey ? " emphasized" : ""}">\n`;
1519
+ body += ` <h2>${escapeHtml(s.heading)}${isKey ? ' <span class="badge">KEY</span>' : ""}</h2>\n`;
1520
+ if (s.body)
1521
+ body += ` <p>${escapeHtml(s.body).replace(/\n/g, "<br>")}</p>\n`;
1522
+ if (s.items) {
1523
+ body += " <ul>\n";
1524
+ for (const item of s.items) {
1525
+ body += item.meta
1526
+ ? ` <li><strong>${escapeHtml(item.text)}</strong> <span class="meta">(${escapeHtml(item.meta)})</span></li>\n`
1527
+ : ` <li>${escapeHtml(item.text)}</li>\n`;
1528
+ }
1529
+ body += " </ul>\n";
1530
+ }
1531
+ body += "</section>\n";
1532
+ }
1533
+ let metaHtml = "";
1534
+ if (metadataBlock) {
1535
+ metaHtml = `<footer class="meta-footer">Generated: ${escapeHtml(generatedAt)} &middot; NodeBench v${escapeHtml(NODEBENCH_VERSION)} &middot; Export ID: ${escapeHtml(exportId)}</footer>`;
1536
+ }
1537
+ const html = `<!DOCTYPE html>
1538
+ <html lang="en">
1539
+ <head>
1540
+ <meta charset="UTF-8">
1541
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1542
+ <title>${escapeHtml(config.headerPrefix + title)}</title>
1543
+ <style>
1544
+ @import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&display=swap');
1545
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
1546
+ body {
1547
+ font-family: 'Manrope', system-ui, sans-serif;
1548
+ background: #09090b; color: #fafafa;
1549
+ padding: 2rem; max-width: 800px; margin: 0 auto;
1550
+ line-height: 1.6;
1551
+ }
1552
+ h1 { font-size: 1.5rem; font-weight: 700; margin-bottom: 0.25rem; }
1553
+ .subtitle { color: #a1a1aa; font-size: 0.85rem; margin-bottom: 1.5rem; }
1554
+ .card {
1555
+ background: rgba(255,255,255,0.02);
1556
+ border: 1px solid rgba(255,255,255,0.06);
1557
+ border-radius: 12px; padding: 1.25rem;
1558
+ margin-bottom: 1rem;
1559
+ }
1560
+ .card.emphasized { border-color: #d97757; }
1561
+ .card h2 { font-size: 0.95rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.12em; margin-bottom: 0.75rem; color: #e4e4e7; }
1562
+ .badge { background: #d97757; color: #09090b; font-size: 0.65rem; padding: 2px 6px; border-radius: 4px; vertical-align: middle; letter-spacing: 0.05em; }
1563
+ .card p { color: #d4d4d8; font-size: 0.9rem; margin-bottom: 0.5rem; }
1564
+ .card ul { list-style: none; padding: 0; }
1565
+ .card li { padding: 0.3rem 0; border-bottom: 1px solid rgba(255,255,255,0.04); font-size: 0.9rem; color: #d4d4d8; }
1566
+ .card li:last-child { border-bottom: none; }
1567
+ .card li strong { color: #fafafa; }
1568
+ .meta { color: #71717a; font-size: 0.8rem; }
1569
+ .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; }
1570
+ </style>
1571
+ </head>
1572
+ <body>
1573
+ <h1>${escapeHtml(config.headerPrefix + title)}</h1>
1574
+ <div class="subtitle">Prepared for ${escapeHtml(audience)} audience</div>
1575
+ ${body}
1576
+ ${metaHtml}
1577
+ </body>
1578
+ </html>`;
1579
+ return { format: "html", exported: html };
1580
+ }
1581
+ return { error: true, message: `Unhandled format: ${format}` };
1582
+ },
1583
+ },
668
1584
  ];
669
1585
  //# sourceMappingURL=founderTools.js.map