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.
- package/dist/benchmarks/dogfoodJudge.js +37 -37
- package/dist/benchmarks/dogfoodJudge.js.map +1 -1
- package/dist/tools/deepSimTools.js +120 -2
- package/dist/tools/deepSimTools.js.map +1 -1
- package/dist/tools/dogfoodJudgeTools.js +213 -7
- package/dist/tools/dogfoodJudgeTools.js.map +1 -1
- package/dist/tools/founderTools.js +923 -7
- package/dist/tools/founderTools.js.map +1 -1
- package/dist/tools/founderTrackingTools.js +302 -0
- package/dist/tools/founderTrackingTools.js.map +1 -1
- package/dist/tools/reconTools.js +99 -0
- package/dist/tools/reconTools.js.map +1 -1
- package/dist/tools/toolRegistry.js +65 -0
- package/dist/tools/toolRegistry.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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, "&")
|
|
1465
|
+
.replace(/</g, "<")
|
|
1466
|
+
.replace(/>/g, ">")
|
|
1467
|
+
.replace(/"/g, """);
|
|
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)} · NodeBench v${escapeHtml(NODEBENCH_VERSION)} · 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
|