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.
- package/dist/benchmarks/dogfoodJudge.js +29 -29
- 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/founderTools.js +886 -1
- package/dist/tools/founderTools.js.map +1 -1
- package/dist/tools/reconTools.js +99 -0
- package/dist/tools/reconTools.js.map +1 -1
- package/dist/tools/toolRegistry.js +26 -0
- package/dist/tools/toolRegistry.js.map +1 -1
- package/dist/tools/webTools.js +1 -1
- package/dist/tools/webTools.js.map +1 -1
- package/package.json +1 -1
|
@@ -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.
|
|
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, "&")
|
|
1360
|
+
.replace(/</g, "<")
|
|
1361
|
+
.replace(/>/g, ">")
|
|
1362
|
+
.replace(/"/g, """);
|
|
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)} · NodeBench v${escapeHtml(NODEBENCH_VERSION)} · 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
|