forgesmith 0.6.1 → 0.7.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/index.mjs CHANGED
@@ -4,12 +4,12 @@ import { randomUUID } from 'crypto';
4
4
  function buildSystemPrompt() {
5
5
  return `You are a technical writer and developer-relations expert. You generate clear, accurate release notes from structured code-intelligence data. Write only the release notes \u2014 no preamble, no meta-commentary.`;
6
6
  }
7
- function buildUserPrompt(data, opts) {
7
+ function buildUserPrompt(data, opts, amberContext, greenContext) {
8
8
  const tone = opts.tone ?? "professional";
9
9
  const length = opts.length ?? "medium";
10
10
  const format = opts.format ?? "markdown";
11
11
  const lengthGuide = { short: "2-3 paragraphs or bullet groups", medium: "4-6 sections", long: "comprehensive, 6+ sections with details" }[length];
12
- const toneGuide = { professional: "formal, clear, business-appropriate", casual: "friendly, approachable, conversational", technical: "precise, implementation-focused, developer-centric" }[tone];
12
+ const toneGuide = { professional: "formal, clear, business-appropriate", casual: "friendly, approachable, conversational", technical: "precise, implementation-focused, developer-centric", executive: "high-level, strategic, board-ready summary" }[tone] ?? "formal, clear, business-appropriate";
13
13
  const hasSessions = (data.sessions?.length ?? 0) > 0;
14
14
  const hasRecs = (data.recommendations?.length ?? 0) > 0;
15
15
  const hasInsights = (data.insights?.length ?? 0) > 0;
@@ -45,11 +45,38 @@ function buildUserPrompt(data, opts) {
45
45
  }
46
46
  lines.push(``);
47
47
  }
48
+ if (amberContext && amberContext.capabilities.length > 0) {
49
+ lines.push(`## Business Capabilities (AMBER layer)`);
50
+ lines.push(amberContext.summary);
51
+ const criticalCaps = amberContext.capabilities.filter(
52
+ (c) => c.criticality === "critical" || c.criticality === "high"
53
+ );
54
+ if (criticalCaps.length > 0) {
55
+ lines.push(
56
+ `This release may touch these capabilities: ${criticalCaps.map((c) => c.name).join(", ")}.`
57
+ );
58
+ }
59
+ lines.push(``);
60
+ }
61
+ if (greenContext) {
62
+ if (greenContext.coherenceScore !== null) {
63
+ lines.push(`## Architecture Health (GREEN layer)`);
64
+ lines.push(
65
+ `Coherence score: ${greenContext.coherenceScore}/100 (grade ${greenContext.coherenceGrade ?? "?"}).`
66
+ );
67
+ if (greenContext.topRisks.length > 0) {
68
+ lines.push(
69
+ `Top risks: ${greenContext.topRisks.slice(0, 3).map((r) => `${r.name} \u2014 ${r.reason}`).join("; ")}.`
70
+ );
71
+ }
72
+ lines.push(``);
73
+ }
74
+ }
48
75
  return lines.join("\n");
49
76
  }
50
- async function generateReleaseNotes(data, opts, provider) {
77
+ async function generateReleaseNotes(data, opts, provider, amberContext, greenContext) {
51
78
  const systemPrompt = buildSystemPrompt();
52
- const userPrompt = buildUserPrompt(data, opts);
79
+ const userPrompt = buildUserPrompt(data, opts, amberContext, greenContext);
53
80
  const response = await provider.complete({
54
81
  systemPrompt,
55
82
  messages: [{ role: "user", content: userPrompt }],
@@ -69,13 +96,16 @@ async function generateReleaseNotes(data, opts, provider) {
69
96
  var NO_DATA_MSG = "No Blueprint data available. Run prism scan first.";
70
97
  function buildSystemPrompt2(tone) {
71
98
  const toneMap = {
72
- technical: "You are a senior software architect who writes precise, technical documentation for engineering teams.",
99
+ professional: "You are a senior software architect who writes clear, professional documentation suitable for all engineering audiences.",
73
100
  casual: "You are a friendly developer advocate who explains codebases in an approachable, conversational style.",
101
+ technical: "You are a senior software architect who writes precise, technical documentation for engineering teams.",
102
+ executive: "You are a CTO-level advisor who explains codebase architecture at a high strategic level for leadership audiences.",
103
+ // Legacy aliases kept for backwards compatibility
74
104
  onboarding: "You are an experienced engineering mentor who helps new developers quickly understand a codebase."
75
105
  };
76
- return `${toneMap[tone] ?? toneMap.technical} Write only the requested document \u2014 no preamble, no meta-commentary.`;
106
+ return `${toneMap[tone] ?? toneMap.professional} Write only the requested document \u2014 no preamble, no meta-commentary.`;
77
107
  }
78
- function buildUserPrompt2(blueprint, opts) {
108
+ function buildUserPrompt2(blueprint, opts, amberContext) {
79
109
  const tone = opts.tone ?? "technical";
80
110
  const length = opts.length ?? "long";
81
111
  const format = opts.format ?? "markdown";
@@ -103,9 +133,28 @@ function buildUserPrompt2(blueprint, opts) {
103
133
  ``,
104
134
  `Cover: architecture overview, key layers/folders, entry points, important relationships, and what a new developer should understand first.`
105
135
  ];
136
+ if (amberContext && amberContext.capabilities.length > 0) {
137
+ lines.push(``);
138
+ lines.push(`## Business Capabilities (AMBER layer)`);
139
+ lines.push(amberContext.summary);
140
+ lines.push(``);
141
+ const sorted = amberContext.capabilities.slice().sort((a, b) => {
142
+ const rank = { critical: 0, high: 1, medium: 2, low: 3 };
143
+ return (rank[a.criticality] ?? 4) - (rank[b.criticality] ?? 4);
144
+ });
145
+ for (const cap of sorted) {
146
+ const tags = [cap.criticality, cap.lifecycle].filter(Boolean).join(", ");
147
+ const fileCount = cap.files.length;
148
+ lines.push(
149
+ `- **${cap.name}** (${cap.id}) [${tags}]${fileCount > 0 ? ` \u2014 ${fileCount} file${fileCount === 1 ? "" : "s"}` : ""}`
150
+ );
151
+ if (cap.description) lines.push(` ${cap.description}`);
152
+ }
153
+ lines.push(`Include a "Business Capabilities" section in the walkthrough mapping these capabilities to the codebase.`);
154
+ }
106
155
  return lines.join("\n");
107
156
  }
108
- async function generateArchitectureWalkthrough(blueprint, opts, provider) {
157
+ async function generateArchitectureWalkthrough(blueprint, opts, provider, amberContext) {
109
158
  if (!blueprint) {
110
159
  return {
111
160
  text: NO_DATA_MSG,
@@ -114,7 +163,7 @@ async function generateArchitectureWalkthrough(blueprint, opts, provider) {
114
163
  }
115
164
  const response = await provider.complete({
116
165
  systemPrompt: buildSystemPrompt2(opts.tone ?? "technical"),
117
- messages: [{ role: "user", content: buildUserPrompt2(blueprint, opts) }],
166
+ messages: [{ role: "user", content: buildUserPrompt2(blueprint, opts, amberContext) }],
118
167
  maxTokens: 4096
119
168
  });
120
169
  return {
@@ -189,11 +238,15 @@ async function generateChangesSince(current, previous, opts, provider) {
189
238
  var NO_DATA_MSG3 = "No Blueprint data available. Run prism scan first.";
190
239
  function buildSystemPrompt4(tone) {
191
240
  const toneMap = {
241
+ professional: "You are a documentation engineer writing a structured, professional onboarding reference for engineering teams.",
242
+ casual: "You are a helpful senior developer writing a welcoming onboarding guide for new team members. Be warm, encouraging, and practical.",
243
+ technical: "You are a senior engineer writing a technical onboarding document focused on implementation details and system internals.",
244
+ executive: "You are a technical lead writing a concise onboarding overview suitable for both technical and leadership audiences.",
245
+ // Legacy aliases kept for backwards compatibility
192
246
  friendly: "You are a helpful senior developer writing a welcoming onboarding guide for new team members. Be warm, encouraging, and practical.",
193
- formal: "You are a documentation engineer writing a structured onboarding reference for enterprise engineering teams.",
194
- technical: "You are a senior engineer writing a technical onboarding document focused on implementation details and system internals."
247
+ formal: "You are a documentation engineer writing a structured onboarding reference for enterprise engineering teams."
195
248
  };
196
- return `${toneMap[tone] ?? toneMap.friendly} Write only the document \u2014 no preamble, no meta-commentary.`;
249
+ return `${toneMap[tone] ?? toneMap.professional} Write only the document \u2014 no preamble, no meta-commentary.`;
197
250
  }
198
251
  function buildUserPrompt4(blueprint, opts) {
199
252
  const tone = opts.tone ?? "friendly";
@@ -254,11 +307,14 @@ async function generateOnboardingDoc(blueprint, opts, provider) {
254
307
  var NO_DATA_MSG4 = "No Blueprint data available. Run prism scan first.";
255
308
  function buildSystemPrompt5(tone) {
256
309
  const toneMap = {
257
- analytical: "You are a software architecture consultant who produces rigorous, evidence-based refactoring reports with clear prioritization.",
310
+ professional: "You are a software architecture consultant who produces rigorous, evidence-based refactoring reports with clear prioritization.",
258
311
  casual: "You are a senior developer who gives honest, practical refactoring advice based on code structure data.",
259
- executive: "You are a CTO-level advisor who summarizes architectural debt and refactoring priorities for engineering leadership."
312
+ technical: "You are a senior engineer who produces detailed, implementation-focused refactoring analysis with concrete code-level recommendations.",
313
+ executive: "You are a CTO-level advisor who summarizes architectural debt and refactoring priorities for engineering leadership.",
314
+ // Legacy aliases kept for backwards compatibility
315
+ analytical: "You are a software architecture consultant who produces rigorous, evidence-based refactoring reports with clear prioritization."
260
316
  };
261
- return `${toneMap[tone] ?? toneMap.analytical} Write only the report \u2014 no preamble, no meta-commentary.`;
317
+ return `${toneMap[tone] ?? toneMap.professional} Write only the report \u2014 no preamble, no meta-commentary.`;
262
318
  }
263
319
  function detectImportCycles(edges) {
264
320
  const graph = /* @__PURE__ */ new Map();
@@ -266,158 +322,2524 @@ function detectImportCycles(edges) {
266
322
  if (!graph.has(e.from)) graph.set(e.from, /* @__PURE__ */ new Set());
267
323
  graph.get(e.from).add(e.to);
268
324
  }
269
- const cycles = [];
270
- const visited = /* @__PURE__ */ new Set();
271
- const stack = /* @__PURE__ */ new Set();
272
- function dfs(node, path) {
273
- if (cycles.length >= 5) return;
274
- if (stack.has(node)) {
275
- const cycleStart = path.indexOf(node);
276
- if (cycleStart !== -1) {
277
- cycles.push(path.slice(cycleStart).join(" \u2192 ") + " \u2192 " + node);
325
+ const cycles = [];
326
+ const visited = /* @__PURE__ */ new Set();
327
+ const stack = /* @__PURE__ */ new Set();
328
+ function dfs(node, path) {
329
+ if (cycles.length >= 5) return;
330
+ if (stack.has(node)) {
331
+ const cycleStart = path.indexOf(node);
332
+ if (cycleStart !== -1) {
333
+ cycles.push(path.slice(cycleStart).join(" \u2192 ") + " \u2192 " + node);
334
+ }
335
+ return;
336
+ }
337
+ if (visited.has(node)) return;
338
+ visited.add(node);
339
+ stack.add(node);
340
+ for (const neighbor of graph.get(node) ?? []) {
341
+ dfs(neighbor, [...path, node]);
342
+ }
343
+ stack.delete(node);
344
+ }
345
+ for (const node of graph.keys()) {
346
+ if (!visited.has(node)) dfs(node, []);
347
+ }
348
+ return cycles;
349
+ }
350
+ function buildUserPrompt5(blueprint, opts, amberContext, greenContext) {
351
+ const tone = opts.tone ?? "analytical";
352
+ const length = opts.length ?? "medium";
353
+ const format = opts.format ?? "markdown";
354
+ const hotFiles = blueprint.files.slice().sort((a, b) => (b.importedByCount ?? 0) + (b.importCount ?? 0) - ((a.importedByCount ?? 0) + (a.importCount ?? 0))).slice(0, 10);
355
+ const highFanOut = blueprint.files.slice().sort((a, b) => (b.importCount ?? 0) - (a.importCount ?? 0)).slice(0, 8);
356
+ const importCycles = detectImportCycles(blueprint.edges);
357
+ const lines = [
358
+ `Generate a refactoring report in ${format} format for the codebase at "${blueprint.targetPath}".`,
359
+ `Tone: ${tone}. Length: ${length}.`,
360
+ ``,
361
+ `## Architecture Metrics`,
362
+ `- Total files: ${blueprint.stats.totalFiles}`,
363
+ `- Dependency edges: ${blueprint.stats.runtimeEdges}`,
364
+ `- Categories: ${JSON.stringify(blueprint.categories)}`,
365
+ ``,
366
+ `## High-Coupling Hot Spots (high total connections)`,
367
+ ...hotFiles.map((f) => `- ${f.path} \u2014 in: ${f.importedByCount ?? 0}, out: ${f.importCount ?? 0}, lines: ${f.lineCount ?? "?"}`),
368
+ ``,
369
+ `## High Fan-Out Files (many outgoing dependencies)`,
370
+ ...highFanOut.map((f) => `- ${f.path} \u2014 imports: ${f.importCount ?? 0}`),
371
+ ``,
372
+ `## Import Cycles Detected (${importCycles.length})`,
373
+ ...importCycles.length > 0 ? importCycles.map((c) => `- ${c}`) : ["No cycles detected in sampled edges"],
374
+ ``,
375
+ `Identify refactoring priorities: coupling issues, over-large files, circular dependencies, layer violations. Suggest concrete refactoring actions with rationale.`
376
+ ];
377
+ if (amberContext && amberContext.capabilities.length > 0) {
378
+ lines.push(``);
379
+ lines.push(`## Business Capabilities with Documentation Drift (AMBER layer)`);
380
+ lines.push(amberContext.summary);
381
+ const drifted = amberContext.capabilities.filter((c) => c.driftCount > 0);
382
+ if (drifted.length > 0) {
383
+ lines.push(`Capabilities with drift (files changed without updating @amber-doc):`);
384
+ for (const cap of drifted) {
385
+ lines.push(
386
+ `- **${cap.name}** (${cap.id}, ${cap.criticality}): ${cap.driftCount} file${cap.driftCount === 1 ? "" : "s"} drifted`
387
+ );
388
+ }
389
+ }
390
+ }
391
+ if (greenContext) {
392
+ lines.push(``);
393
+ lines.push(`## Cross-Layer Risks (GREEN layer)`);
394
+ lines.push(greenContext.summary);
395
+ if (greenContext.topRisks.length > 0) {
396
+ lines.push(`Top architectural risks:`);
397
+ for (const risk of greenContext.topRisks) {
398
+ lines.push(
399
+ `- **${risk.name}** (impact: ${risk.impact}): ${risk.reason}`
400
+ );
401
+ }
402
+ }
403
+ if (greenContext.insights.length > 0) {
404
+ lines.push(`Cross-layer insights:`);
405
+ for (const insight of greenContext.insights.slice(0, 5)) {
406
+ lines.push(`- **${insight.title}**: ${insight.body}`);
407
+ }
408
+ }
409
+ }
410
+ return lines.join("\n");
411
+ }
412
+ async function generateRefactoringReport(blueprint, opts, provider, amberContext, greenContext) {
413
+ if (!blueprint) {
414
+ return {
415
+ text: NO_DATA_MSG4,
416
+ metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), usedTokens: 0, generator: "forgesmith" }
417
+ };
418
+ }
419
+ const response = await provider.complete({
420
+ systemPrompt: buildSystemPrompt5(opts.tone ?? "analytical"),
421
+ messages: [{ role: "user", content: buildUserPrompt5(blueprint, opts, amberContext, greenContext) }],
422
+ maxTokens: 3072
423
+ });
424
+ return {
425
+ text: response.content,
426
+ metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), usedTokens: response.usedTokens, generator: "forgesmith" }
427
+ };
428
+ }
429
+
430
+ // src/generators/askDrivenAsset.ts
431
+ var NO_DATA_MSG5 = "No Blueprint data available. Run prism scan first.";
432
+ var FORMAT_GUIDES = {
433
+ markdown: { name: "Markdown document", structure: "Use headers, bullet lists, and code blocks where appropriate.", maxTokens: 3072 },
434
+ blog: { name: "blog post", structure: "Write with an engaging intro, clear sections, a conclusion, and a call-to-action.", maxTokens: 3072 },
435
+ social: { name: "social media post", structure: "Write concise, punchy content suitable for Twitter/LinkedIn. Max 280 characters for Twitter mode.", maxTokens: 512 },
436
+ email: { name: "email", structure: "Use Subject:, greeting, body paragraphs, and a sign-off.", maxTokens: 1024 },
437
+ slack: { name: "Slack message", structure: "Keep it conversational, use *bold* for emphasis, bullet points for lists. Max 3 paragraphs.", maxTokens: 512 },
438
+ slide: { name: "presentation outline", structure: "Structure as slide titles with 3-5 bullet points each. Include a title slide and summary slide.", maxTokens: 2048 }
439
+ };
440
+ function buildSystemPrompt6(format, tone) {
441
+ const toneMap = {
442
+ professional: "You are a professional technical writer and developer advocate.",
443
+ casual: "You are a friendly engineering blogger who writes in an approachable, conversational style.",
444
+ technical: "You are a senior software engineer writing precise, implementation-focused content.",
445
+ executive: "You are a VP of Engineering writing high-level, business-value-focused content for leadership."
446
+ };
447
+ const guide = FORMAT_GUIDES[format];
448
+ return `${toneMap[tone] ?? toneMap.professional} Generate a ${guide.name} based on the user's question and the provided codebase architecture context. ${guide.structure} Write only the requested content \u2014 no preamble, no meta-commentary.`;
449
+ }
450
+ function buildUserPrompt6(blueprint, question, opts) {
451
+ const format = opts.format ?? "markdown";
452
+ const tone = opts.tone ?? "professional";
453
+ const length = opts.length ?? "medium";
454
+ const guide = FORMAT_GUIDES[format];
455
+ const lengthGuide = {
456
+ short: "Keep it concise \u2014 1-2 paragraphs or equivalent.",
457
+ medium: "Medium length \u2014 3-5 paragraphs or equivalent.",
458
+ long: "Detailed and comprehensive \u2014 cover the topic thoroughly."
459
+ }[length];
460
+ const topFiles = blueprint.files.slice().sort((a, b) => (b.importedByCount ?? 0) - (a.importedByCount ?? 0)).slice(0, 12);
461
+ const lines = [
462
+ `## User's Question`,
463
+ question,
464
+ ``,
465
+ `## Output Requirements`,
466
+ `- Format: ${guide.name}`,
467
+ `- Tone: ${tone}`,
468
+ `- Length: ${length} (${lengthGuide})`,
469
+ ``,
470
+ `## Codebase Architecture Context`,
471
+ `Target: ${blueprint.targetPath}`,
472
+ `Total files: ${blueprint.stats.totalFiles} | Dependency edges: ${blueprint.stats.runtimeEdges}`,
473
+ `Categories: app=${blueprint.categories.app ?? 0}, components=${blueprint.categories.component ?? 0}, lib=${blueprint.categories.lib ?? 0}, hooks=${blueprint.categories.hook ?? 0}`,
474
+ ``,
475
+ `Key files (by usage):`,
476
+ ...topFiles.map((f) => `- ${f.path} [${f.category ?? "?"}] \u2014 imported by ${f.importedByCount ?? 0} files, ${f.lineCount ?? "?"} lines`)
477
+ ];
478
+ if (blueprint.edges.length > 0) {
479
+ const edgeSample = blueprint.edges.slice(0, 15);
480
+ lines.push(``, `Dependency edges (sample):`, ...edgeSample.map((e) => `- ${e.from} \u2192 ${e.to}`));
481
+ }
482
+ lines.push(``, `Answer the user's question using the architecture context above. Generate the ${guide.name} now.`);
483
+ return lines.join("\n");
484
+ }
485
+ async function generateAskDrivenAsset(blueprint, question, opts, provider) {
486
+ if (!blueprint) {
487
+ return {
488
+ text: NO_DATA_MSG5,
489
+ metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), usedTokens: 0, generator: "forgesmith" }
490
+ };
491
+ }
492
+ if (!question || !question.trim()) {
493
+ return {
494
+ text: "No question provided. Please ask something about your codebase.",
495
+ metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), usedTokens: 0, generator: "forgesmith" }
496
+ };
497
+ }
498
+ const format = opts.format ?? "markdown";
499
+ const tone = opts.tone ?? "professional";
500
+ const maxTokens = FORMAT_GUIDES[format].maxTokens;
501
+ const response = await provider.complete({
502
+ systemPrompt: buildSystemPrompt6(format, tone),
503
+ messages: [{ role: "user", content: buildUserPrompt6(blueprint, question, opts) }],
504
+ maxTokens
505
+ });
506
+ return {
507
+ text: response.content,
508
+ metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), usedTokens: response.usedTokens, generator: "forgesmith" }
509
+ };
510
+ }
511
+
512
+ // src/generators/generatePresentation.ts
513
+ function buildContext(blueprint, opts) {
514
+ const topFiles = blueprint.files.slice().sort((a, b) => (b.importedByCount ?? 0) - (a.importedByCount ?? 0)).slice(0, 10);
515
+ const projectName = opts.projectName || blueprint.targetPath.split("/").filter(Boolean).pop() || "Project";
516
+ const totalLoc = blueprint.files.reduce(
517
+ (sum, f) => sum + (f.lineCount ?? 0),
518
+ 0
519
+ );
520
+ const lines = [
521
+ `## Codebase Context`,
522
+ `Project: ${projectName}`,
523
+ `Path: ${blueprint.targetPath}`,
524
+ `Total files: ${blueprint.stats.totalFiles}`,
525
+ `Total lines of code: ${totalLoc}`,
526
+ `Dependency edges: ${blueprint.stats.runtimeEdges}`,
527
+ `Categories: app=${blueprint.categories.app ?? 0}, components=${blueprint.categories.component ?? 0}, lib=${blueprint.categories.lib ?? 0}, hooks=${blueprint.categories.hook ?? 0}`,
528
+ ``,
529
+ `## Most-imported files`,
530
+ ...topFiles.map(
531
+ (f) => `- ${f.path} [${f.category ?? "unknown"}] \u2014 importedBy: ${f.importedByCount ?? 0}, lines: ${f.lineCount ?? "?"}`
532
+ )
533
+ ];
534
+ if (blueprint.edges.length > 0) {
535
+ const edgeSample = blueprint.edges.slice(0, 10);
536
+ lines.push(
537
+ ``,
538
+ `## Dependency sample`,
539
+ ...edgeSample.map((e) => `- ${e.from} \u2192 ${e.to}${e.type ? ` (${e.type})` : ""}`)
540
+ );
541
+ }
542
+ return lines.join("\n");
543
+ }
544
+ function planSlideTypes(opts) {
545
+ const { slideCount, focusArea, audience } = opts;
546
+ const focus = focusArea ?? "all";
547
+ const always = ["title", "closing"];
548
+ let middle = [];
549
+ if (focus === "architecture") {
550
+ middle = ["executive", "architecture", "metrics", "risks", "recommendations"];
551
+ } else if (focus === "health") {
552
+ middle = ["executive", "health", "risks", "recommendations", "metrics"];
553
+ } else if (focus === "risks") {
554
+ middle = ["executive", "risks", "recommendations", "architecture", "health"];
555
+ } else {
556
+ if (audience === "executive") {
557
+ middle = ["executive", "health", "risks", "recommendations", "architecture", "metrics", "capabilities", "recommendations"];
558
+ } else if (audience === "technical") {
559
+ middle = ["architecture", "metrics", "risks", "health", "recommendations", "capabilities", "executive", "recommendations"];
560
+ } else {
561
+ middle = ["executive", "architecture", "health", "risks", "recommendations", "metrics", "capabilities", "executive"];
562
+ }
563
+ }
564
+ const targetMiddle = slideCount - 2;
565
+ const trimmed = middle.slice(0, targetMiddle);
566
+ const seen = /* @__PURE__ */ new Set();
567
+ const deduped = [];
568
+ for (const t of trimmed) {
569
+ if (!seen.has(t)) {
570
+ seen.add(t);
571
+ deduped.push(t);
572
+ }
573
+ }
574
+ return [always[0], ...deduped, always[1]];
575
+ }
576
+ function buildSystemPrompt7(opts) {
577
+ const toneMap = {
578
+ professional: "You are a senior engineering consultant who creates compelling, data-driven presentations for technical and business stakeholders.",
579
+ casual: "You are a developer advocate who creates friendly, approachable presentations that make architecture exciting.",
580
+ executive: "You are a VP of Engineering who creates concise, business-value-focused presentations for C-suite audiences."
581
+ };
582
+ return `${toneMap[opts.tone]} Generate ONLY valid JSON \u2014 no markdown fences, no prose, no commentary outside the JSON object.`;
583
+ }
584
+ function buildUserPrompt7(blueprint, opts, slideTypes) {
585
+ const projectName = opts.projectName || blueprint.targetPath.split("/").filter(Boolean).pop() || "Project";
586
+ const context = buildContext(blueprint, opts);
587
+ const slideTypeDescriptions = {
588
+ title: "Opening slide with project name, tagline, and date. Fields: title (project name), subtitle (tagline), speakerNotes",
589
+ executive: "Executive summary with 3 key findings. Fields: title, bullets (exactly 3 bullet strings), speakerNotes",
590
+ architecture: "Architecture overview. Fields: title, subtitle (layer summary), bullets (up to 5 key architecture facts), visualHint (describe a diagram), speakerNotes",
591
+ capabilities: "Capability map overview. Fields: title, bullets (up to 5 capability areas found), speakerNotes",
592
+ health: "Codebase health score. Fields: title, highlight (a letter grade A-F or numeric score), highlightLabel, bullets (up to 3 health indicators), speakerNotes",
593
+ risks: "Top risks. Fields: title, bullets (exactly 3 risks, each prefixed with High/Medium/Low impact), speakerNotes",
594
+ recommendations: "Action items. Fields: title, bullets (up to 5 concrete action items), speakerNotes",
595
+ metrics: "Key metrics. Fields: title, highlight (most important metric), highlightLabel, bullets (up to 5 metrics), speakerNotes",
596
+ closing: "Next steps and call to action. Fields: title, subtitle, bullets (up to 3 next steps), speakerNotes"
597
+ };
598
+ const slidePlan = slideTypes.map((t, i) => ` ${i + 1}. type="${t}" \u2014 ${slideTypeDescriptions[t]}`).join("\n");
599
+ return [
600
+ `Generate a ${opts.slideCount}-slide presentation deck for: ${projectName}`,
601
+ `Audience: ${opts.audience}. Tone: ${opts.tone}. Theme: ${opts.theme}.`,
602
+ ``,
603
+ context,
604
+ ``,
605
+ `## Slide Plan (${slideTypes.length} slides)`,
606
+ slidePlan,
607
+ ``,
608
+ `## Output Format`,
609
+ `Return a single JSON object with this exact shape:`,
610
+ `{`,
611
+ ` "title": "<deck title>",`,
612
+ ` "subtitle": "<deck subtitle>",`,
613
+ ` "slides": [`,
614
+ ` {`,
615
+ ` "type": "<one of the PresentationSlideType values>",`,
616
+ ` "title": "<slide title>",`,
617
+ ` "subtitle": "<optional subtitle>",`,
618
+ ` "bullets": ["<bullet 1>", "<bullet 2>"],`,
619
+ ` "highlight": "<optional large callout>",`,
620
+ ` "highlightLabel": "<optional label>",`,
621
+ ` "speakerNotes": "<presenter notes>",`,
622
+ ` "visualHint": "<optional visual description>"`,
623
+ ` }`,
624
+ ` ]`,
625
+ `}`,
626
+ ``,
627
+ `Rules:`,
628
+ `- Each slide in the output MUST match the type listed in the slide plan above.`,
629
+ `- bullets array: max 5 items, each \u2264 80 characters.`,
630
+ `- speakerNotes: 1-3 sentences a presenter would say.`,
631
+ `- Derive all content from the codebase context above \u2014 no invented facts.`,
632
+ `- Return ONLY valid JSON. No markdown. No prose outside the JSON.`
633
+ ].join("\n");
634
+ }
635
+ function buildFallback(blueprint, opts, slideTypes) {
636
+ const projectName = opts.projectName || blueprint.targetPath.split("/").filter(Boolean).pop() || "Project";
637
+ const totalLoc = blueprint.files.reduce(
638
+ (sum, f) => sum + (f.lineCount ?? 0),
639
+ 0
640
+ );
641
+ const slideBuilders = {
642
+ title: () => ({
643
+ type: "title",
644
+ title: projectName,
645
+ subtitle: "Architecture & Code Intelligence Report",
646
+ speakerNotes: `Welcome. Today we cover the architecture of ${projectName}.`
647
+ }),
648
+ executive: () => ({
649
+ type: "executive",
650
+ title: "Executive Summary",
651
+ bullets: [
652
+ `${blueprint.stats.totalFiles} files analyzed across all layers`,
653
+ `${blueprint.stats.runtimeEdges} dependency edges mapped`,
654
+ `Architecture categories: app, components, lib, hooks`
655
+ ],
656
+ speakerNotes: "Three key findings from the codebase scan."
657
+ }),
658
+ architecture: () => ({
659
+ type: "architecture",
660
+ title: "Architecture Overview",
661
+ subtitle: `${blueprint.stats.totalFiles} files, ${blueprint.stats.runtimeEdges} edges`,
662
+ bullets: [
663
+ `App layer: ${blueprint.categories.app ?? 0} files`,
664
+ `Components: ${blueprint.categories.component ?? 0} files`,
665
+ `Libraries: ${blueprint.categories.lib ?? 0} files`,
666
+ `Hooks: ${blueprint.categories.hook ?? 0} files`
667
+ ],
668
+ visualHint: "Layered architecture diagram with dependency arrows",
669
+ speakerNotes: "The codebase is organized into four primary layers."
670
+ }),
671
+ capabilities: () => ({
672
+ type: "capabilities",
673
+ title: "Capability Map",
674
+ bullets: ["Core application logic", "Component layer", "Shared utilities", "Custom hooks"],
675
+ speakerNotes: "These are the key capability areas identified in the scan."
676
+ }),
677
+ health: () => ({
678
+ type: "health",
679
+ title: "Codebase Health",
680
+ highlight: "B",
681
+ highlightLabel: "Overall Grade",
682
+ bullets: [
683
+ `${blueprint.stats.totalFiles} total files`,
684
+ `${blueprint.stats.runtimeEdges} dependency edges`,
685
+ `Avg LOC per file: ${blueprint.stats.totalFiles > 0 ? Math.round(totalLoc / blueprint.stats.totalFiles) : 0}`
686
+ ],
687
+ speakerNotes: "The codebase shows good structural health with opportunities for improvement."
688
+ }),
689
+ risks: () => ({
690
+ type: "risks",
691
+ title: "Top Risks",
692
+ bullets: [
693
+ "High impact: Large files with many incoming dependencies",
694
+ "Medium impact: Missing test coverage in critical paths",
695
+ "Low impact: Inconsistent category classification"
696
+ ],
697
+ speakerNotes: "Three risks identified, prioritized by impact."
698
+ }),
699
+ recommendations: () => ({
700
+ type: "recommendations",
701
+ title: "Recommendations",
702
+ bullets: [
703
+ "Break down files with high importedByCount into smaller modules",
704
+ "Establish clear boundaries between app, lib, and component layers",
705
+ "Add test coverage to files with the most incoming dependencies",
706
+ "Review and resolve circular dependency patterns",
707
+ "Document architecture decisions for new team members"
708
+ ],
709
+ speakerNotes: "Five concrete action items, ordered by priority."
710
+ }),
711
+ metrics: () => ({
712
+ type: "metrics",
713
+ title: "Key Metrics",
714
+ highlight: String(blueprint.stats.totalFiles),
715
+ highlightLabel: "Total Files",
716
+ bullets: [
717
+ `Lines of code: ${totalLoc.toLocaleString()}`,
718
+ `Dependency edges: ${blueprint.stats.runtimeEdges}`,
719
+ `App files: ${blueprint.categories.app ?? 0}`,
720
+ `Components: ${blueprint.categories.component ?? 0}`,
721
+ `Libraries: ${blueprint.categories.lib ?? 0}`
722
+ ],
723
+ speakerNotes: "Key quantitative metrics from the Blueprint scan."
724
+ }),
725
+ closing: () => ({
726
+ type: "closing",
727
+ title: "Next Steps",
728
+ subtitle: "Start with the highest-impact recommendations",
729
+ bullets: [
730
+ "Schedule architecture review with the team",
731
+ "Prioritize the top 3 recommendations",
732
+ "Set up regular prism0x2A scans"
733
+ ],
734
+ speakerNotes: "Thank you. Questions?"
735
+ })
736
+ };
737
+ const slides = slideTypes.map((t) => slideBuilders[t]());
738
+ return {
739
+ title: `${projectName} \u2014 Architecture Deck`,
740
+ subtitle: `${opts.audience.charAt(0).toUpperCase() + opts.audience.slice(1)} presentation \xB7 ${(/* @__PURE__ */ new Date()).toLocaleDateString()}`,
741
+ slideCount: slides.length,
742
+ slides,
743
+ theme: opts.theme,
744
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
745
+ };
746
+ }
747
+ function parseLlmResponse(raw, slideTypes, opts, blueprint) {
748
+ let text = raw.trim();
749
+ if (text.startsWith("```")) {
750
+ text = text.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
751
+ }
752
+ let parsed;
753
+ try {
754
+ parsed = JSON.parse(text);
755
+ } catch {
756
+ return null;
757
+ }
758
+ const rawSlides = Array.isArray(parsed.slides) ? parsed.slides : [];
759
+ if (rawSlides.length === 0) return null;
760
+ const slides = rawSlides.map((s, i) => {
761
+ const slide = s ?? {};
762
+ const inferredType = typeof slide.type === "string" && [
763
+ "title",
764
+ "executive",
765
+ "architecture",
766
+ "capabilities",
767
+ "health",
768
+ "risks",
769
+ "recommendations",
770
+ "metrics",
771
+ "closing"
772
+ ].includes(slide.type) ? slide.type : slideTypes[i] ?? "executive";
773
+ const bullets = Array.isArray(slide.bullets) ? slide.bullets.filter((b) => typeof b === "string").slice(0, 5) : void 0;
774
+ return {
775
+ type: inferredType,
776
+ title: typeof slide.title === "string" ? slide.title : `Slide ${i + 1}`,
777
+ ...typeof slide.subtitle === "string" && slide.subtitle ? { subtitle: slide.subtitle } : {},
778
+ ...bullets && bullets.length > 0 ? { bullets } : {},
779
+ ...typeof slide.highlight === "string" && slide.highlight ? { highlight: slide.highlight } : {},
780
+ ...typeof slide.highlightLabel === "string" && slide.highlightLabel ? { highlightLabel: slide.highlightLabel } : {},
781
+ ...typeof slide.speakerNotes === "string" && slide.speakerNotes ? { speakerNotes: slide.speakerNotes } : {},
782
+ ...typeof slide.visualHint === "string" && slide.visualHint ? { visualHint: slide.visualHint } : {}
783
+ };
784
+ });
785
+ const projectName = opts.projectName || blueprint.targetPath.split("/").filter(Boolean).pop() || "Project";
786
+ return {
787
+ title: typeof parsed.title === "string" && parsed.title ? parsed.title : `${projectName} \u2014 Architecture Deck`,
788
+ subtitle: typeof parsed.subtitle === "string" && parsed.subtitle ? parsed.subtitle : `${opts.audience} presentation`,
789
+ slideCount: slides.length,
790
+ slides,
791
+ theme: opts.theme,
792
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
793
+ };
794
+ }
795
+ async function generatePresentation(opts) {
796
+ const { blueprint, llm } = opts;
797
+ const slideTypes = planSlideTypes(opts);
798
+ try {
799
+ const response = await llm.complete({
800
+ systemPrompt: buildSystemPrompt7(opts),
801
+ messages: [
802
+ {
803
+ role: "user",
804
+ content: buildUserPrompt7(blueprint, opts, slideTypes)
805
+ }
806
+ ],
807
+ maxTokens: 4096
808
+ });
809
+ const parsed = parseLlmResponse(response.content, slideTypes, opts, blueprint);
810
+ if (parsed) return parsed;
811
+ } catch {
812
+ }
813
+ return buildFallback(blueprint, opts, slideTypes);
814
+ }
815
+
816
+ // src/generators/generateComplianceDoc.ts
817
+ var FRAMEWORK_SPECS = {
818
+ SOX: {
819
+ fullName: "Sarbanes-Oxley Act (SOX)",
820
+ focusAreas: [
821
+ "Internal controls over financial reporting (ICFR)",
822
+ "Access control and segregation of duties",
823
+ "Change management procedures",
824
+ "Audit trail completeness",
825
+ "IT general controls (ITGCs)"
826
+ ],
827
+ requiredSections: [
828
+ "Scope and System Boundaries",
829
+ "IT General Controls",
830
+ "Access Control and Segregation of Duties",
831
+ "Change Management",
832
+ "Audit Trail and Logging",
833
+ "Financial Reporting Controls"
834
+ ],
835
+ sectionGuidance: "Focus on Section 302 (CEO/CFO certification) and Section 404 (management assessment of internal controls). Identify which capabilities handle financial data, who can access them, and whether audit trails are complete."
836
+ },
837
+ ISO27001: {
838
+ fullName: "ISO/IEC 27001:2022 Information Security Management",
839
+ focusAreas: [
840
+ "Information security policies (Clause 5)",
841
+ "Risk assessment and treatment (Clause 6)",
842
+ "Information assets classification",
843
+ "Access control (Annex A 5.15\u20135.18)",
844
+ "Cryptography (Annex A 8.24)",
845
+ "Supplier relationships (Annex A 5.19\u20135.22)"
846
+ ],
847
+ requiredSections: [
848
+ "Scope and Context",
849
+ "Information Assets",
850
+ "Risk Assessment",
851
+ "Access Control",
852
+ "Cryptography and Data Protection",
853
+ "Incident Management",
854
+ "Business Continuity"
855
+ ],
856
+ sectionGuidance: "Structure the document around the ISO 27001:2022 Annex A controls. Map each capability to the relevant controls and identify statement of applicability (SoA) status."
857
+ },
858
+ GDPR: {
859
+ fullName: "General Data Protection Regulation (GDPR)",
860
+ focusAreas: [
861
+ "Article 30 Records of Processing Activities (ROPA)",
862
+ "Lawful basis for processing",
863
+ "Data subject rights (Articles 15\u201322)",
864
+ "Data retention and deletion",
865
+ "Data transfers (Chapter V)",
866
+ "Privacy by design and default (Article 25)"
867
+ ],
868
+ requiredSections: [
869
+ "Records of Processing Activities (ROPA)",
870
+ "Lawful Basis for Processing",
871
+ "Data Subject Rights",
872
+ "Data Retention Policy",
873
+ "International Data Transfers",
874
+ "Privacy by Design",
875
+ "Data Breach Response"
876
+ ],
877
+ sectionGuidance: "Structure around Article 30 ROPA format. For each capability processing personal data: identify purpose, lawful basis, categories of data, retention period, and third-party processors."
878
+ },
879
+ SOC2: {
880
+ fullName: "SOC 2 Type II (Trust Services Criteria)",
881
+ focusAreas: [
882
+ "Security (Common Criteria \u2014 CC)",
883
+ "Availability (Availability Criteria \u2014 A)",
884
+ "Confidentiality (Confidentiality Criteria \u2014 C)",
885
+ "Processing Integrity (PI)",
886
+ "Privacy (P)"
887
+ ],
888
+ requiredSections: [
889
+ "System Description",
890
+ "Security \u2014 Common Criteria",
891
+ "Availability",
892
+ "Confidentiality",
893
+ "Change Management",
894
+ "Monitoring",
895
+ "Vendor Management"
896
+ ],
897
+ sectionGuidance: "Follow the AICPA Trust Services Criteria. For each section, identify relevant capabilities, current controls, and any gaps versus the criteria."
898
+ },
899
+ CUSTOM: {
900
+ fullName: "Custom Compliance Framework",
901
+ focusAreas: [
902
+ "Scope and applicability",
903
+ "Control requirements",
904
+ "Risk assessment",
905
+ "Monitoring and audit"
906
+ ],
907
+ requiredSections: [
908
+ "Scope",
909
+ "Controls Inventory",
910
+ "Risk Assessment",
911
+ "Gaps and Recommendations"
912
+ ],
913
+ sectionGuidance: "Generate a structured compliance document based on the codebase capabilities and general best practices."
914
+ }
915
+ };
916
+ function buildCodebaseContext(blueprint, opts) {
917
+ const lines = [
918
+ `## Codebase Overview`,
919
+ `Project: ${opts.projectName}`,
920
+ `Total files: ${blueprint.stats.totalFiles}`,
921
+ `Dependency edges: ${blueprint.stats.runtimeEdges}`
922
+ ];
923
+ const topFiles = blueprint.files.slice().sort((a, b) => (b.importedByCount ?? 0) - (a.importedByCount ?? 0)).slice(0, 8);
924
+ lines.push(``, `## Most-imported files (likely critical components)`);
925
+ for (const f of topFiles) {
926
+ lines.push(`- ${f.path} [${f.category ?? "unknown"}] \u2014 imported by ${f.importedByCount ?? 0} modules`);
927
+ }
928
+ return lines.join("\n");
929
+ }
930
+ function buildAmberContext(amber) {
931
+ const lines = [`## AMBER Capability Registry`, amber.summary, ``];
932
+ for (const cap of amber.capabilities.slice(0, 20)) {
933
+ const tags = [cap.criticality, cap.lifecycle];
934
+ if (cap.driftCount > 0) tags.push(`drift:${cap.driftCount}`);
935
+ if (cap.hasTests) tags.push("documented");
936
+ lines.push(
937
+ `- ${cap.name} (${cap.id}) [${tags.join(", ")}]` + (cap.description ? `: ${cap.description}` : "")
938
+ );
939
+ }
940
+ return lines.join("\n");
941
+ }
942
+ function buildComplianceContext(ctx, framework) {
943
+ const lines = [
944
+ `## PRISM Compliance Analysis`,
945
+ `Framework: ${ctx.framework}`,
946
+ `Total capabilities: ${ctx.totalCapabilities}`,
947
+ `Risk summary \u2014 High: ${ctx.riskSummary.high}, Medium: ${ctx.riskSummary.medium}, Low: ${ctx.riskSummary.low}`,
948
+ ``,
949
+ `### Critical / High Capabilities`
950
+ ];
951
+ for (const cap of ctx.criticalCapabilities.slice(0, 15)) {
952
+ const tags = [cap.criticality];
953
+ if (cap.accessControl) tags.push("access-control");
954
+ if (cap.auditTrail) tags.push("audit-trail");
955
+ if (cap.dataProcessing) tags.push("data-processing");
956
+ if (!cap.testCoverage) tags.push("no-doc");
957
+ if (cap.driftCount > 0) tags.push(`drift:${cap.driftCount}`);
958
+ lines.push(`- ${cap.name} (${cap.id}) [${tags.join(", ")}]`);
959
+ }
960
+ if (framework === "SOX" && ctx.sox) {
961
+ lines.push(``, `### SOX: Financial capabilities: ${ctx.sox.financialCapabilities.join(", ") || "none identified"}`);
962
+ lines.push(`SOX: Access-controlled: ${ctx.sox.accessControlled.join(", ") || "none identified"}`);
963
+ }
964
+ if (framework === "GDPR" && ctx.gdpr) {
965
+ lines.push(``, `### GDPR: Data processing capabilities: ${ctx.gdpr.dataProcessingCapabilities.join(", ") || "none identified"}`);
966
+ lines.push(`GDPR: Retention capabilities: ${ctx.gdpr.retentionCapabilities.join(", ") || "none identified"}`);
967
+ }
968
+ if (framework === "ISO27001" && ctx.iso27001) {
969
+ lines.push(``, `### ISO27001: Information assets: ${ctx.iso27001.informationAssets.join(", ") || "none identified"}`);
970
+ lines.push(`ISO27001: Security controls: ${ctx.iso27001.securityControls.join(", ") || "none identified"}`);
971
+ }
972
+ if (framework === "SOC2" && ctx.soc2) {
973
+ lines.push(``, `### SOC2: Security capabilities: ${ctx.soc2.securityCapabilities.join(", ") || "none identified"}`);
974
+ lines.push(`SOC2: Availability capabilities: ${ctx.soc2.availabilityCapabilities.join(", ") || "none identified"}`);
975
+ }
976
+ return lines.join("\n");
977
+ }
978
+ function buildSystemPrompt8(opts) {
979
+ const toneMap = {
980
+ formal: "You are a senior compliance officer writing a formal regulatory compliance document for external auditors and regulators.",
981
+ technical: "You are a security-focused software architect writing a technical compliance assessment grounded in code-level evidence.",
982
+ executive: "You are a Chief Compliance Officer writing a concise executive summary compliance report for board-level review."
983
+ };
984
+ return `${toneMap[opts.tone]} Generate ONLY valid JSON \u2014 no markdown fences, no prose, no commentary outside the JSON object.`;
985
+ }
986
+ function buildUserPrompt8(opts) {
987
+ const spec = FRAMEWORK_SPECS[opts.framework] ?? FRAMEWORK_SPECS["CUSTOM"];
988
+ const frameworkName = opts.framework === "CUSTOM" ? opts.customFramework ?? "Custom Framework" : spec.fullName;
989
+ const contextParts = [
990
+ buildCodebaseContext(opts.blueprint, opts)
991
+ ];
992
+ if (opts.amberContext) contextParts.push(buildAmberContext(opts.amberContext));
993
+ if (opts.complianceContext) contextParts.push(buildComplianceContext(opts.complianceContext, opts.framework));
994
+ const org = opts.organizationName ? `Organization: ${opts.organizationName}` : "";
995
+ const recsInstruction = opts.includeRecommendations ? 'Include a "recommendations" array with 3\u20137 actionable remediation items.' : 'Set "recommendations" to an empty array.';
996
+ return [
997
+ `Generate a ${frameworkName} compliance document for: ${opts.projectName}`,
998
+ org,
999
+ `Tone: ${opts.tone}`,
1000
+ ``,
1001
+ contextParts.join("\n\n"),
1002
+ ``,
1003
+ `## Framework: ${frameworkName}`,
1004
+ `Focus areas:`,
1005
+ spec.focusAreas.map((f) => `- ${f}`).join("\n"),
1006
+ ``,
1007
+ spec.sectionGuidance,
1008
+ ``,
1009
+ `Required sections: ${spec.requiredSections.join(", ")}`,
1010
+ ``,
1011
+ `## Output Format`,
1012
+ `Return a single JSON object with this exact shape:`,
1013
+ `{`,
1014
+ ` "documentTitle": "<formal document title>",`,
1015
+ ` "executiveSummary": "<3\u20135 sentences summarising compliance posture>",`,
1016
+ ` "confidentiality": "INTERNAL" | "CONFIDENTIAL" | "PUBLIC",`,
1017
+ ` "complianceGaps": ["<gap 1>", "<gap 2>"],`,
1018
+ ` "recommendations": ["<rec 1>", "<rec 2>"],`,
1019
+ ` "sections": [`,
1020
+ ` {`,
1021
+ ` "title": "<section title>",`,
1022
+ ` "content": "<substantive section prose, 2\u20134 paragraphs>",`,
1023
+ ` "capabilities": ["<capability id 1>"],`,
1024
+ ` "riskLevel": "high" | "medium" | "low",`,
1025
+ ` "status": "compliant" | "partial" | "gap"`,
1026
+ ` }`,
1027
+ ` ]`,
1028
+ `}`,
1029
+ ``,
1030
+ `Rules:`,
1031
+ `- Derive ALL content from the codebase context above \u2014 no invented facts.`,
1032
+ `- Each section MUST reference real capability IDs from the AMBER registry where available.`,
1033
+ `- complianceGaps: list specific gaps found (missing controls, undocumented capabilities, drift).`,
1034
+ recsInstruction,
1035
+ `- confidentiality: CONFIDENTIAL for SOX/ISO27001/SOC2, INTERNAL for GDPR/CUSTOM.`,
1036
+ `- Return ONLY valid JSON. No markdown. No prose outside the JSON.`
1037
+ ].filter(Boolean).join("\n");
1038
+ }
1039
+ function buildFallback2(opts) {
1040
+ const spec = FRAMEWORK_SPECS[opts.framework] ?? FRAMEWORK_SPECS["CUSTOM"];
1041
+ const frameworkName = opts.framework === "CUSTOM" ? opts.customFramework ?? "Custom Framework" : spec.fullName;
1042
+ const totalCaps = opts.complianceContext?.totalCapabilities ?? opts.amberContext?.capabilities.length ?? 0;
1043
+ const highRisk = opts.complianceContext?.riskSummary.high ?? 0;
1044
+ const sections = spec.requiredSections.map((title) => ({
1045
+ title,
1046
+ content: `Assessment of ${title.toLowerCase()} for ${opts.projectName}. Based on Blueprint analysis, ${totalCaps} capabilities were identified for review under ${frameworkName}.`,
1047
+ capabilities: opts.amberContext?.capabilities.slice(0, 2).map((c) => c.id) ?? [],
1048
+ riskLevel: "medium",
1049
+ status: "partial"
1050
+ }));
1051
+ return {
1052
+ framework: opts.framework,
1053
+ documentTitle: `${opts.projectName} \u2014 ${frameworkName} Compliance Assessment`,
1054
+ executiveSummary: `This document presents the ${frameworkName} compliance assessment for ${opts.projectName}. Analysis identified ${totalCaps} business capabilities, of which ${highRisk} present elevated risk requiring remediation. The assessment covers key control domains and provides a structured gap analysis.`,
1055
+ sections,
1056
+ complianceGaps: [
1057
+ `${totalCaps - (opts.amberContext?.taggedFiles ?? 0)} files lack capability annotations \u2014 compliance traceability is incomplete.`,
1058
+ `${opts.amberContext?.driftedCapabilities ?? 0} capabilities have documentation drift \u2014 controls may be misrepresented.`
1059
+ ],
1060
+ recommendations: opts.includeRecommendations ? [
1061
+ "Ensure all critical capabilities have up-to-date @amber-doc annotations.",
1062
+ "Implement automated compliance scanning in CI/CD pipeline.",
1063
+ "Conduct quarterly capability review against framework requirements."
1064
+ ] : [],
1065
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1066
+ confidentiality: opts.framework === "GDPR" || opts.framework === "CUSTOM" ? "INTERNAL" : "CONFIDENTIAL"
1067
+ };
1068
+ }
1069
+ function parseLlmResponse2(raw, opts) {
1070
+ let text = raw.trim();
1071
+ if (text.startsWith("```")) {
1072
+ text = text.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
1073
+ }
1074
+ let parsed;
1075
+ try {
1076
+ parsed = JSON.parse(text);
1077
+ } catch {
1078
+ return null;
1079
+ }
1080
+ const rawSections = Array.isArray(parsed.sections) ? parsed.sections : [];
1081
+ if (rawSections.length === 0) return null;
1082
+ const sections = rawSections.map((s) => {
1083
+ const sec = s ?? {};
1084
+ return {
1085
+ title: typeof sec.title === "string" ? sec.title : "Section",
1086
+ content: typeof sec.content === "string" ? sec.content : "",
1087
+ capabilities: Array.isArray(sec.capabilities) ? sec.capabilities.filter((c) => typeof c === "string") : [],
1088
+ ...typeof sec.riskLevel === "string" && ["high", "medium", "low"].includes(sec.riskLevel) ? { riskLevel: sec.riskLevel } : {},
1089
+ ...typeof sec.status === "string" && ["compliant", "partial", "gap"].includes(sec.status) ? { status: sec.status } : {}
1090
+ };
1091
+ });
1092
+ const confidentiality = parsed.confidentiality === "CONFIDENTIAL" ? "CONFIDENTIAL" : parsed.confidentiality === "PUBLIC" ? "PUBLIC" : "INTERNAL";
1093
+ return {
1094
+ framework: opts.framework,
1095
+ documentTitle: typeof parsed.documentTitle === "string" ? parsed.documentTitle : `${opts.projectName} \u2014 Compliance Assessment`,
1096
+ sections,
1097
+ executiveSummary: typeof parsed.executiveSummary === "string" ? parsed.executiveSummary : "",
1098
+ complianceGaps: Array.isArray(parsed.complianceGaps) ? parsed.complianceGaps.filter((g) => typeof g === "string") : [],
1099
+ recommendations: Array.isArray(parsed.recommendations) ? parsed.recommendations.filter((r) => typeof r === "string") : [],
1100
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1101
+ confidentiality
1102
+ };
1103
+ }
1104
+ async function generateComplianceDoc(opts) {
1105
+ const systemPrompt = buildSystemPrompt8(opts);
1106
+ const userPrompt = buildUserPrompt8(opts);
1107
+ try {
1108
+ const response = await opts.llm.complete({
1109
+ systemPrompt,
1110
+ messages: [{ role: "user", content: userPrompt }],
1111
+ maxTokens: 6e3
1112
+ });
1113
+ const parsed = parseLlmResponse2(response.content, opts);
1114
+ if (parsed) return parsed;
1115
+ } catch {
1116
+ }
1117
+ return buildFallback2(opts);
1118
+ }
1119
+
1120
+ // src/generators/generateADR.ts
1121
+ function buildCodebaseContext2(blueprint, projectName) {
1122
+ const topFiles = blueprint.files.slice().sort((a, b) => (b.importedByCount ?? 0) - (a.importedByCount ?? 0)).slice(0, 8);
1123
+ const totalLoc = blueprint.files.reduce((sum, f) => sum + (f.lineCount ?? 0), 0);
1124
+ const lines = [
1125
+ `## Codebase Overview`,
1126
+ `Project: ${projectName}`,
1127
+ `Total files: ${blueprint.stats.totalFiles}`,
1128
+ `Total LOC: ${totalLoc.toLocaleString()}`,
1129
+ `Dependency edges: ${blueprint.stats.runtimeEdges}`,
1130
+ ``,
1131
+ `## Most-imported files (architectural hotspots)`
1132
+ ];
1133
+ for (const f of topFiles) {
1134
+ lines.push(`- ${f.path} [${f.category ?? "?"}] \u2014 imported by ${f.importedByCount ?? 0} modules, ${f.lineCount ?? 0} lines`);
1135
+ }
1136
+ if (blueprint.edges.length > 0) {
1137
+ const edgeSample = blueprint.edges.slice(0, 8);
1138
+ lines.push(``, `## Dependency sample`);
1139
+ for (const e of edgeSample) {
1140
+ lines.push(`- ${e.from} \u2192 ${e.to}${e.type ? ` (${e.type})` : ""}`);
1141
+ }
1142
+ }
1143
+ return lines.join("\n");
1144
+ }
1145
+ function buildAdrContext(ctx, focus, selectedCycle, selectedCapability) {
1146
+ const lines = [`## PRISM Analysis`];
1147
+ if (ctx.coherenceScore !== null) {
1148
+ lines.push(`Architecture Coherence Score: ${ctx.coherenceScore}/100`);
1149
+ }
1150
+ if (ctx.architecturalPatterns.length > 0) {
1151
+ lines.push(`Detected patterns: ${ctx.architecturalPatterns.join(", ")}`);
1152
+ }
1153
+ if (ctx.topRisks.length > 0) {
1154
+ lines.push(``, `### Top Architectural Risks`);
1155
+ for (const r of ctx.topRisks.slice(0, 5)) {
1156
+ lines.push(`- ${r.name} (${r.capabilityId}): ${r.reason}`);
1157
+ }
1158
+ }
1159
+ if ((focus === "cycles" || focus === "overall") && ctx.importCycles.length > 0) {
1160
+ lines.push(``, `### Import Cycles (${ctx.importCycles.length} detected)`);
1161
+ const cyclesToShow = selectedCycle ? ctx.importCycles.filter((c) => c.files.some((f) => selectedCycle.includes(f))) : ctx.importCycles.slice(0, 5);
1162
+ for (const c of cyclesToShow) {
1163
+ lines.push(`- [${c.severity}] ${c.files.join(" \u2192 ")}`);
1164
+ }
1165
+ }
1166
+ if ((focus === "capabilities" || focus === "overall") && ctx.capabilityBoundaries.length > 0) {
1167
+ lines.push(``, `### Capability Boundaries (dependency edges)`);
1168
+ const boundaries = selectedCapability ? ctx.capabilityBoundaries.filter((b) => b.from === selectedCapability || b.to === selectedCapability) : ctx.capabilityBoundaries.slice(0, 10);
1169
+ for (const b of boundaries) {
1170
+ lines.push(`- ${b.from} \u2192 ${b.to} (${b.edgeCount} import edges)`);
1171
+ }
1172
+ }
1173
+ if ((focus === "dependencies" || focus === "overall") && ctx.highChurnFiles.length > 0) {
1174
+ lines.push(``, `### High-Churn Files`);
1175
+ for (const f of ctx.highChurnFiles.slice(0, 8)) {
1176
+ const caps = f.capabilities.length > 0 ? ` [${f.capabilities.join(", ")}]` : "";
1177
+ lines.push(`- ${f.path}${caps} \u2014 ${f.commits} commits`);
1178
+ }
1179
+ }
1180
+ if (ctx.orphanedFiles.length > 0) {
1181
+ lines.push(``, `### Orphaned Files (${ctx.orphanedFiles.length} untagged)`);
1182
+ for (const f of ctx.orphanedFiles.slice(0, 5)) {
1183
+ lines.push(`- ${f}`);
1184
+ }
1185
+ }
1186
+ return lines.join("\n");
1187
+ }
1188
+ function buildAmberContext2(amber) {
1189
+ const lines = [`## AMBER Capability Registry`, amber.summary, ``];
1190
+ for (const cap of amber.capabilities.slice(0, 15)) {
1191
+ const tags = [cap.criticality, cap.lifecycle];
1192
+ if (cap.driftCount > 0) tags.push(`drift:${cap.driftCount}`);
1193
+ lines.push(
1194
+ `- ${cap.name} (${cap.id}) [${tags.join(", ")}]${cap.files.length > 0 ? ` \u2014 ${cap.files.length} files` : ""}`
1195
+ );
1196
+ }
1197
+ return lines.join("\n");
1198
+ }
1199
+ function buildSystemPrompt9() {
1200
+ return "You are a principal software architect with deep expertise in architecture decision records (ADRs) following the MADR (Markdown Architectural Decision Records) format. You analyse codebases and produce actionable, evidence-grounded ADRs. Generate ONLY valid JSON \u2014 no markdown fences, no prose, no commentary outside the JSON object.";
1201
+ }
1202
+ function buildUserPrompt9(opts) {
1203
+ const focusDescriptions = {
1204
+ overall: "Generate a comprehensive set of ADRs covering the most significant architectural decisions evident from import cycles, capability boundaries, dependency patterns, and code structure.",
1205
+ cycles: "Focus on ADRs that address the import cycle patterns detected. Each cycle represents an implicit architectural decision \u2014 make those decisions explicit and propose resolutions.",
1206
+ capabilities: "Focus on ADRs about capability boundary decisions \u2014 how capabilities are decomposed, what depends on what, and whether the boundaries are intentional or accidental.",
1207
+ dependencies: "Focus on ADRs about dependency management \u2014 high-churn files, hotspots, fan-out patterns, and whether the dependency structure aligns with the intended architecture."
1208
+ };
1209
+ const parts = [
1210
+ `Generate 3\u20137 Architecture Decision Records (ADRs) for: ${opts.projectName}`,
1211
+ `Focus: ${opts.focus} \u2014 ${focusDescriptions[opts.focus]}`,
1212
+ ``,
1213
+ buildCodebaseContext2(opts.blueprint, opts.projectName)
1214
+ ];
1215
+ if (opts.amberContext) {
1216
+ parts.push(``, buildAmberContext2(opts.amberContext));
1217
+ }
1218
+ if (opts.adrContext) {
1219
+ parts.push(``, buildAdrContext(opts.adrContext, opts.focus, opts.selectedCycle, opts.selectedCapability));
1220
+ }
1221
+ if (opts.selectedCycle && opts.selectedCycle.length > 0) {
1222
+ parts.push(``, `## Selected Cycle for Focus`, opts.selectedCycle.join(" \u2192 "));
1223
+ }
1224
+ if (opts.selectedCapability) {
1225
+ parts.push(``, `## Selected Capability for Focus: ${opts.selectedCapability}`);
1226
+ }
1227
+ parts.push(
1228
+ ``,
1229
+ `## Output Format`,
1230
+ `Return a single JSON object:`,
1231
+ `{`,
1232
+ ` "summary": "<2\u20133 sentence overall architectural assessment>",`,
1233
+ ` "technicalDebt": "<1\u20132 sentence estimate of technical debt severity>",`,
1234
+ ` "adrs": [`,
1235
+ ` {`,
1236
+ ` "id": "ADR-001",`,
1237
+ ` "title": "<short imperative title>",`,
1238
+ ` "date": "${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}",`,
1239
+ ` "status": "Proposed",`,
1240
+ ` "context": "<what is the situation and why does this decision need to be made? 2\u20134 sentences>",`,
1241
+ ` "decision": "<what was decided and why? 2\u20134 sentences with rationale>",`,
1242
+ ` "consequences": {`,
1243
+ ` "positive": ["<benefit 1>", "<benefit 2>"],`,
1244
+ ` "negative": ["<drawback 1>"],`,
1245
+ ` "neutral": ["<neutral fact>"]`,
1246
+ ` },`,
1247
+ ` "alternatives": ["<alternative option 1>", "<alternative option 2>"],`,
1248
+ ` "relatedCapabilities": ["<capability-id-1>"],`,
1249
+ ` "relatedFiles": ["<file/path.ts>"]`,
1250
+ ` }`,
1251
+ ` ]`,
1252
+ `}`,
1253
+ ``,
1254
+ `Rules:`,
1255
+ `- IDs: ADR-001, ADR-002, \u2026 in order.`,
1256
+ `- Derive ALL content from the codebase data above \u2014 no invented facts.`,
1257
+ `- Each ADR MUST address a real pattern visible in the data (cycle, boundary, dependency).`,
1258
+ `- relatedCapabilities: reference real capability IDs from AMBER registry when available.`,
1259
+ `- relatedFiles: reference real file paths from the blueprint.`,
1260
+ `- status is always "Proposed" for new ADRs.`,
1261
+ `- Return ONLY valid JSON. No markdown. No prose outside the JSON.`
1262
+ );
1263
+ return parts.join("\n");
1264
+ }
1265
+ function buildFallback3(opts) {
1266
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1267
+ const adrs = [];
1268
+ if (opts.focus === "cycles" || opts.focus === "overall") {
1269
+ const cycleFiles = opts.adrContext?.importCycles[0]?.files ?? opts.selectedCycle ?? [];
1270
+ adrs.push({
1271
+ id: "ADR-001",
1272
+ title: "Introduce Module Boundary Abstractions to Break Import Cycles",
1273
+ date: today,
1274
+ status: "Proposed",
1275
+ context: `${opts.projectName} contains ${opts.adrContext?.importCycles.length ?? "multiple"} detected import cycles. Circular dependencies create tight coupling that makes independent testing and deployment difficult.`,
1276
+ decision: "Introduce shared interface/abstraction modules at cycle breakpoints to invert dependencies. Files that create cycles should depend on abstractions, not on each other directly.",
1277
+ consequences: {
1278
+ positive: ["Enables independent testing of cyclic modules", "Reduces coupling between layers"],
1279
+ negative: ["Requires refactoring existing imports", "Short-term increase in file count"],
1280
+ neutral: ["Aligns with Dependency Inversion Principle"]
1281
+ },
1282
+ alternatives: ["Extract shared state to a separate module", "Use event-driven communication between modules"],
1283
+ relatedCapabilities: opts.amberContext?.capabilities.slice(0, 2).map((c) => c.id) ?? [],
1284
+ relatedFiles: cycleFiles.slice(0, 3)
1285
+ });
1286
+ }
1287
+ if (opts.focus === "capabilities" || opts.focus === "overall") {
1288
+ adrs.push({
1289
+ id: `ADR-00${adrs.length + 1}`,
1290
+ title: "Enforce Explicit Capability Boundaries with Barrel Exports",
1291
+ date: today,
1292
+ status: "Proposed",
1293
+ context: `Capability boundaries in ${opts.projectName} are implicit \u2014 cross-capability imports create unintended dependencies. The AMBER registry identifies capabilities but import boundaries don't enforce them.`,
1294
+ decision: "Each capability should expose a single barrel index file. External modules import only from the barrel, never from internal implementation files.",
1295
+ consequences: {
1296
+ positive: ["Capability-level API surfaces become explicit", "Refactoring internals doesn't break consumers"],
1297
+ negative: ["Requires creating barrel files for each capability", "Barrel files need maintenance"],
1298
+ neutral: ["Standard pattern in large TypeScript codebases"]
1299
+ },
1300
+ alternatives: ["Use ESLint import boundaries rules", "Monorepo packages per capability"],
1301
+ relatedCapabilities: opts.amberContext?.capabilities.slice(0, 3).map((c) => c.id) ?? [],
1302
+ relatedFiles: []
1303
+ });
1304
+ }
1305
+ adrs.push({
1306
+ id: `ADR-00${adrs.length + 1}`,
1307
+ title: "Establish Architectural Fitness Functions for Ongoing Compliance",
1308
+ date: today,
1309
+ status: "Proposed",
1310
+ context: `The ${opts.projectName} codebase currently lacks automated enforcement of architectural constraints. Without fitness functions, architectural drift accumulates silently.`,
1311
+ decision: "Integrate PRISM / prism0x2A scans into CI to track coherence score, import cycles, and capability drift. Gate merges on cycle count not increasing.",
1312
+ consequences: {
1313
+ positive: ["Architectural health is continuously measured", "Teams get early warning of degradation"],
1314
+ negative: ["CI time increases slightly", "Requires PRISM setup in CI environment"],
1315
+ neutral: ["Shift from reactive to proactive architecture governance"]
1316
+ },
1317
+ alternatives: ["Manual architecture review cadence", "Weekly automated reports only"],
1318
+ relatedCapabilities: [],
1319
+ relatedFiles: []
1320
+ });
1321
+ return {
1322
+ adrs,
1323
+ summary: `${opts.projectName} has ${opts.blueprint.stats.totalFiles} files with ${opts.blueprint.stats.runtimeEdges} dependency edges. ${opts.adrContext?.importCycles.length ?? 0} import cycles and ${opts.adrContext?.capabilityBoundaries.length ?? 0} cross-capability dependency edges indicate structural decisions that should be made explicit via ADRs.`,
1324
+ technicalDebt: "Moderate-to-high architectural debt evident from cycle patterns and implicit capability boundaries. Estimated 3\u20136 engineering weeks to implement the proposed decisions."
1325
+ };
1326
+ }
1327
+ function parseLlmResponse3(raw) {
1328
+ let text = raw.trim();
1329
+ if (text.startsWith("```")) {
1330
+ text = text.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
1331
+ }
1332
+ let parsed;
1333
+ try {
1334
+ parsed = JSON.parse(text);
1335
+ } catch {
1336
+ return null;
1337
+ }
1338
+ const rawAdrs = Array.isArray(parsed.adrs) ? parsed.adrs : [];
1339
+ if (rawAdrs.length === 0) return null;
1340
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1341
+ const adrs = rawAdrs.map((a, idx) => {
1342
+ const adr = a ?? {};
1343
+ const consequences = typeof adr.consequences === "object" && adr.consequences !== null ? adr.consequences : {};
1344
+ const status = typeof adr.status === "string" && ["Proposed", "Accepted", "Deprecated", "Superseded"].includes(adr.status) ? adr.status : "Proposed";
1345
+ return {
1346
+ id: typeof adr.id === "string" ? adr.id : `ADR-${String(idx + 1).padStart(3, "0")}`,
1347
+ title: typeof adr.title === "string" ? adr.title : `Decision ${idx + 1}`,
1348
+ date: typeof adr.date === "string" ? adr.date : today,
1349
+ status,
1350
+ context: typeof adr.context === "string" ? adr.context : "",
1351
+ decision: typeof adr.decision === "string" ? adr.decision : "",
1352
+ consequences: {
1353
+ positive: Array.isArray(consequences.positive) ? consequences.positive.filter((s) => typeof s === "string") : [],
1354
+ negative: Array.isArray(consequences.negative) ? consequences.negative.filter((s) => typeof s === "string") : [],
1355
+ neutral: Array.isArray(consequences.neutral) ? consequences.neutral.filter((s) => typeof s === "string") : []
1356
+ },
1357
+ alternatives: Array.isArray(adr.alternatives) ? adr.alternatives.filter((s) => typeof s === "string") : [],
1358
+ relatedCapabilities: Array.isArray(adr.relatedCapabilities) ? adr.relatedCapabilities.filter((s) => typeof s === "string") : [],
1359
+ relatedFiles: Array.isArray(adr.relatedFiles) ? adr.relatedFiles.filter((s) => typeof s === "string") : []
1360
+ };
1361
+ });
1362
+ return {
1363
+ adrs,
1364
+ summary: typeof parsed.summary === "string" ? parsed.summary : "",
1365
+ technicalDebt: typeof parsed.technicalDebt === "string" ? parsed.technicalDebt : ""
1366
+ };
1367
+ }
1368
+ async function generateADR(opts) {
1369
+ try {
1370
+ const response = await opts.llm.complete({
1371
+ systemPrompt: buildSystemPrompt9(),
1372
+ messages: [{ role: "user", content: buildUserPrompt9(opts) }],
1373
+ maxTokens: 6e3
1374
+ });
1375
+ const parsed = parseLlmResponse3(response.content);
1376
+ if (parsed) return parsed;
1377
+ } catch {
1378
+ }
1379
+ return buildFallback3(opts);
1380
+ }
1381
+
1382
+ // src/generators/generateSprintRetro.ts
1383
+ function classifyCommit(subject) {
1384
+ const s = subject.toLowerCase();
1385
+ if (s.startsWith("feat") || s.includes("add ") || s.includes("implement") || s.includes("new ")) return "feature";
1386
+ if (s.startsWith("fix") || s.includes("bug") || s.includes("patch") || s.includes("hotfix")) return "fix";
1387
+ if (s.startsWith("refactor") || s.startsWith("refact") || s.includes("rewrite") || s.includes("restructure")) return "refactor";
1388
+ return "chore";
1389
+ }
1390
+ function buildContext2(opts) {
1391
+ const lines = [
1392
+ `## Sprint Context`,
1393
+ `Project: ${opts.projectName}`,
1394
+ `Sprint: ${opts.sprintName ?? "Current Sprint"}`,
1395
+ `Team size: ${opts.teamSize ?? "unknown"}`,
1396
+ `Tone: ${opts.tone}`,
1397
+ ``
1398
+ ];
1399
+ if (opts.gitContext) {
1400
+ const { commits, filesChanged, fromRef, toRef } = opts.gitContext;
1401
+ lines.push(
1402
+ `## Git Activity (${fromRef} \u2192 ${toRef})`,
1403
+ `Commits: ${commits.length}`,
1404
+ `Files changed: ${filesChanged}`,
1405
+ ``,
1406
+ `### Commits`,
1407
+ ...commits.slice(0, 30).map(
1408
+ (c) => `- [${classifyCommit(c.subject)}] ${c.subject}${c.author ? ` \u2014 ${c.author}` : ""}`
1409
+ ),
1410
+ ``
1411
+ );
1412
+ }
1413
+ if (opts.scoreBefore !== void 0 || opts.scoreAfter !== void 0) {
1414
+ lines.push(
1415
+ `## Architecture Health`,
1416
+ `Score at sprint start: ${opts.scoreBefore ?? "unknown"}`,
1417
+ `Score at sprint end: ${opts.scoreAfter ?? "unknown"}`,
1418
+ ``
1419
+ );
1420
+ }
1421
+ if (opts.amberContext) {
1422
+ const { capabilities, driftedCapabilities, summary } = opts.amberContext;
1423
+ lines.push(
1424
+ `## AMBER Capability Layer`,
1425
+ summary,
1426
+ `Drifted capabilities: ${driftedCapabilities}`,
1427
+ ``,
1428
+ `### Capabilities`,
1429
+ ...capabilities.slice(0, 15).map(
1430
+ (c) => `- ${c.name} [${c.criticality}/${c.lifecycle}]${c.driftCount > 0 ? ` \u26A0 drift: ${c.driftCount} files` : ""}`
1431
+ ),
1432
+ ``
1433
+ );
1434
+ }
1435
+ if (opts.greenContext) {
1436
+ lines.push(
1437
+ `## GREEN Cross-Layer Insights`,
1438
+ opts.greenContext.summary,
1439
+ ``
1440
+ );
1441
+ if (opts.greenContext.topRisks.length > 0) {
1442
+ lines.push(
1443
+ `### Top Risks`,
1444
+ ...opts.greenContext.topRisks.slice(0, 5).map(
1445
+ (r) => `- ${r.name}: ${r.reason} (impact: ${r.impact}/10)`
1446
+ ),
1447
+ ``
1448
+ );
1449
+ }
1450
+ }
1451
+ return lines.join("\n");
1452
+ }
1453
+ function buildSystemPrompt10(opts) {
1454
+ const toneMap = {
1455
+ professional: "You are a senior engineering manager facilitating a data-driven sprint retrospective. Be precise and actionable.",
1456
+ casual: "You are a friendly scrum master running a fun and honest retrospective. Keep it light but insightful.",
1457
+ "team-friendly": "You are a team lead who wants everyone to feel heard. Balance honesty with encouragement."
1458
+ };
1459
+ return `${toneMap[opts.tone]} Generate ONLY valid JSON \u2014 no markdown fences, no prose, no commentary outside the JSON object.`;
1460
+ }
1461
+ function buildUserPrompt10(opts) {
1462
+ const context = buildContext2(opts);
1463
+ const sprintName = opts.sprintName ?? "Current Sprint";
1464
+ return [
1465
+ `Generate a sprint retrospective for: ${opts.projectName} \u2014 ${sprintName}`,
1466
+ ``,
1467
+ context,
1468
+ ``,
1469
+ `## Output Format`,
1470
+ `Return a single JSON object with this exact shape:`,
1471
+ `{`,
1472
+ ` "sprintName": "${sprintName}",`,
1473
+ ` "period": "<date range or sprint identifier>",`,
1474
+ ` "summary": "<2-3 sentence executive summary of the sprint>",`,
1475
+ ` "delivered": [`,
1476
+ ` { "item": "<what was built>", "capability": "<AMBER capability id if applicable>", "type": "feature|fix|refactor|chore" }`,
1477
+ ` ],`,
1478
+ ` "healthDelta": {`,
1479
+ ` "scoreBefore": <number or null>,`,
1480
+ ` "scoreAfter": <number or null>,`,
1481
+ ` "delta": <number or null>,`,
1482
+ ` "verdict": "improved|stable|degraded",`,
1483
+ ` "degradedCapabilities": ["<capability name>"]`,
1484
+ ` },`,
1485
+ ` "wentWell": ["<positive thing 1>", ...],`,
1486
+ ` "improvements": ["<improvement needed 1>", ...],`,
1487
+ ` "puzzles": ["<question or confusion 1>", ...],`,
1488
+ ` "actions": ["<concrete action item 1>", ...],`,
1489
+ ` "debtIncurred": ["<technical debt item 1>", ...],`,
1490
+ ` "slideTitle": "<1 sentence sprint summary for a slide>",`,
1491
+ ` "slidePoints": ["<key point 1>", "<key point 2>", "<key point 3>"]`,
1492
+ `}`,
1493
+ ``,
1494
+ `Rules:`,
1495
+ `- delivered: derive from git commits, 5\u201315 items`,
1496
+ `- wentWell: 3\u20135 genuine positives from the data`,
1497
+ `- improvements: 3\u20135 concrete improvements (not vague)`,
1498
+ `- puzzles: 2\u20134 things that were unclear or confusing this sprint`,
1499
+ `- actions: 3\u20135 specific, assigned-sounding action items`,
1500
+ `- debtIncurred: 1\u20134 technical debt items visible from the commits/drift`,
1501
+ `- slidePoints: exactly 3 bullet strings \u2264 80 characters`,
1502
+ `- Derive everything from the context above \u2014 no invented facts`,
1503
+ `- Return ONLY valid JSON. No markdown. No prose outside the JSON.`
1504
+ ].join("\n");
1505
+ }
1506
+ function computeHealthDelta(scoreBefore, scoreAfter, amberContext) {
1507
+ const before = scoreBefore ?? null;
1508
+ const after = scoreAfter ?? null;
1509
+ const delta = before !== null && after !== null ? after - before : null;
1510
+ let verdict = "stable";
1511
+ if (delta !== null) {
1512
+ if (delta > 2) verdict = "improved";
1513
+ else if (delta < -2) verdict = "degraded";
1514
+ }
1515
+ const degradedCapabilities = amberContext ? amberContext.capabilities.filter((c) => c.driftCount > 0 && (c.criticality === "critical" || c.criticality === "high")).map((c) => c.name) : [];
1516
+ return { scoreBefore: before, scoreAfter: after, delta, verdict, degradedCapabilities };
1517
+ }
1518
+ function buildFallback4(opts) {
1519
+ const sprintName = opts.sprintName ?? "Current Sprint";
1520
+ const commits = opts.gitContext?.commits ?? [];
1521
+ const healthDelta = computeHealthDelta(opts.scoreBefore, opts.scoreAfter, opts.amberContext);
1522
+ const delivered = commits.slice(0, 10).map((c) => ({
1523
+ item: c.subject,
1524
+ type: classifyCommit(c.subject)
1525
+ }));
1526
+ return {
1527
+ sprintName,
1528
+ period: opts.gitContext ? `${opts.gitContext.fromRef} \u2192 ${opts.gitContext.toRef}` : "This sprint",
1529
+ summary: `${opts.projectName} completed ${commits.length} commits this sprint across ${opts.gitContext?.filesChanged ?? 0} files.`,
1530
+ delivered,
1531
+ healthDelta,
1532
+ wentWell: [
1533
+ `${commits.filter((c) => classifyCommit(c.subject) === "feature").length} features shipped`,
1534
+ `${commits.filter((c) => classifyCommit(c.subject) === "fix").length} bugs fixed`,
1535
+ "Team maintained delivery cadence"
1536
+ ],
1537
+ improvements: [
1538
+ "Reduce WIP by completing in-flight work before starting new",
1539
+ "Improve commit message clarity",
1540
+ "Address documentation drift in critical capabilities"
1541
+ ],
1542
+ puzzles: [
1543
+ "Unclear ownership for cross-capability changes",
1544
+ "Test coverage strategy for new features"
1545
+ ],
1546
+ actions: [
1547
+ "Schedule architecture review for flagged capabilities",
1548
+ "Set up automated coherence score tracking in CI",
1549
+ "Document top 3 architectural decisions made this sprint"
1550
+ ],
1551
+ debtIncurred: healthDelta.degradedCapabilities.length > 0 ? healthDelta.degradedCapabilities.map((c) => `Documentation drift in ${c}`) : ["Review and address any undocumented capability changes"],
1552
+ slideTitle: `${sprintName}: ${commits.length} commits, ${opts.gitContext?.filesChanged ?? 0} files changed`,
1553
+ slidePoints: [
1554
+ `${commits.length} commits across ${opts.gitContext?.filesChanged ?? 0} files`,
1555
+ `Architecture health: ${healthDelta.verdict}`,
1556
+ `${healthDelta.degradedCapabilities.length} capabilities need attention`
1557
+ ]
1558
+ };
1559
+ }
1560
+ function parseLlmResponse4(raw, opts) {
1561
+ let text = raw.trim();
1562
+ if (text.startsWith("```")) {
1563
+ text = text.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
1564
+ }
1565
+ let parsed;
1566
+ try {
1567
+ parsed = JSON.parse(text);
1568
+ } catch {
1569
+ return null;
1570
+ }
1571
+ const sprintName = typeof parsed.sprintName === "string" ? parsed.sprintName : opts.sprintName ?? "Current Sprint";
1572
+ const period = typeof parsed.period === "string" ? parsed.period : "";
1573
+ const summary = typeof parsed.summary === "string" ? parsed.summary : "";
1574
+ const rawDelivered = Array.isArray(parsed.delivered) ? parsed.delivered : [];
1575
+ const delivered = rawDelivered.filter((d) => typeof d.item === "string").map((d) => {
1576
+ const item = d;
1577
+ const type = ["feature", "fix", "refactor", "chore"].includes(item.type) ? item.type : "chore";
1578
+ return {
1579
+ item: item.item,
1580
+ ...typeof item.capability === "string" ? { capability: item.capability } : {},
1581
+ type
1582
+ };
1583
+ });
1584
+ const hd = parsed.healthDelta ?? {};
1585
+ const scoreBeforeRaw = typeof hd.scoreBefore === "number" ? hd.scoreBefore : null;
1586
+ const scoreAfterRaw = typeof hd.scoreAfter === "number" ? hd.scoreAfter : null;
1587
+ const deltaRaw = typeof hd.delta === "number" ? hd.delta : null;
1588
+ const verdictRaw = ["improved", "stable", "degraded"].includes(hd.verdict) ? hd.verdict : "stable";
1589
+ const degradedCaps = Array.isArray(hd.degradedCapabilities) ? hd.degradedCapabilities.filter((x) => typeof x === "string") : [];
1590
+ const toStringArray = (key) => {
1591
+ const val = parsed[key];
1592
+ if (!Array.isArray(val)) return [];
1593
+ return val.filter((x) => typeof x === "string");
1594
+ };
1595
+ if (!summary && delivered.length === 0) return null;
1596
+ return {
1597
+ sprintName,
1598
+ period,
1599
+ summary,
1600
+ delivered,
1601
+ healthDelta: {
1602
+ scoreBefore: scoreBeforeRaw,
1603
+ scoreAfter: scoreAfterRaw,
1604
+ delta: deltaRaw,
1605
+ verdict: verdictRaw,
1606
+ degradedCapabilities: degradedCaps
1607
+ },
1608
+ wentWell: toStringArray("wentWell"),
1609
+ improvements: toStringArray("improvements"),
1610
+ puzzles: toStringArray("puzzles"),
1611
+ actions: toStringArray("actions"),
1612
+ debtIncurred: toStringArray("debtIncurred"),
1613
+ slideTitle: typeof parsed.slideTitle === "string" ? parsed.slideTitle : sprintName,
1614
+ slidePoints: toStringArray("slidePoints").slice(0, 3)
1615
+ };
1616
+ }
1617
+ async function generateSprintRetro(opts) {
1618
+ try {
1619
+ const response = await opts.llm.complete({
1620
+ systemPrompt: buildSystemPrompt10(opts),
1621
+ messages: [{ role: "user", content: buildUserPrompt10(opts) }],
1622
+ maxTokens: 4096
1623
+ });
1624
+ const parsed = parseLlmResponse4(response.content, opts);
1625
+ if (parsed) return parsed;
1626
+ } catch {
1627
+ }
1628
+ return buildFallback4(opts);
1629
+ }
1630
+
1631
+ // src/generators/generateNewsletter.ts
1632
+ function buildContext3(opts) {
1633
+ const { digestContext: d, amberContext, projectName, teamName, targetAudience, tone } = opts;
1634
+ const lines = [
1635
+ `## Newsletter Context`,
1636
+ `Project: ${projectName}`,
1637
+ `Team: ${teamName ?? "Engineering"}`,
1638
+ `Target audience: ${targetAudience}`,
1639
+ `Tone: ${tone}`,
1640
+ ``,
1641
+ `## Weekly Digest \u2014 ${d.period}`,
1642
+ `Commits: ${d.commitCount}`,
1643
+ `Files changed: ${d.filesChanged}`,
1644
+ `Coherence grade: ${d.grade ?? "N/A"}`,
1645
+ `Score start: ${d.scoreStart ?? "N/A"}`,
1646
+ `Score end: ${d.scoreEnd ?? "N/A"}`,
1647
+ `Score delta: ${d.scoreDelta !== null ? d.scoreDelta > 0 ? `+${d.scoreDelta}` : String(d.scoreDelta) : "N/A"}`,
1648
+ `Health summary: ${d.healthSummary}`,
1649
+ ``
1650
+ ];
1651
+ if (d.topCommits.length > 0) {
1652
+ lines.push(`### Top commits`, ...d.topCommits.map((c) => `- ${c}`), ``);
1653
+ }
1654
+ if (d.newDrifts.length > 0) {
1655
+ lines.push(`### New capability drifts`, ...d.newDrifts.map((c) => `- ${c}`), ``);
1656
+ }
1657
+ if (d.resolvedDrifts.length > 0) {
1658
+ lines.push(`### Resolved drifts`, ...d.resolvedDrifts.map((c) => `- ${c}`), ``);
1659
+ }
1660
+ if (d.newRisks.length > 0) {
1661
+ lines.push(`### New risks`, ...d.newRisks.map((r) => `- ${r}`), ``);
1662
+ }
1663
+ if (amberContext) {
1664
+ lines.push(`## AMBER Capability Summary`, amberContext.summary, ``);
1665
+ }
1666
+ return lines.join("\n");
1667
+ }
1668
+ function buildSystemPrompt11(opts) {
1669
+ const audienceMap = {
1670
+ "product-team": "You are writing a newsletter for the product engineering team \u2014 technical details are welcome.",
1671
+ management: "You are writing a newsletter for engineering managers \u2014 focus on velocity, quality, and risk.",
1672
+ stakeholders: "You are writing a newsletter for business stakeholders \u2014 translate technical work into business outcomes.",
1673
+ "all-hands": "You are writing a company-wide newsletter \u2014 accessible to everyone, inspiring, and concise."
1674
+ };
1675
+ const toneMap = {
1676
+ professional: "Use a professional, data-driven tone.",
1677
+ casual: "Use a friendly, conversational tone \u2014 contractions welcome.",
1678
+ accessible: "Use zero technical jargon. If you must mention a technical concept, define it in plain language immediately."
1679
+ };
1680
+ return [
1681
+ audienceMap[opts.targetAudience],
1682
+ toneMap[opts.tone],
1683
+ "Generate ONLY valid JSON \u2014 no markdown fences, no prose, no commentary outside the JSON object."
1684
+ ].join(" ");
1685
+ }
1686
+ function buildUserPrompt11(opts) {
1687
+ const context = buildContext3(opts);
1688
+ const { projectName, teamName, targetAudience, tone, includeMetrics } = opts;
1689
+ return [
1690
+ `Generate a weekly engineering newsletter for: ${projectName}`,
1691
+ `Team: ${teamName ?? "Engineering"} | Audience: ${targetAudience} | Tone: ${tone}`,
1692
+ `Include metrics: ${includeMetrics}`,
1693
+ ``,
1694
+ context,
1695
+ ``,
1696
+ `## Output Format`,
1697
+ `Return a single JSON object with this exact shape:`,
1698
+ `{`,
1699
+ ` "subject": "<email subject line, \u226460 chars>",`,
1700
+ ` "preview": "<email preview text, \u226450 chars>",`,
1701
+ ` "greeting": "<opening greeting>",`,
1702
+ ` "headline": "<1-sentence hook that opens the newsletter>",`,
1703
+ ` "sections": [`,
1704
+ ` { "heading": "<section heading>", "body": "<2-4 sentences>", "type": "highlight|metrics|risk|shoutout|upcoming" }`,
1705
+ ` ],`,
1706
+ ` "metrics": [`,
1707
+ ` { "label": "<metric name>", "value": "<value>", "trend": "up|down|stable" }`,
1708
+ ` ],`,
1709
+ ` "closing": "<1-2 sentence closing>",`,
1710
+ ` "unsubscribeNote": "Reply STOP to unsubscribe from this digest.",`,
1711
+ ` "slackVersion": "<Slack mrkdwn formatted version of the newsletter>",`,
1712
+ ` "teamsVersion": "<Microsoft Teams Adaptive Card JSON as a string>",`,
1713
+ ` "htmlVersion": "<complete HTML email with inline styles, dark-mode friendly>"`,
1714
+ `}`,
1715
+ ``,
1716
+ `Rules:`,
1717
+ `- sections: 3\u20135 sections covering the highlights`,
1718
+ `- metrics: ${includeMetrics ? "include 3\u20135 key metrics from the digest" : "return empty array []"}`,
1719
+ `- slackVersion: use *bold*, _italic_, \`code\`, > blockquote, \u2022 bullets (mrkdwn)`,
1720
+ `- teamsVersion: valid Teams Adaptive Card JSON with TextBlock, FactSet elements`,
1721
+ `- htmlVersion: full HTML with <html><head><body>, inline CSS, no external resources`,
1722
+ `- subject: must reference the week/project and be compelling`,
1723
+ `- Derive all content from the digest context above \u2014 no invented facts`,
1724
+ `- Return ONLY valid JSON. No markdown fences. No prose outside the JSON.`
1725
+ ].join("\n");
1726
+ }
1727
+ function buildHtmlEmail(opts, sections, metrics) {
1728
+ const { projectName, teamName, digestContext: d } = opts;
1729
+ const sectionHtml = sections.map((s) => `
1730
+ <tr><td style="padding:20px 32px 0">
1731
+ <h2 style="margin:0 0 8px;font-size:16px;color:#e2e8f0">${s.heading}</h2>
1732
+ <p style="margin:0;color:#94a3b8;font-size:14px;line-height:1.6">${s.body}</p>
1733
+ </td></tr>`).join("");
1734
+ const metricsHtml = metrics.length > 0 ? `
1735
+ <tr><td style="padding:20px 32px 0">
1736
+ <table width="100%" cellpadding="0" cellspacing="0" border="0">
1737
+ <tr>${metrics.map((m) => `
1738
+ <td style="text-align:center;padding:12px;background:#1e293b;border-radius:8px;margin:4px">
1739
+ <div style="font-size:22px;font-weight:700;color:${m.trend === "up" ? "#34d399" : m.trend === "down" ? "#f87171" : "#94a3b8"}">${m.value}</div>
1740
+ <div style="font-size:12px;color:#64748b;margin-top:4px">${m.label}</div>
1741
+ </td>`).join("<td style='width:8px'></td>")}</tr>
1742
+ </table>
1743
+ </td></tr>` : "";
1744
+ return `<!DOCTYPE html>
1745
+ <html lang="en">
1746
+ <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>${projectName} \u2014 Weekly Digest</title></head>
1747
+ <body style="margin:0;padding:0;background:#0f172a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif">
1748
+ <table width="100%" cellpadding="0" cellspacing="0" border="0" style="max-width:600px;margin:0 auto">
1749
+ <tr><td style="padding:32px 32px 16px;border-bottom:1px solid #1e293b">
1750
+ <h1 style="margin:0;font-size:24px;font-weight:700;color:#f1f5f9">${projectName}</h1>
1751
+ <p style="margin:4px 0 0;color:#64748b;font-size:14px">${teamName ?? "Engineering"} \xB7 ${d.period}</p>
1752
+ </td></tr>
1753
+ ${sectionHtml}
1754
+ ${metricsHtml}
1755
+ <tr><td style="padding:24px 32px;border-top:1px solid #1e293b;margin-top:24px">
1756
+ <p style="margin:0;color:#475569;font-size:12px">Generated by forge0x2B \xB7 Reply STOP to unsubscribe.</p>
1757
+ </td></tr>
1758
+ </table>
1759
+ </body>
1760
+ </html>`;
1761
+ }
1762
+ function buildSlackVersion(opts, sections, metrics) {
1763
+ const { projectName, digestContext: d } = opts;
1764
+ const lines = [
1765
+ `*${projectName} \u2014 Weekly Engineering Digest*`,
1766
+ `_${d.period}_`,
1767
+ ``,
1768
+ `*Health:* ${d.healthSummary}`,
1769
+ ``,
1770
+ ...sections.map((s) => [`*${s.heading}*`, s.body, ""].join("\n"))
1771
+ ];
1772
+ if (metrics.length > 0) {
1773
+ lines.push(
1774
+ `*Key Metrics*`,
1775
+ ...metrics.map((m) => `\u2022 *${m.label}:* ${m.value} ${m.trend === "up" ? "\u2191" : m.trend === "down" ? "\u2193" : "\u2192"}`),
1776
+ ``
1777
+ );
1778
+ }
1779
+ lines.push(`_Generated by forge0x2B_`);
1780
+ return lines.join("\n");
1781
+ }
1782
+ function buildTeamsVersion(opts, sections) {
1783
+ const { projectName, digestContext: d } = opts;
1784
+ const card = {
1785
+ type: "AdaptiveCard",
1786
+ $schema: "http://adaptivecards.io/schemas/adaptive-card.json",
1787
+ version: "1.4",
1788
+ body: [
1789
+ {
1790
+ type: "TextBlock",
1791
+ text: `${projectName} \u2014 Weekly Engineering Digest`,
1792
+ weight: "Bolder",
1793
+ size: "Large",
1794
+ color: "Accent"
1795
+ },
1796
+ {
1797
+ type: "TextBlock",
1798
+ text: d.period,
1799
+ isSubtle: true,
1800
+ spacing: "None"
1801
+ },
1802
+ {
1803
+ type: "TextBlock",
1804
+ text: d.healthSummary,
1805
+ wrap: true,
1806
+ spacing: "Medium"
1807
+ },
1808
+ ...sections.slice(0, 3).map((s) => ({
1809
+ type: "Container",
1810
+ spacing: "Medium",
1811
+ items: [
1812
+ { type: "TextBlock", text: s.heading, weight: "Bolder", wrap: true },
1813
+ { type: "TextBlock", text: s.body, wrap: true, isSubtle: true }
1814
+ ]
1815
+ })),
1816
+ {
1817
+ type: "TextBlock",
1818
+ text: "Generated by forge0x2B",
1819
+ isSubtle: true,
1820
+ size: "Small",
1821
+ spacing: "Large"
1822
+ }
1823
+ ]
1824
+ };
1825
+ return JSON.stringify(card);
1826
+ }
1827
+ function buildFallback5(opts) {
1828
+ const { projectName, teamName, digestContext: d, includeMetrics } = opts;
1829
+ const sections = [
1830
+ {
1831
+ heading: "This Week in Engineering",
1832
+ body: `The team shipped ${d.commitCount} commits across ${d.filesChanged} files during ${d.period}. ${d.healthSummary}`,
1833
+ type: "highlight"
1834
+ }
1835
+ ];
1836
+ if (d.topCommits.length > 0) {
1837
+ sections.push({
1838
+ heading: "Highlights",
1839
+ body: d.topCommits.slice(0, 3).join(". ") + ".",
1840
+ type: "highlight"
1841
+ });
1842
+ }
1843
+ if (d.newRisks.length > 0) {
1844
+ sections.push({
1845
+ heading: "On Our Radar",
1846
+ body: `We identified ${d.newRisks.length} new risk${d.newRisks.length > 1 ? "s" : ""} this week: ${d.newRisks.slice(0, 2).join(", ")}.`,
1847
+ type: "risk"
1848
+ });
1849
+ }
1850
+ const metrics = includeMetrics ? [
1851
+ { label: "Commits", value: String(d.commitCount), trend: "stable" },
1852
+ { label: "Files Changed", value: String(d.filesChanged), trend: "stable" },
1853
+ ...d.grade ? [{ label: "Architecture Grade", value: d.grade, trend: d.scoreDelta !== null && d.scoreDelta > 0 ? "up" : d.scoreDelta !== null && d.scoreDelta < 0 ? "down" : "stable" }] : []
1854
+ ] : [];
1855
+ return {
1856
+ subject: `${projectName} Engineering Digest \u2014 ${d.period}`,
1857
+ preview: `${d.commitCount} commits \xB7 ${d.healthSummary.slice(0, 40)}`,
1858
+ greeting: `Hello ${teamName ?? "team"},`,
1859
+ headline: `Here's what the engineering team shipped this week.`,
1860
+ sections,
1861
+ metrics,
1862
+ closing: `Thanks for reading. See you next week!`,
1863
+ unsubscribeNote: "Reply STOP to unsubscribe from this digest.",
1864
+ slackVersion: buildSlackVersion(opts, sections, metrics),
1865
+ teamsVersion: buildTeamsVersion(opts, sections),
1866
+ htmlVersion: buildHtmlEmail(opts, sections, metrics)
1867
+ };
1868
+ }
1869
+ function parseLlmResponse5(raw, opts) {
1870
+ let text = raw.trim();
1871
+ if (text.startsWith("```")) {
1872
+ text = text.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
1873
+ }
1874
+ let parsed;
1875
+ try {
1876
+ parsed = JSON.parse(text);
1877
+ } catch {
1878
+ return null;
1879
+ }
1880
+ const getString = (key, fallback) => typeof parsed[key] === "string" ? parsed[key] : fallback;
1881
+ const rawSections = Array.isArray(parsed.sections) ? parsed.sections : [];
1882
+ const sections = rawSections.filter((s) => typeof s.heading === "string").map((s) => {
1883
+ const item = s;
1884
+ return {
1885
+ heading: item.heading,
1886
+ body: typeof item.body === "string" ? item.body : "",
1887
+ type: ["highlight", "metrics", "risk", "shoutout", "upcoming"].includes(item.type) ? item.type : "highlight"
1888
+ };
1889
+ });
1890
+ const rawMetrics = Array.isArray(parsed.metrics) ? parsed.metrics : [];
1891
+ const metrics = rawMetrics.filter((m) => typeof m.label === "string").map((m) => {
1892
+ const item = m;
1893
+ return {
1894
+ label: item.label,
1895
+ value: typeof item.value === "string" ? item.value : "",
1896
+ trend: ["up", "down", "stable"].includes(item.trend) ? item.trend : "stable"
1897
+ };
1898
+ });
1899
+ const subject = getString("subject", "");
1900
+ if (!subject) return null;
1901
+ const slackVersion = getString("slackVersion", "") || buildSlackVersion(opts, sections, metrics);
1902
+ const teamsVersion = getString("teamsVersion", "") || buildTeamsVersion(opts, sections);
1903
+ const htmlVersion = getString("htmlVersion", "") || buildHtmlEmail(opts, sections, metrics);
1904
+ return {
1905
+ subject,
1906
+ preview: getString("preview", "").slice(0, 50),
1907
+ greeting: getString("greeting", `Hello ${opts.teamName ?? "team"},`),
1908
+ headline: getString("headline", ""),
1909
+ sections,
1910
+ metrics,
1911
+ closing: getString("closing", ""),
1912
+ unsubscribeNote: "Reply STOP to unsubscribe from this digest.",
1913
+ slackVersion,
1914
+ teamsVersion,
1915
+ htmlVersion
1916
+ };
1917
+ }
1918
+ async function generateNewsletter(opts) {
1919
+ try {
1920
+ const response = await opts.llm.complete({
1921
+ systemPrompt: buildSystemPrompt11(opts),
1922
+ messages: [{ role: "user", content: buildUserPrompt11(opts) }],
1923
+ maxTokens: 8192
1924
+ });
1925
+ const parsed = parseLlmResponse5(response.content, opts);
1926
+ if (parsed) return parsed;
1927
+ } catch {
1928
+ }
1929
+ return buildFallback5(opts);
1930
+ }
1931
+
1932
+ // src/generators/generateRadio.ts
1933
+ function buildContext4(opts) {
1934
+ const { digestContext: d, amberContext, projectName, audience } = opts;
1935
+ const lines = [
1936
+ `## Architecture Radio Context`,
1937
+ `Project: ${projectName}`,
1938
+ `Audience: ${audience}`,
1939
+ `Period: ${d.period}`,
1940
+ ``,
1941
+ `## Weekly Digest`,
1942
+ `Commits: ${d.commitCount}`,
1943
+ `Files changed: ${d.filesChanged}`,
1944
+ `Coherence grade: ${d.grade ?? "N/A"}`,
1945
+ `Score: ${d.scoreStart ?? "N/A"} \u2192 ${d.scoreEnd ?? "N/A"} (delta: ${d.scoreDelta !== null ? d.scoreDelta > 0 ? `+${d.scoreDelta}` : String(d.scoreDelta) : "N/A"})`,
1946
+ `Health: ${d.healthSummary}`,
1947
+ ``
1948
+ ];
1949
+ if (d.topCommits.length > 0) {
1950
+ lines.push(`### Top Commits`, ...d.topCommits.slice(0, 5).map((c) => `- ${c}`), ``);
1951
+ }
1952
+ if (d.newDrifts.length > 0) {
1953
+ lines.push(`### Capability Drifts`, ...d.newDrifts.map((c) => `- ${c}`), ``);
1954
+ }
1955
+ if (d.resolvedDrifts.length > 0) {
1956
+ lines.push(`### Resolved Drifts`, ...d.resolvedDrifts.map((c) => `- ${c}`), ``);
1957
+ }
1958
+ if (d.newRisks.length > 0) {
1959
+ lines.push(`### New Risks`, ...d.newRisks.map((r) => `- ${r}`), ``);
1960
+ }
1961
+ if (amberContext) {
1962
+ lines.push(
1963
+ `## AMBER Capability Summary`,
1964
+ `Total capabilities: ${amberContext.capabilities.length}`,
1965
+ `Drifted: ${amberContext.driftedCapabilities}`,
1966
+ `Tagged files: ${amberContext.taggedFiles} of ${amberContext.totalFiles} (${amberContext.taggedPercent}%)`,
1967
+ ``,
1968
+ amberContext.summary,
1969
+ ``
1970
+ );
1971
+ }
1972
+ return lines.join("\n");
1973
+ }
1974
+ function buildSystemPrompt12(audience) {
1975
+ const audienceInstructions = audience === "executive" ? "You are writing for a CTO, VP Engineering, or executive stakeholder. Use ZERO technical jargon. Focus exclusively on business impact: hours saved, risk reduced, team health, delivery confidence. Translate engineering events into business outcomes." : "You are writing for a senior engineering team. Be specific \u2014 include file counts, capability names, cycle details, and concrete numbers. Engineers appreciate precision and honesty.";
1976
+ return [
1977
+ `You are Architecture Radio \u2014 the daily briefing that turns raw engineering signal into clear, compelling communication.`,
1978
+ audienceInstructions,
1979
+ `Keep Slack version punchy and action-oriented (~150 words). Email version has proper structure with greeting, paragraphs, and CTA (~400 words). Twitter version is a shareable insight \u2014 public-safe, no sensitive data, \u2264280 chars.`,
1980
+ `Generate ONLY valid JSON \u2014 no markdown fences, no prose, no commentary outside the JSON object.`
1981
+ ].join(" ");
1982
+ }
1983
+ function buildUserPrompt12(opts) {
1984
+ const context = buildContext4(opts);
1985
+ return [
1986
+ `Generate today's Architecture Radio briefing for: ${opts.projectName}`,
1987
+ `Audience: ${opts.audience}`,
1988
+ ``,
1989
+ context,
1990
+ ``,
1991
+ `## Output Format`,
1992
+ `Return a single JSON object with this exact shape:`,
1993
+ `{`,
1994
+ ` "headline": "<1 powerful sentence capturing the most important thing \u2014 not a title, a statement>",`,
1995
+ ` "slackVersion": "<~150 word Slack mrkdwn message \u2014 punchy, action-oriented, uses *bold*, _italic_, \u2022 bullets>",`,
1996
+ ` "emailVersion": "<~400 word complete HTML email \u2014 full <html><head><body> with inline CSS, dark background, proper greeting, body paragraphs, clear CTA>",`,
1997
+ ` "twitterVersion": "<insight worth sharing publicly \u2014 \u2264280 chars, no sensitive data, no project-specific names unless generic>"`,
1998
+ `}`,
1999
+ ``,
2000
+ `Rules:`,
2001
+ `- headline: not a generic recap \u2014 capture what MATTERS most today`,
2002
+ `- slackVersion: start with the headline, use mrkdwn formatting`,
2003
+ `- emailVersion: full HTML with inline styles, dark-mode friendly (#0f172a background), greeting, 2-3 paragraphs, bold CTA at end`,
2004
+ `- twitterVersion: shareable engineering insight, safe for public, abstract enough for any team`,
2005
+ `- ${opts.audience === "executive" ? "No technical jargon anywhere \u2014 business language only" : "Be technically precise \u2014 engineers hate vague"}`,
2006
+ `- Derive all content from the digest context \u2014 no invented facts`,
2007
+ `- Return ONLY valid JSON. No markdown fences. No prose outside the JSON.`
2008
+ ].join("\n");
2009
+ }
2010
+ function buildFallback6(opts) {
2011
+ const { digestContext: d, projectName, audience } = opts;
2012
+ const isExec = audience === "executive";
2013
+ const headline = d.newRisks.length > 0 ? isExec ? `${projectName} has ${d.newRisks.length} new architectural risk${d.newRisks.length > 1 ? "s" : ""} requiring attention.` : `${d.newDrifts.length} capability drift${d.newDrifts.length !== 1 ? "s" : ""} detected across ${d.filesChanged} changed files \u2014 action needed.` : isExec ? `${projectName} delivered a healthy week with ${d.commitCount} changes and stable architecture.` : `${d.commitCount} commits, ${d.filesChanged} files \u2014 architecture grade: ${d.grade ?? "N/A"}. ${d.healthSummary}`;
2014
+ const slackVersion = [
2015
+ `*\u{1F4E1} Architecture Radio \u2014 ${d.period}*`,
2016
+ ``,
2017
+ `*${headline}*`,
2018
+ ``,
2019
+ `\u2022 *Health:* ${d.healthSummary}`,
2020
+ d.commitCount > 0 ? `\u2022 *Commits:* ${d.commitCount} across ${d.filesChanged} files` : "",
2021
+ d.newDrifts.length > 0 ? `\u2022 *Drifts:* ${d.newDrifts.slice(0, 3).join(", ")}` : "",
2022
+ d.newRisks.length > 0 ? `\u2022 *Risks:* ${d.newRisks.slice(0, 2).join(", ")}` : "",
2023
+ ``,
2024
+ `_Generated by forge0x2B_`
2025
+ ].filter(Boolean).join("\n");
2026
+ const emailVersion = `<!DOCTYPE html>
2027
+ <html lang="en">
2028
+ <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>Architecture Radio \u2014 ${projectName}</title></head>
2029
+ <body style="margin:0;padding:0;background:#0f172a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif">
2030
+ <table width="100%" cellpadding="0" cellspacing="0" border="0" style="max-width:600px;margin:0 auto">
2031
+ <tr><td style="padding:32px 32px 16px;border-bottom:1px solid #1e293b">
2032
+ <div style="font-size:11px;color:#f97316;font-weight:600;letter-spacing:0.1em;text-transform:uppercase;margin-bottom:8px">\u{1F4E1} Architecture Radio</div>
2033
+ <h1 style="margin:0;font-size:22px;font-weight:700;color:#f1f5f9;line-height:1.3">${headline}</h1>
2034
+ <p style="margin:8px 0 0;color:#64748b;font-size:13px">${d.period}</p>
2035
+ </td></tr>
2036
+ <tr><td style="padding:24px 32px">
2037
+ <p style="margin:0 0 16px;color:#94a3b8;font-size:14px;line-height:1.6">Hello,</p>
2038
+ <p style="margin:0 0 16px;color:#cbd5e1;font-size:14px;line-height:1.6">${d.healthSummary} The team made ${d.commitCount} commit${d.commitCount !== 1 ? "s" : ""} across ${d.filesChanged} file${d.filesChanged !== 1 ? "s" : ""} during ${d.period}.</p>
2039
+ ${d.newDrifts.length > 0 ? `<p style="margin:0 0 16px;color:#cbd5e1;font-size:14px;line-height:1.6"><strong style="color:#f1f5f9">Capability attention needed:</strong> ${d.newDrifts.slice(0, 3).join(", ")}.</p>` : ""}
2040
+ ${d.newRisks.length > 0 ? `<p style="margin:0 0 16px;color:#cbd5e1;font-size:14px;line-height:1.6"><strong style="color:#fca5a5">New risks identified:</strong> ${d.newRisks.slice(0, 3).join(", ")}.</p>` : ""}
2041
+ <p style="margin:24px 0 0;color:#64748b;font-size:12px;border-top:1px solid #1e293b;padding-top:16px">Generated with <a href="https://forge0x2b.dev" style="color:#f97316;text-decoration:none">forge0x2B</a> \xB7 Architecture intelligence for engineering teams</p>
2042
+ </td></tr>
2043
+ </table>
2044
+ </body>
2045
+ </html>`;
2046
+ const twitterVersion = d.scoreDelta !== null ? `Engineering teams that track architectural drift resolve issues ${Math.abs(d.scoreDelta)}x faster. What gets measured gets fixed. \u{1F3D7}\uFE0F` : `The best architecture documentation is one your team actually reads \u2014 and it updates itself. \u{1F4E1}`;
2047
+ return { headline, slackVersion, emailVersion, twitterVersion };
2048
+ }
2049
+ function parseLlmResponse6(raw) {
2050
+ let text = raw.trim();
2051
+ if (text.startsWith("```")) {
2052
+ text = text.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
2053
+ }
2054
+ let parsed;
2055
+ try {
2056
+ parsed = JSON.parse(text);
2057
+ } catch {
2058
+ return null;
2059
+ }
2060
+ const getString = (key) => typeof parsed[key] === "string" ? parsed[key] : "";
2061
+ const headline = getString("headline");
2062
+ if (!headline) return null;
2063
+ return {
2064
+ headline,
2065
+ slackVersion: getString("slackVersion"),
2066
+ emailVersion: getString("emailVersion"),
2067
+ twitterVersion: getString("twitterVersion").slice(0, 280)
2068
+ };
2069
+ }
2070
+ async function generateRadio(options) {
2071
+ try {
2072
+ const response = await options.llm.complete({
2073
+ systemPrompt: buildSystemPrompt12(options.audience),
2074
+ messages: [{ role: "user", content: buildUserPrompt12(options) }],
2075
+ maxTokens: 4096
2076
+ });
2077
+ const parsed = parseLlmResponse6(response.content);
2078
+ if (parsed) return parsed;
2079
+ } catch {
2080
+ }
2081
+ return buildFallback6(options);
2082
+ }
2083
+
2084
+ // src/generators/generateArc42.ts
2085
+ var ARC42_ATTRIBUTION = `arc42 template \xA9 arc42.org, Creative Commons Attribution 4.0 International (CC-BY 4.0).
2086
+ See https://arc42.org for the original template and documentation.`;
2087
+ function buildSection5(opts) {
2088
+ const { amberContext, projectName } = opts;
2089
+ if (!amberContext || amberContext.capabilities.length === 0) {
2090
+ return `## 5. Building Block View
2091
+
2092
+ No AMBER capability data available. Run an AMBER scan to populate this section.
2093
+
2094
+ *This section would show the top-level decomposition of the system into building blocks (capabilities) and their relationships.*
2095
+ `;
2096
+ }
2097
+ const lines = [
2098
+ `## 5. Building Block View`,
2099
+ ``,
2100
+ `*Source: PRISM AMBER capability registry*`,
2101
+ ``,
2102
+ `### Level 1 \u2014 ${projectName} (Whitebox)`,
2103
+ ``,
2104
+ `**Total capabilities:** ${amberContext.capabilities.length}`,
2105
+ `**Tagged files:** ${amberContext.taggedFiles} of ${amberContext.totalFiles} (${amberContext.taggedPercent.toFixed(0)}%)`,
2106
+ ``,
2107
+ `| Capability | Criticality | Lifecycle | Files |`,
2108
+ `|------------|-------------|-----------|-------|`
2109
+ ];
2110
+ for (const cap of amberContext.capabilities) {
2111
+ lines.push(
2112
+ `| ${cap.name} | ${cap.criticality} | ${cap.lifecycle} | ${cap.files.length} |`
2113
+ );
2114
+ }
2115
+ if (amberContext.driftedCapabilities > 0) {
2116
+ lines.push(
2117
+ ``,
2118
+ `> **Note:** ${amberContext.driftedCapabilities} capabilities have documentation drift (files modified since last doc update).`
2119
+ );
2120
+ }
2121
+ if (amberContext.orphanedFiles.length > 0) {
2122
+ lines.push(
2123
+ ``,
2124
+ `### Orphaned Files (not assigned to any capability)`,
2125
+ ``,
2126
+ ...amberContext.orphanedFiles.slice(0, 10).map((f) => `- \`${f}\``),
2127
+ amberContext.orphanedFiles.length > 10 ? `- *... and ${amberContext.orphanedFiles.length - 10} more*` : ``
2128
+ );
2129
+ }
2130
+ return lines.join("\n");
2131
+ }
2132
+ function buildSection9(opts) {
2133
+ const { digestContext: d } = opts;
2134
+ const lines = [
2135
+ `## 9. Architecture Decisions`,
2136
+ ``,
2137
+ `*Source: PRISM architecture signals*`,
2138
+ ``
2139
+ ];
2140
+ if (d.newRisks.length > 0) {
2141
+ lines.push(
2142
+ `### Recent Architectural Risks Surfaced`,
2143
+ ``,
2144
+ ...d.newRisks.map((r) => `- ${r}`),
2145
+ ``
2146
+ );
2147
+ }
2148
+ if (d.newDrifts.length > 0 || d.resolvedDrifts.length > 0) {
2149
+ lines.push(
2150
+ `### Documentation State`,
2151
+ ``
2152
+ );
2153
+ if (d.newDrifts.length > 0) {
2154
+ lines.push(
2155
+ `**New capability drifts** (${d.newDrifts.length}):`,
2156
+ ...d.newDrifts.map((c) => `- ${c}`),
2157
+ ``
2158
+ );
2159
+ }
2160
+ if (d.resolvedDrifts.length > 0) {
2161
+ lines.push(
2162
+ `**Resolved drifts** (${d.resolvedDrifts.length}):`,
2163
+ ...d.resolvedDrifts.map((c) => `- \u2713 ${c}`),
2164
+ ``
2165
+ );
2166
+ }
2167
+ }
2168
+ lines.push(
2169
+ `### Placeholder`,
2170
+ ``,
2171
+ `*Add ADRs (Architecture Decision Records) here. Use the forge0x2B ADR generator to produce MADR-format decisions from your codebase.*`,
2172
+ ``,
2173
+ `| ID | Title | Status | Date |`,
2174
+ `|----|-------|--------|------|`,
2175
+ `| ADR-001 | *Example decision* | Proposed | ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)} |`
2176
+ );
2177
+ return lines.join("\n");
2178
+ }
2179
+ function buildSection10(opts) {
2180
+ const { digestContext: d } = opts;
2181
+ const lines = [
2182
+ `## 10. Quality Requirements`,
2183
+ ``,
2184
+ `*Source: PRISM coherence metrics*`,
2185
+ ``,
2186
+ `### Quality Tree`,
2187
+ ``,
2188
+ `| Quality Goal | Scenario | Priority | Current State |`,
2189
+ `|--------------|----------|----------|---------------|`
2190
+ ];
2191
+ const grade = d.grade ?? "N/A";
2192
+ const gradeColor = grade.startsWith("A") ? "\u2713" : grade.startsWith("B") ? "~" : "\u26A0";
2193
+ lines.push(
2194
+ `| Architecture Coherence | Architecture grade \u2265 B | High | ${gradeColor} ${grade} |`,
2195
+ `| Documentation Coverage | All capabilities documented | Medium | ${d.newDrifts.length === 0 ? "\u2713 No drift" : `\u26A0 ${d.newDrifts.length} drifted`} |`,
2196
+ `| Change Stability | Low churn per capability | Medium | ${d.filesChanged} files changed this period |`
2197
+ );
2198
+ if (d.scoreStart !== null && d.scoreEnd !== null) {
2199
+ const delta = d.scoreDelta !== null ? d.scoreDelta > 0 ? `+${d.scoreDelta}` : String(d.scoreDelta) : "N/A";
2200
+ lines.push(
2201
+ ``,
2202
+ `### Coherence Score Trend`,
2203
+ ``,
2204
+ `| Period | Start | End | Delta |`,
2205
+ `|--------|-------|-----|-------|`,
2206
+ `| ${d.period} | ${d.scoreStart} | ${d.scoreEnd} | ${delta} |`
2207
+ );
2208
+ }
2209
+ return lines.join("\n");
2210
+ }
2211
+ function buildSection11(opts) {
2212
+ const { digestContext: d, amberContext } = opts;
2213
+ const lines = [
2214
+ `## 11. Risks and Technical Debt`,
2215
+ ``,
2216
+ `*Source: PRISM risk signals*`,
2217
+ ``,
2218
+ `### Risk Register`,
2219
+ ``,
2220
+ `| Risk | Probability | Impact | Mitigation |`,
2221
+ `|------|------------|--------|------------|`
2222
+ ];
2223
+ if (d.newRisks.length > 0) {
2224
+ for (const risk of d.newRisks) {
2225
+ lines.push(`| ${risk} | Medium | Medium | Review and address in next sprint |`);
2226
+ }
2227
+ } else {
2228
+ lines.push(`| *No new risks detected in ${d.period}* | \u2014 | \u2014 | \u2014 |`);
2229
+ }
2230
+ if (amberContext && amberContext.driftedCapabilities > 0) {
2231
+ lines.push(`| Documentation drift: ${amberContext.driftedCapabilities} capabilities | High | Low | Run \`prism sync\` |`);
2232
+ }
2233
+ if (d.newDrifts.length > 0) {
2234
+ lines.push(
2235
+ ``,
2236
+ `### Technical Debt \u2014 Documentation Drift`,
2237
+ ``,
2238
+ `The following capabilities have drifted documentation (code changed since last doc update):`,
2239
+ ``,
2240
+ ...d.newDrifts.map((c) => `- \`${c}\``)
2241
+ );
2242
+ }
2243
+ lines.push(
2244
+ ``,
2245
+ `### Health Summary`,
2246
+ ``,
2247
+ `> ${d.healthSummary}`
2248
+ );
2249
+ return lines.join("\n");
2250
+ }
2251
+ function buildSection12(opts) {
2252
+ const { amberContext, projectName } = opts;
2253
+ const lines = [
2254
+ `## 12. Glossary`,
2255
+ ``,
2256
+ `*Source: PRISM AMBER capability registry (Ubiquitous Language)*`,
2257
+ ``,
2258
+ `| Term | Definition |`,
2259
+ `|------|------------|`
2260
+ ];
2261
+ if (amberContext && amberContext.capabilities.length > 0) {
2262
+ for (const cap of amberContext.capabilities) {
2263
+ const desc = cap.description ? cap.description : `${cap.criticality} ${cap.lifecycle} capability in ${projectName}.`;
2264
+ lines.push(`| **${cap.name}** | ${desc} |`);
2265
+ }
2266
+ } else {
2267
+ lines.push(
2268
+ `| *No capabilities registered yet* | Run an AMBER scan to populate the ubiquitous language. |`
2269
+ );
2270
+ }
2271
+ lines.push(
2272
+ ``,
2273
+ `*Add domain-specific terms and acronyms below as the glossary evolves.*`
2274
+ );
2275
+ return lines.join("\n");
2276
+ }
2277
+ function buildSection2() {
2278
+ return `## 2. Constraints
2279
+
2280
+ *Fill in the constraints that apply to your architecture. These are non-negotiable boundaries.*
2281
+
2282
+ ### Technical Constraints
2283
+
2284
+ | Constraint | Background |
2285
+ |------------|------------|
2286
+ | *[e.g. Must run on Kubernetes]* | *[reason]* |
2287
+ | *[e.g. Postgres only \u2014 no other DBs]* | *[reason]* |
2288
+
2289
+ ### Organizational Constraints
2290
+
2291
+ | Constraint | Background |
2292
+ |------------|------------|
2293
+ | *[e.g. Team of N developers]* | *[reason]* |
2294
+ | *[e.g. 2-week sprints]* | *[reason]* |
2295
+
2296
+ ### Conventions
2297
+
2298
+ | Convention | Background |
2299
+ |------------|------------|
2300
+ | *[e.g. TypeScript strict mode]* | *[reason]* |
2301
+ `;
2302
+ }
2303
+ function buildSection6() {
2304
+ return `## 6. Runtime View
2305
+
2306
+ *Describe the important runtime scenarios \u2014 how the system behaves at runtime for key use cases.*
2307
+
2308
+ ### Scenario 1: [Name the scenario]
2309
+
2310
+ \`\`\`
2311
+ [Actor] \u2192 [Component A] \u2192 [Component B] \u2192 [External System]
2312
+ \u2190\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 [Response] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2313
+ \`\`\`
2314
+
2315
+ **Description:** *[Describe what happens step by step.]*
2316
+
2317
+ ### Scenario 2: [Name the scenario]
2318
+
2319
+ *[Add more scenarios as needed.]*
2320
+ `;
2321
+ }
2322
+ function buildSection7() {
2323
+ return `## 7. Deployment View
2324
+
2325
+ *Describe the technical infrastructure \u2014 environments, nodes, and how the system is distributed.*
2326
+
2327
+ ### Infrastructure Level 1
2328
+
2329
+ \`\`\`
2330
+ [Environment: Production]
2331
+ \u2514\u2500\u2500 [Server / Cloud Region]
2332
+ \u251C\u2500\u2500 [Application Tier]
2333
+ \u251C\u2500\u2500 [Database Tier]
2334
+ \u2514\u2500\u2500 [External Services]
2335
+ \`\`\`
2336
+
2337
+ ### Environments
2338
+
2339
+ | Environment | Purpose | URL / Access |
2340
+ |-------------|---------|-------------|
2341
+ | Production | Live system | *[URL]* |
2342
+ | Staging | Pre-production testing | *[URL]* |
2343
+ | Development | Local dev | localhost |
2344
+
2345
+ *Fill in your actual infrastructure topology.*
2346
+ `;
2347
+ }
2348
+ function buildLlmContext(opts) {
2349
+ const { projectName, projectDescription, teamSize, techStack, digestContext: d, amberContext } = opts;
2350
+ const lines = [
2351
+ `Project: ${projectName}`,
2352
+ projectDescription ? `Description: ${projectDescription}` : "",
2353
+ teamSize ? `Team size: ${teamSize}` : "",
2354
+ techStack && techStack.length > 0 ? `Tech stack: ${techStack.join(", ")}` : "",
2355
+ ``,
2356
+ `Architecture health: ${d.healthSummary}`,
2357
+ d.grade ? `Grade: ${d.grade}` : "",
2358
+ `Period: ${d.period}`,
2359
+ `Commits: ${d.commitCount}`,
2360
+ `Files changed: ${d.filesChanged}`,
2361
+ d.topCommits.length > 0 ? `Recent commits: ${d.topCommits.slice(0, 4).join("; ")}` : ""
2362
+ ].filter(Boolean);
2363
+ if (amberContext && amberContext.capabilities.length > 0) {
2364
+ lines.push(
2365
+ ``,
2366
+ `Capabilities: ${amberContext.capabilities.map((c) => c.name).join(", ")}`,
2367
+ `Total capabilities: ${amberContext.capabilities.length}`
2368
+ );
2369
+ }
2370
+ return lines.join("\n");
2371
+ }
2372
+ async function generateLlmSections(opts) {
2373
+ const context = buildLlmContext(opts);
2374
+ const systemPrompt = `You are an expert software architect generating arc42 architecture documentation.
2375
+ Write concise, informative Markdown content for each section.
2376
+ Base your content on the provided PRISM architecture data and project information.
2377
+ Use professional, clear language appropriate for technical documentation.
2378
+ Do not reproduce copyrighted content. All insights are derived from the provided data.`;
2379
+ const userPrompt = `Generate four arc42 documentation sections for this project.
2380
+
2381
+ Project data:
2382
+ ${context}
2383
+
2384
+ Generate the following four sections in Markdown. Each section should start with the exact header shown.
2385
+ Keep each section focused and practical \u2014 100\u2013300 words per section.
2386
+
2387
+ ---
2388
+ ## 1. Introduction and Goals
2389
+
2390
+ Write an overview of:
2391
+ - What the system does (based on the project name, description, and capabilities)
2392
+ - Key quality goals (2\u20133 measurable goals)
2393
+ - Key stakeholders and their expectations
2394
+
2395
+ ---
2396
+ ## 3. Context and Scope
2397
+
2398
+ Write:
2399
+ - A system context description (what the system does, what it connects to)
2400
+ - External systems and actors that interact with the system
2401
+ - What is explicitly out of scope
2402
+
2403
+ ---
2404
+ ## 4. Solution Strategy
2405
+
2406
+ Write:
2407
+ - Core technology decisions and why
2408
+ - Key architectural patterns used (infer from the tech stack and capability structure)
2409
+ - How quality goals are addressed by the architecture
2410
+
2411
+ ---
2412
+ ## 8. Crosscutting Concepts
2413
+
2414
+ Identify crosscutting concerns from the capability names and architecture:
2415
+ - Security / authentication approach
2416
+ - Error handling and logging
2417
+ - Caching strategy (if applicable)
2418
+ - Testing approach
2419
+ - Any other crosscutting patterns visible from the capability structure
2420
+ `;
2421
+ const response = await opts.llm.complete({
2422
+ systemPrompt,
2423
+ messages: [{ role: "user", content: userPrompt }],
2424
+ maxTokens: 4096
2425
+ });
2426
+ const text = response.content;
2427
+ function extractSection(marker, nextMarker) {
2428
+ const start = text.indexOf(marker);
2429
+ if (start === -1) return "";
2430
+ const end = nextMarker ? text.indexOf(nextMarker, start + marker.length) : text.length;
2431
+ return end === -1 ? text.slice(start).trim() : text.slice(start, end).trim();
2432
+ }
2433
+ const s1 = extractSection("## 1. Introduction", "## 3. Context");
2434
+ const s3 = extractSection("## 3. Context", "## 4. Solution");
2435
+ const s4 = extractSection("## 4. Solution", "## 8. Crosscutting");
2436
+ const s8 = extractSection("## 8. Crosscutting", "");
2437
+ return { s1, s3, s4, s8 };
2438
+ }
2439
+ function fallbackSection1(opts) {
2440
+ const { projectName, projectDescription, amberContext } = opts;
2441
+ const capCount = amberContext?.capabilities.length ?? 0;
2442
+ return `## 1. Introduction and Goals
2443
+
2444
+ **${projectName}** ${projectDescription ?? "is a software system documented using arc42."}
2445
+
2446
+ ### Quality Goals
2447
+
2448
+ | Priority | Quality Goal | Scenario |
2449
+ |----------|-------------|----------|
2450
+ | 1 | Correctness | System produces correct results |
2451
+ | 2 | Maintainability | New features can be added without regression |
2452
+ | 3 | Performance | System responds within acceptable time |
2453
+
2454
+ ### Stakeholders
2455
+
2456
+ | Role | Expectations |
2457
+ |------|-------------|
2458
+ | Development Team | Clear architecture structure, ${capCount} documented capabilities |
2459
+ | Product Owner | Feature delivery aligned with roadmap |
2460
+ | Operations | System is deployable and observable |
2461
+
2462
+ *Generated from PRISM data \u2014 enrich with project-specific goals.*
2463
+ `;
2464
+ }
2465
+ function fallbackSection3(opts) {
2466
+ const { projectName } = opts;
2467
+ return `## 3. Context and Scope
2468
+
2469
+ ### System Context
2470
+
2471
+ **${projectName}** interacts with the following external systems:
2472
+
2473
+ | External System | Description | Relationship |
2474
+ |----------------|-------------|-------------|
2475
+ | *[Users / Clients]* | End users of the system | Consumer |
2476
+ | *[External API / Service]* | *[Describe]* | Integration |
2477
+ | *[Database]* | Data persistence | Provider |
2478
+
2479
+ ### In Scope
2480
+
2481
+ *[Describe what the system is responsible for.]*
2482
+
2483
+ ### Out of Scope
2484
+
2485
+ *[Describe what is deliberately excluded.]*
2486
+ `;
2487
+ }
2488
+ function fallbackSection4(opts) {
2489
+ const { techStack } = opts;
2490
+ const stack = techStack && techStack.length > 0 ? techStack.join(", ") : "TypeScript, Next.js";
2491
+ return `## 4. Solution Strategy
2492
+
2493
+ ### Technology Decisions
2494
+
2495
+ **Tech stack:** ${stack}
2496
+
2497
+ | Decision | Rationale |
2498
+ |----------|-----------|
2499
+ | *[Framework choice]* | *[Why this framework]* |
2500
+ | *[Database choice]* | *[Why this DB]* |
2501
+ | *[Architecture pattern]* | *[Why this pattern]* |
2502
+
2503
+ ### Architectural Approach
2504
+
2505
+ *Describe the core architectural approach \u2014 e.g. monolith, microservices, event-driven, CQRS.*
2506
+
2507
+ ### Quality Goal Mapping
2508
+
2509
+ | Quality Goal | Architecture Response |
2510
+ |-------------|----------------------|
2511
+ | Correctness | *[How architecture ensures correctness]* |
2512
+ | Maintainability | *[How architecture supports maintainability]* |
2513
+ `;
2514
+ }
2515
+ function fallbackSection8(opts) {
2516
+ return `## 8. Crosscutting Concepts
2517
+
2518
+ ### Security
2519
+
2520
+ *Describe the authentication and authorization approach.*
2521
+
2522
+ ### Error Handling
2523
+
2524
+ *Describe the error handling and logging strategy.*
2525
+
2526
+ ### Observability
2527
+
2528
+ *Describe how the system is monitored \u2014 logs, metrics, tracing.*
2529
+
2530
+ ### Testing Strategy
2531
+
2532
+ *Describe the testing levels \u2014 unit, integration, end-to-end.*
2533
+
2534
+ ### Configuration
2535
+
2536
+ *Describe how the system is configured across environments.*
2537
+ `;
2538
+ }
2539
+ async function generateArc42(opts) {
2540
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
2541
+ const s5Content = buildSection5(opts);
2542
+ const s9Content = buildSection9(opts);
2543
+ const s10Content = buildSection10(opts);
2544
+ const s11Content = buildSection11(opts);
2545
+ const s12Content = buildSection12(opts);
2546
+ const s2Content = buildSection2();
2547
+ const s6Content = buildSection6();
2548
+ const s7Content = buildSection7();
2549
+ let s1Content;
2550
+ let s3Content;
2551
+ let s4Content;
2552
+ let s8Content;
2553
+ try {
2554
+ const llmSections = await generateLlmSections(opts);
2555
+ s1Content = llmSections.s1 || fallbackSection1(opts);
2556
+ s3Content = llmSections.s3 || fallbackSection3(opts);
2557
+ s4Content = llmSections.s4 || fallbackSection4(opts);
2558
+ s8Content = llmSections.s8 || fallbackSection8(opts);
2559
+ } catch {
2560
+ s1Content = fallbackSection1(opts);
2561
+ s3Content = fallbackSection3(opts);
2562
+ s4Content = fallbackSection4(opts);
2563
+ s8Content = fallbackSection8();
2564
+ }
2565
+ const sections = [
2566
+ { number: 1, title: "Introduction and Goals", content: s1Content, dataSource: "llm" },
2567
+ { number: 2, title: "Constraints", content: s2Content, dataSource: "template" },
2568
+ { number: 3, title: "Context and Scope", content: s3Content, dataSource: "llm" },
2569
+ { number: 4, title: "Solution Strategy", content: s4Content, dataSource: "llm" },
2570
+ { number: 5, title: "Building Block View", content: s5Content, dataSource: "prism" },
2571
+ { number: 6, title: "Runtime View", content: s6Content, dataSource: "template" },
2572
+ { number: 7, title: "Deployment View", content: s7Content, dataSource: "template" },
2573
+ { number: 8, title: "Crosscutting Concepts", content: s8Content, dataSource: "llm" },
2574
+ { number: 9, title: "Architecture Decisions", content: s9Content, dataSource: "prism" },
2575
+ { number: 10, title: "Quality Requirements", content: s10Content, dataSource: "prism" },
2576
+ { number: 11, title: "Risks and Technical Debt", content: s11Content, dataSource: "prism" },
2577
+ { number: 12, title: "Glossary", content: s12Content, dataSource: "prism" }
2578
+ ];
2579
+ const fullMarkdown = [
2580
+ `<!-- This document uses the arc42 template \xA9 arc42.org, licensed under CC-BY 4.0 -->`,
2581
+ `<!-- Generated by PRISM0x2A + forge0x2B \u2014 Architecture intelligence -->`,
2582
+ `<!-- ${generatedAt} -->`,
2583
+ ``,
2584
+ `# Architecture Documentation \u2014 ${opts.projectName}`,
2585
+ ``,
2586
+ `> ${ARC42_ATTRIBUTION}`,
2587
+ ``,
2588
+ ...sections.map((s) => s.content)
2589
+ ].join("\n\n");
2590
+ return {
2591
+ sections,
2592
+ fullMarkdown,
2593
+ attribution: ARC42_ATTRIBUTION,
2594
+ generatedAt
2595
+ };
2596
+ }
2597
+
2598
+ // src/generators/generateKnowledgeCapture.ts
2599
+ function buildContext5(opts) {
2600
+ const { digestContext: d, amberContext, projectName, departingDeveloper, focusCapabilities } = opts;
2601
+ const lines = [
2602
+ `## Knowledge Capture Context`,
2603
+ `Project: ${projectName}`,
2604
+ departingDeveloper ? `Departing Developer: ${departingDeveloper}` : `Departing Developer: (not specified \u2014 write for general knowledge transfer)`,
2605
+ focusCapabilities && focusCapabilities.length > 0 ? `Focus Capabilities: ${focusCapabilities.join(", ")}` : `Focus Capabilities: all`,
2606
+ ``,
2607
+ `## Recent Activity \u2014 ${d.period}`,
2608
+ `Commits: ${d.commitCount}`,
2609
+ `Files changed: ${d.filesChanged}`,
2610
+ `Architecture grade: ${d.grade ?? "N/A"}`,
2611
+ `Health: ${d.healthSummary}`,
2612
+ ``
2613
+ ];
2614
+ if (d.topCommits.length > 0) {
2615
+ lines.push(`### Recent Commits`, ...d.topCommits.map((c) => `- ${c}`), ``);
2616
+ }
2617
+ if (d.newDrifts.length > 0) {
2618
+ lines.push(
2619
+ `### Drifted Capabilities (need documentation attention)`,
2620
+ ...d.newDrifts.map((c) => `- ${c}`),
2621
+ ``
2622
+ );
2623
+ }
2624
+ if (d.newRisks.length > 0) {
2625
+ lines.push(`### Known Risks`, ...d.newRisks.map((r) => `- ${r}`), ``);
2626
+ }
2627
+ if (amberContext) {
2628
+ lines.push(`## AMBER Capability Registry`);
2629
+ lines.push(`Total capabilities: ${amberContext.capabilities.length}`);
2630
+ lines.push(`Drifted: ${amberContext.driftedCapabilities}`);
2631
+ lines.push(`Tagged files: ${amberContext.taggedFiles} / ${amberContext.totalFiles} (${amberContext.taggedPercent}%)`);
2632
+ lines.push(``);
2633
+ const toInclude = focusCapabilities && focusCapabilities.length > 0 ? amberContext.capabilities.filter(
2634
+ (c) => focusCapabilities.some((f) => c.name.toLowerCase().includes(f.toLowerCase()) || c.id.toLowerCase().includes(f.toLowerCase()))
2635
+ ) : amberContext.capabilities;
2636
+ if (toInclude.length > 0) {
2637
+ lines.push(`### Capabilities`);
2638
+ for (const cap of toInclude.slice(0, 20)) {
2639
+ lines.push(`#### ${cap.name} (${cap.id})`);
2640
+ lines.push(`- Criticality: ${cap.criticality}`);
2641
+ lines.push(`- Lifecycle: ${cap.lifecycle}`);
2642
+ lines.push(`- Files: ${cap.files.length}`);
2643
+ lines.push(`- Drift: ${cap.driftCount} file${cap.driftCount !== 1 ? "s" : ""} out of date`);
2644
+ if (cap.description) lines.push(`- Description: ${cap.description}`);
2645
+ lines.push(``);
278
2646
  }
279
- return;
280
2647
  }
281
- if (visited.has(node)) return;
282
- visited.add(node);
283
- stack.add(node);
284
- for (const neighbor of graph.get(node) ?? []) {
285
- dfs(neighbor, [...path, node]);
2648
+ if (amberContext.orphanedFiles.length > 0) {
2649
+ lines.push(
2650
+ `### Orphaned Files (no capability tag)`,
2651
+ ...amberContext.orphanedFiles.slice(0, 10).map((f) => `- ${f}`),
2652
+ ``
2653
+ );
286
2654
  }
287
- stack.delete(node);
288
- }
289
- for (const node of graph.keys()) {
290
- if (!visited.has(node)) dfs(node, []);
291
2655
  }
292
- return cycles;
2656
+ return lines.join("\n");
293
2657
  }
294
- function buildUserPrompt5(blueprint, opts) {
295
- const tone = opts.tone ?? "analytical";
296
- const length = opts.length ?? "medium";
297
- const format = opts.format ?? "markdown";
298
- const hotFiles = blueprint.files.slice().sort((a, b) => (b.importedByCount ?? 0) + (b.importCount ?? 0) - ((a.importedByCount ?? 0) + (a.importCount ?? 0))).slice(0, 10);
299
- const highFanOut = blueprint.files.slice().sort((a, b) => (b.importCount ?? 0) - (a.importCount ?? 0)).slice(0, 8);
300
- const importCycles = detectImportCycles(blueprint.edges);
301
- const lines = [
302
- `Generate a refactoring report in ${format} format for the codebase at "${blueprint.targetPath}".`,
303
- `Tone: ${tone}. Length: ${length}.`,
2658
+ function buildSystemPrompt13(opts) {
2659
+ const devName = opts.departingDeveloper ?? "a departing developer";
2660
+ return [
2661
+ `You are generating a Knowledge Capture document \u2014 a complete architectural brain dump before ${devName} leaves the team.`,
2662
+ `This document is crucial. It preserves institutional knowledge that would otherwise walk out the door.`,
2663
+ `Write with warmth and humanity \u2014 this is someone's legacy. Be thorough, specific, and genuinely useful.`,
2664
+ `Make it feel like ${devName} wrote it themselves, with care for the next person who will maintain this system.`,
2665
+ `Generate ONLY valid JSON \u2014 no markdown fences, no prose, no commentary outside the JSON object.`
2666
+ ].join(" ");
2667
+ }
2668
+ function buildUserPrompt13(opts) {
2669
+ const context = buildContext5(opts);
2670
+ const devName = opts.departingDeveloper ?? "the departing developer";
2671
+ return [
2672
+ `Generate a complete Knowledge Capture document for: ${opts.projectName}`,
2673
+ `This preserves ${devName}'s architectural knowledge for the team.`,
304
2674
  ``,
305
- `## Architecture Metrics`,
306
- `- Total files: ${blueprint.stats.totalFiles}`,
307
- `- Dependency edges: ${blueprint.stats.runtimeEdges}`,
308
- `- Categories: ${JSON.stringify(blueprint.categories)}`,
2675
+ context,
309
2676
  ``,
310
- `## High-Coupling Hot Spots (high total connections)`,
311
- ...hotFiles.map((f) => `- ${f.path} \u2014 in: ${f.importedByCount ?? 0}, out: ${f.importCount ?? 0}, lines: ${f.lineCount ?? "?"}`),
2677
+ `## Required Document Structure`,
2678
+ `The document must cover these 6 sections:`,
2679
+ `1. Architecture Overview \u2014 what the system does, how capabilities relate to each other, the mental model`,
2680
+ `2. Critical Capabilities \u2014 deep dive on the most important/drifted/complex capabilities, what makes them tricky`,
2681
+ `3. Known Issues & Workarounds \u2014 things that don't work as expected, temporary hacks, "do not touch" zones`,
2682
+ `4. Where to Start \u2014 recommended reading order for a new developer, which files to read first`,
2683
+ `5. Tribal Knowledge \u2014 things not obvious from code: why decisions were made, what was tried and failed, hidden dependencies`,
2684
+ `6. Onboarding Checklist \u2014 concrete 10-step list for a new team member joining this project`,
312
2685
  ``,
313
- `## High Fan-Out Files (many outgoing dependencies)`,
314
- ...highFanOut.map((f) => `- ${f.path} \u2014 imports: ${f.importCount ?? 0}`),
2686
+ `## Output Format`,
2687
+ `Return a single JSON object with this exact shape:`,
2688
+ `{`,
2689
+ ` "sections": [`,
2690
+ ` { "title": "<section title>", "content": "<full markdown content for this section, multiple paragraphs>" }`,
2691
+ ` ],`,
2692
+ ` "criticalKnowledge": ["<most important thing 1>", "<most important thing 2>", ...],`,
2693
+ ` "onboardingChecklist": ["<step 1: specific action>", "<step 2: specific action>", ...]`,
2694
+ `}`,
315
2695
  ``,
316
- `## Import Cycles Detected (${importCycles.length})`,
317
- ...importCycles.length > 0 ? importCycles.map((c) => `- ${c}`) : ["No cycles detected in sampled edges"],
2696
+ `Rules:`,
2697
+ `- sections: exactly 6, in the order above, with rich content (not just bullets)`,
2698
+ `- criticalKnowledge: 5-8 bullets \u2014 the things a new developer MUST know or they will make mistakes`,
2699
+ `- onboardingChecklist: exactly 10 items \u2014 concrete, actionable steps (not "read the README", but "read src/auth/ starting with session.ts")`,
2700
+ `- Write as if ${devName} is speaking directly to their replacement \u2014 warm, honest, specific`,
2701
+ `- Include file paths and capability names from the context where relevant`,
2702
+ `- Derive all content from the context \u2014 no invented facts`,
2703
+ `- Return ONLY valid JSON. No markdown fences. No prose outside the JSON.`
2704
+ ].join("\n");
2705
+ }
2706
+ function sectionsToMarkdown(sections, opts, criticalKnowledge, onboardingChecklist) {
2707
+ const devName = opts.departingDeveloper ?? "the team";
2708
+ const date = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" });
2709
+ const parts = [
2710
+ `# Knowledge Capture: ${opts.projectName}`,
318
2711
  ``,
319
- `Identify refactoring priorities: coupling issues, over-large files, circular dependencies, layer violations. Suggest concrete refactoring actions with rationale.`
2712
+ `> **Prepared by:** ${devName} `,
2713
+ `> **Date:** ${date} `,
2714
+ `> **Purpose:** Preserve architectural knowledge for the next maintainer`,
2715
+ ``,
2716
+ `---`,
2717
+ ``,
2718
+ `## TL;DR \u2014 Critical Knowledge`,
2719
+ ``,
2720
+ ...criticalKnowledge.map((k) => `- ${k}`),
2721
+ ``,
2722
+ `---`,
2723
+ ``
320
2724
  ];
321
- return lines.join("\n");
322
- }
323
- async function generateRefactoringReport(blueprint, opts, provider) {
324
- if (!blueprint) {
325
- return {
326
- text: NO_DATA_MSG4,
327
- metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), usedTokens: 0, generator: "forgesmith" }
328
- };
2725
+ for (const section of sections) {
2726
+ parts.push(`## ${section.title}`, ``, section.content, ``, `---`, ``);
329
2727
  }
330
- const response = await provider.complete({
331
- systemPrompt: buildSystemPrompt5(opts.tone ?? "analytical"),
332
- messages: [{ role: "user", content: buildUserPrompt5(blueprint, opts) }],
333
- maxTokens: 3072
334
- });
335
- return {
336
- text: response.content,
337
- metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), usedTokens: response.usedTokens, generator: "forgesmith" }
338
- };
339
- }
340
-
341
- // src/generators/askDrivenAsset.ts
342
- var NO_DATA_MSG5 = "No Blueprint data available. Run prism scan first.";
343
- var FORMAT_GUIDES = {
344
- markdown: { name: "Markdown document", structure: "Use headers, bullet lists, and code blocks where appropriate.", maxTokens: 3072 },
345
- blog: { name: "blog post", structure: "Write with an engaging intro, clear sections, a conclusion, and a call-to-action.", maxTokens: 3072 },
346
- social: { name: "social media post", structure: "Write concise, punchy content suitable for Twitter/LinkedIn. Max 280 characters for Twitter mode.", maxTokens: 512 },
347
- email: { name: "email", structure: "Use Subject:, greeting, body paragraphs, and a sign-off.", maxTokens: 1024 },
348
- slack: { name: "Slack message", structure: "Keep it conversational, use *bold* for emphasis, bullet points for lists. Max 3 paragraphs.", maxTokens: 512 },
349
- slide: { name: "presentation outline", structure: "Structure as slide titles with 3-5 bullet points each. Include a title slide and summary slide.", maxTokens: 2048 }
350
- };
351
- function buildSystemPrompt6(format, tone) {
352
- const toneMap = {
353
- professional: "You are a professional technical writer and developer advocate.",
354
- casual: "You are a friendly engineering blogger who writes in an approachable, conversational style.",
355
- technical: "You are a senior software engineer writing precise, implementation-focused content.",
356
- executive: "You are a VP of Engineering writing high-level, business-value-focused content for leadership."
357
- };
358
- const guide = FORMAT_GUIDES[format];
359
- return `${toneMap[tone] ?? toneMap.professional} Generate a ${guide.name} based on the user's question and the provided codebase architecture context. ${guide.structure} Write only the requested content \u2014 no preamble, no meta-commentary.`;
360
- }
361
- function buildUserPrompt6(blueprint, question, opts) {
362
- const format = opts.format ?? "markdown";
363
- const tone = opts.tone ?? "professional";
364
- const length = opts.length ?? "medium";
365
- const guide = FORMAT_GUIDES[format];
366
- const lengthGuide = {
367
- short: "Keep it concise \u2014 1-2 paragraphs or equivalent.",
368
- medium: "Medium length \u2014 3-5 paragraphs or equivalent.",
369
- long: "Detailed and comprehensive \u2014 cover the topic thoroughly."
370
- }[length];
371
- const topFiles = blueprint.files.slice().sort((a, b) => (b.importedByCount ?? 0) - (a.importedByCount ?? 0)).slice(0, 12);
372
- const lines = [
373
- `## User's Question`,
374
- question,
2728
+ parts.push(
2729
+ `## Onboarding Checklist`,
375
2730
  ``,
376
- `## Output Requirements`,
377
- `- Format: ${guide.name}`,
378
- `- Tone: ${tone}`,
379
- `- Length: ${length} (${lengthGuide})`,
2731
+ `For the next person joining this project:`,
380
2732
  ``,
381
- `## Codebase Architecture Context`,
382
- `Target: ${blueprint.targetPath}`,
383
- `Total files: ${blueprint.stats.totalFiles} | Dependency edges: ${blueprint.stats.runtimeEdges}`,
384
- `Categories: app=${blueprint.categories.app ?? 0}, components=${blueprint.categories.component ?? 0}, lib=${blueprint.categories.lib ?? 0}, hooks=${blueprint.categories.hook ?? 0}`,
2733
+ ...onboardingChecklist.map((step, i) => `- [ ] **Step ${i + 1}:** ${step}`),
385
2734
  ``,
386
- `Key files (by usage):`,
387
- ...topFiles.map((f) => `- ${f.path} [${f.category ?? "?"}] \u2014 imported by ${f.importedByCount ?? 0} files, ${f.lineCount ?? "?"} lines`)
2735
+ `---`,
2736
+ ``,
2737
+ `*Generated with [forge0x2B](https://forge0x2b.dev) \xB7 Architecture intelligence for engineering teams*`
2738
+ );
2739
+ return parts.join("\n");
2740
+ }
2741
+ function buildFallback7(opts) {
2742
+ const { digestContext: d, amberContext, projectName, departingDeveloper } = opts;
2743
+ const critCaps = amberContext?.capabilities.filter((c) => c.criticality === "critical" || c.driftCount > 0).slice(0, 5).map((c) => c.name) ?? [];
2744
+ const sections = [
2745
+ {
2746
+ title: "Architecture Overview",
2747
+ content: `${projectName} is composed of ${amberContext?.capabilities.length ?? "several"} tracked capabilities. ${d.healthSummary}
2748
+
2749
+ The system currently has ${d.filesChanged} recently changed files and a coherence grade of ${d.grade ?? "unknown"}.`
2750
+ },
2751
+ {
2752
+ title: "Critical Capabilities",
2753
+ content: critCaps.length > 0 ? `The following capabilities require the most attention:
2754
+
2755
+ ${critCaps.map((c) => `**${c}** \u2014 review all files tagged with this capability`).join("\n\n")}` : `Review the AMBER capability registry for the current capability map and any drift indicators.`
2756
+ },
2757
+ {
2758
+ title: "Known Issues & Workarounds",
2759
+ content: d.newRisks.length > 0 ? `The following risks have been identified:
2760
+
2761
+ ${d.newRisks.map((r) => `- **${r}**: Review related files before making changes`).join("\n")}` : `No critical known issues at time of writing. Check the PRISM risk registry for the latest state.`
2762
+ },
2763
+ {
2764
+ title: "Where to Start",
2765
+ content: `Start by understanding the capability structure. Read \`.amber/capabilities.md\` for the full capability registry, then \`.amber/state.json\` for file-to-capability mappings.
2766
+
2767
+ Focus on capabilities with high drift counts first \u2014 those are where documentation has fallen behind the code.`
2768
+ },
2769
+ {
2770
+ title: "Tribal Knowledge",
2771
+ content: `Key things not obvious from reading the code:
2772
+
2773
+ - The AMBER tags in source files are the source of truth for capability ownership
2774
+ - Drift count indicates documentation debt \u2014 files changed without updating @amber-doc tags
2775
+ - Architecture score changes over time \u2014 check \`.green/\` for trend data`
2776
+ },
2777
+ {
2778
+ title: "Architecture Decisions",
2779
+ content: `${departingDeveloper ? `${departingDeveloper} made` : "Key"} architectural decisions are not yet fully documented. Recommended: schedule a knowledge transfer session and ask specifically about: capability boundaries, cross-capability dependencies, and any "do not touch" areas.`
2780
+ }
388
2781
  ];
389
- if (blueprint.edges.length > 0) {
390
- const edgeSample = blueprint.edges.slice(0, 15);
391
- lines.push(``, `Dependency edges (sample):`, ...edgeSample.map((e) => `- ${e.from} \u2192 ${e.to}`));
392
- }
393
- lines.push(``, `Answer the user's question using the architecture context above. Generate the ${guide.name} now.`);
394
- return lines.join("\n");
2782
+ const criticalKnowledge = [
2783
+ `The AMBER capability registry (.amber/) is the source of truth for how the codebase is organized`,
2784
+ `Drift count > 0 means documentation hasn't kept pace with code changes \u2014 fix before adding features`,
2785
+ amberContext ? `${amberContext.driftedCapabilities} of ${amberContext.capabilities.length} capabilities currently have drift` : `Check drift count before starting work`,
2786
+ d.newRisks.length > 0 ? `${d.newRisks.length} active risk${d.newRisks.length > 1 ? "s" : ""} identified: ${d.newRisks.slice(0, 2).join(", ")}` : `Monitor architecture score for regression`,
2787
+ `Architecture grade: ${d.grade ?? "unknown"} \u2014 understand what's driving this before making broad changes`
2788
+ ].filter(Boolean);
2789
+ const onboardingChecklist = [
2790
+ `Read this entire document before writing any code`,
2791
+ `Install and run PRISM to get a live architecture view`,
2792
+ `Review .amber/capabilities.md \u2014 understand every capability and its criticality`,
2793
+ `Check .amber/state.json to see which files belong to which capabilities`,
2794
+ `Run the test suite and confirm it passes before making changes`,
2795
+ `Read the top ${Math.min(d.topCommits.length || 5, 5)} recent commits to understand current momentum`,
2796
+ `Review capabilities with drift count > 0 \u2014 read both the code and the docs`,
2797
+ `Set up forge0x2B to get daily Architecture Radio briefings`,
2798
+ `Ask the team about any "do not touch" areas that aren't documented`,
2799
+ `Make your first PR small and well-scoped to verify your understanding`
2800
+ ];
2801
+ const markdownDoc = sectionsToMarkdown(sections, opts, criticalKnowledge, onboardingChecklist);
2802
+ return { markdownDoc, sections, criticalKnowledge, onboardingChecklist };
395
2803
  }
396
- async function generateAskDrivenAsset(blueprint, question, opts, provider) {
397
- if (!blueprint) {
398
- return {
399
- text: NO_DATA_MSG5,
400
- metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), usedTokens: 0, generator: "forgesmith" }
401
- };
2804
+ function parseLlmResponse7(raw, opts) {
2805
+ let text = raw.trim();
2806
+ if (text.startsWith("```")) {
2807
+ text = text.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
402
2808
  }
403
- if (!question || !question.trim()) {
2809
+ let parsed;
2810
+ try {
2811
+ parsed = JSON.parse(text);
2812
+ } catch {
2813
+ return null;
2814
+ }
2815
+ const rawSections = Array.isArray(parsed.sections) ? parsed.sections : [];
2816
+ const sections = rawSections.filter((s) => typeof s.title === "string").map((s) => {
2817
+ const item = s;
404
2818
  return {
405
- text: "No question provided. Please ask something about your codebase.",
406
- metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), usedTokens: 0, generator: "forgesmith" }
2819
+ title: item.title,
2820
+ content: typeof item.content === "string" ? item.content : ""
407
2821
  };
408
- }
409
- const format = opts.format ?? "markdown";
410
- const tone = opts.tone ?? "professional";
411
- const maxTokens = FORMAT_GUIDES[format].maxTokens;
412
- const response = await provider.complete({
413
- systemPrompt: buildSystemPrompt6(format, tone),
414
- messages: [{ role: "user", content: buildUserPrompt6(blueprint, question, opts) }],
415
- maxTokens
416
2822
  });
417
- return {
418
- text: response.content,
419
- metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), usedTokens: response.usedTokens, generator: "forgesmith" }
420
- };
2823
+ if (sections.length === 0) return null;
2824
+ const rawCritical = Array.isArray(parsed.criticalKnowledge) ? parsed.criticalKnowledge : [];
2825
+ const criticalKnowledge = rawCritical.filter((k) => typeof k === "string").map((k) => k);
2826
+ const rawChecklist = Array.isArray(parsed.onboardingChecklist) ? parsed.onboardingChecklist : [];
2827
+ const onboardingChecklist = rawChecklist.filter((k) => typeof k === "string").map((k) => k);
2828
+ const markdownDoc = sectionsToMarkdown(sections, opts, criticalKnowledge, onboardingChecklist);
2829
+ return { markdownDoc, sections, criticalKnowledge, onboardingChecklist };
2830
+ }
2831
+ async function generateKnowledgeCapture(options) {
2832
+ try {
2833
+ const response = await options.llm.complete({
2834
+ systemPrompt: buildSystemPrompt13(options),
2835
+ messages: [{ role: "user", content: buildUserPrompt13(options) }],
2836
+ maxTokens: 8192
2837
+ });
2838
+ const parsed = parseLlmResponse7(response.content, options);
2839
+ if (parsed) return parsed;
2840
+ } catch {
2841
+ }
2842
+ return buildFallback7(options);
421
2843
  }
422
2844
 
423
2845
  // src/forge/types.ts
@@ -1905,6 +4327,80 @@ var WIDGET_TEMPLATE_SOCIAL_PROOF = {
1905
4327
  { id: "show_divider", label: "Show divider", type: "select", options: ["yes", "no"], default: "no" }
1906
4328
  ]
1907
4329
  };
4330
+ var WIDGET_TEMPLATE_ANIMATED_STAT = {
4331
+ id: "animated-stat",
4332
+ name: "Animated Stat",
4333
+ description: "Full-card animated number counter. Perfect for LinkedIn/Instagram. Export as video or GIF.",
4334
+ free_tier: false,
4335
+ animated: true,
4336
+ animDurationMs: 2200,
4337
+ videoDurationMs: 3800,
4338
+ defaultWidth: 600,
4339
+ defaultHeight: 400,
4340
+ exportFormats: ["standalone", "html", "markdown"],
4341
+ slots: [
4342
+ { id: "value", label: "Value (number)", type: "number", required: true, placeholder: "138", default: "138" },
4343
+ { id: "label", label: "Label", type: "text", required: true, placeholder: "Files analysed", default: "Files analysed" },
4344
+ { id: "unit", label: "Unit (optional)", type: "text", placeholder: "k LOC", default: "" },
4345
+ { id: "sublabel", label: "Sublabel / context", type: "text", placeholder: "across 3 modules", default: "" },
4346
+ { id: "delta", label: "Delta text", type: "text", placeholder: "+22% this sprint", default: "" },
4347
+ { id: "delta_direction", label: "Delta direction", type: "select", options: ["up", "down", "neutral"], default: "up" }
4348
+ ]
4349
+ };
4350
+ var WIDGET_TEMPLATE_RELEASE_CARD = {
4351
+ id: "release-card",
4352
+ name: "Release Card",
4353
+ description: "Animated release announcement \u2014 version, headline, and staggered change list. Great for LinkedIn posts.",
4354
+ free_tier: false,
4355
+ animated: true,
4356
+ animDurationMs: 1200,
4357
+ videoDurationMs: 3e3,
4358
+ defaultWidth: 600,
4359
+ defaultHeight: 400,
4360
+ exportFormats: ["standalone", "html", "markdown"],
4361
+ slots: [
4362
+ { id: "version", label: "Version", type: "text", required: true, placeholder: "v2.4.0", default: "v2.4.0" },
4363
+ { id: "headline", label: "Headline", type: "text", required: true, placeholder: "Faster. Smarter. Leaner.", default: "Faster. Smarter. Leaner." },
4364
+ { id: "changes", label: "Changes (one per line)", type: "multiline", required: true, placeholder: "40% faster cold starts\nNew Capability Registry\nFixed circular import detection", default: "40% faster cold starts\nNew Capability Registry\nFixed circular import detection" },
4365
+ { id: "tag", label: "Release tag", type: "select", options: ["feat", "fix", "perf", "break", ""], default: "feat" }
4366
+ ]
4367
+ };
4368
+ var WIDGET_TEMPLATE_CHART_BARS = {
4369
+ id: "chart-bars",
4370
+ name: "Chart \u2014 Bars",
4371
+ description: "Animated Chart.js bar chart from your codebase metrics. Perfect for visualising churn, coverage, or LOC.",
4372
+ free_tier: false,
4373
+ animated: true,
4374
+ animDurationMs: 1600,
4375
+ videoDurationMs: 3200,
4376
+ defaultWidth: 640,
4377
+ defaultHeight: 420,
4378
+ exportFormats: ["standalone", "html", "markdown"],
4379
+ slots: [
4380
+ { id: "title", label: "Chart title", type: "text", placeholder: "Files by module", default: "Files by module" },
4381
+ { id: "labels", label: "Labels (one per line)", type: "multiline", required: true, placeholder: "api\nlib\ncomponents\napp", default: "api\nlib\ncomponents\napp" },
4382
+ { id: "values", label: "Values (one per line)", type: "multiline", required: true, placeholder: "24\n18\n41\n12", default: "24\n18\n41\n12" },
4383
+ { id: "orientation", label: "Orientation", type: "select", options: ["vertical", "horizontal"], default: "vertical" },
4384
+ { id: "accent_color", label: "Bar colour (CSS)", type: "color", placeholder: "#f97316", default: "" }
4385
+ ]
4386
+ };
4387
+ var WIDGET_TEMPLATE_ARCHITECTURE_BADGE = {
4388
+ id: "architecture-badge",
4389
+ name: "Architecture Badge",
4390
+ description: "Animated tech-stack showcase with Lucide icons. Items pop in with spring animation \u2014 great for profile posts.",
4391
+ free_tier: false,
4392
+ animated: true,
4393
+ animDurationMs: 900,
4394
+ videoDurationMs: 2500,
4395
+ defaultWidth: 580,
4396
+ defaultHeight: 360,
4397
+ exportFormats: ["standalone", "html", "markdown"],
4398
+ slots: [
4399
+ { id: "title", label: "Title", type: "text", placeholder: "Built with", default: "Built with" },
4400
+ { id: "items", label: "Stack items (one per line)", type: "multiline", required: true, placeholder: "TypeScript\nNext.js\nPostgres\nTailwind", default: "TypeScript\nNext.js\nPostgres\nTailwind" },
4401
+ { id: "subtitle", label: "Subtitle / tagline", type: "text", placeholder: "100% local-first \xB7 zero telemetry", default: "" }
4402
+ ]
4403
+ };
1908
4404
  var BUNDLED_WIDGET_TEMPLATES = [
1909
4405
  WIDGET_TEMPLATE_STAT_CARD,
1910
4406
  WIDGET_TEMPLATE_FEATURE_GRID,
@@ -1913,7 +4409,12 @@ var BUNDLED_WIDGET_TEMPLATES = [
1913
4409
  WIDGET_TEMPLATE_METRIC_BADGE,
1914
4410
  WIDGET_TEMPLATE_PRICING_TIER,
1915
4411
  WIDGET_TEMPLATE_CHANGELOG_ROW,
1916
- WIDGET_TEMPLATE_SOCIAL_PROOF
4412
+ WIDGET_TEMPLATE_SOCIAL_PROOF,
4413
+ // Animated — standalone + video export
4414
+ WIDGET_TEMPLATE_ANIMATED_STAT,
4415
+ WIDGET_TEMPLATE_RELEASE_CARD,
4416
+ WIDGET_TEMPLATE_CHART_BARS,
4417
+ WIDGET_TEMPLATE_ARCHITECTURE_BADGE
1917
4418
  ];
1918
4419
  var FREE_TIER_WIDGET_IDS = BUNDLED_WIDGET_TEMPLATES.filter((t) => t.free_tier).map((t) => t.id);
1919
4420
  function getWidgetTemplate(id) {
@@ -2072,6 +4573,184 @@ ${divider ? `<hr style="border:none;border-top:1px solid rgba(255,255,255,.08);m
2072
4573
  ${logos.map((l) => ` <span style="font-weight:600;font-size:.875rem;color:var(--pw-brand-muted);letter-spacing:-.01em">${escHtml(l)}</span>`).join("\n")}
2073
4574
  </div>`;
2074
4575
  }
4576
+ function renderAnimatedStatHtml(slots, t) {
4577
+ const value = getSlotValue(slots, t, "value");
4578
+ const label = getSlotValue(slots, t, "label");
4579
+ const unit = getSlotValue(slots, t, "unit");
4580
+ const sublabel = getSlotValue(slots, t, "sublabel");
4581
+ const dir = getSlotValue(slots, t, "delta_direction");
4582
+ const delta = getSlotValue(slots, t, "delta");
4583
+ const arrow = dir === "up" ? "\u2191" : dir === "down" ? "\u2193" : "";
4584
+ const deltaColor = dir === "up" ? "#22c55e" : dir === "down" ? "#ef4444" : "var(--pw-brand-muted)";
4585
+ return `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:240px;text-align:center;gap:var(--pw-spacing-sm)">
4586
+ <div id="pw-anim-value" style="font-size:5rem;font-weight:900;line-height:1;font-family:var(--pw-font-heading);color:var(--pw-brand-primary);letter-spacing:-0.04em">0</div>
4587
+ ${unit ? `<div style="font-size:1.25rem;font-weight:600;color:var(--pw-brand-muted);margin-top:-var(--pw-spacing-sm)">${escHtml(unit)}</div>` : ""}
4588
+ <div style="font-size:1.1rem;font-weight:600;color:var(--pw-brand-text);letter-spacing:0.01em">${escHtml(label)}</div>
4589
+ ${sublabel ? `<div style="font-size:.8rem;color:var(--pw-brand-muted)">${escHtml(sublabel)}</div>` : ""}
4590
+ ${delta ? `<div style="font-size:.875rem;color:${deltaColor};font-weight:600">${arrow}${arrow ? " " : ""}${escHtml(delta)}</div>` : ""}
4591
+ </div>
4592
+ <script>
4593
+ (function(){
4594
+ var target = ${Number(value.replace(/[^0-9.-]/g, "")) || 0};
4595
+ var isFloat = target % 1 !== 0;
4596
+ var obj = { val: 0 };
4597
+ anime({
4598
+ targets: obj,
4599
+ val: target,
4600
+ duration: ${2e3},
4601
+ easing: "easeOutExpo",
4602
+ update: function() {
4603
+ document.getElementById("pw-anim-value").textContent =
4604
+ isFloat ? obj.val.toFixed(1) : Math.round(obj.val).toLocaleString();
4605
+ }
4606
+ });
4607
+ })();
4608
+ </script>`;
4609
+ }
4610
+ function renderReleaseCardHtml(slots, t) {
4611
+ const version = getSlotValue(slots, t, "version");
4612
+ const headline = getSlotValue(slots, t, "headline");
4613
+ const changes = getSlotValue(slots, t, "changes").split("\n").filter(Boolean);
4614
+ const tag = getSlotValue(slots, t, "tag");
4615
+ const tagColors = { feat: "#22c55e", fix: "#f59e0b", perf: "#60a5fa", break: "#ef4444" };
4616
+ const tc = tagColors[tag] ?? "var(--pw-brand-accent)";
4617
+ return `<div class="pw-release-wrap" style="display:flex;flex-direction:column;gap:var(--pw-spacing-md)">
4618
+ <div style="display:flex;align-items:center;gap:var(--pw-spacing-sm);opacity:0" class="pw-ri">
4619
+ <span style="font-family:var(--pw-font-mono);font-size:1.1rem;font-weight:700;color:var(--pw-brand-primary)">${escHtml(version)}</span>
4620
+ ${tag ? `<span style="font-size:.7rem;padding:2px 8px;border-radius:var(--pw-radius-full);background:${tc}22;color:${tc};font-weight:700;text-transform:uppercase;letter-spacing:.06em">${escHtml(tag)}</span>` : ""}
4621
+ </div>
4622
+ <h2 style="margin:0;font-size:1.5rem;font-weight:800;line-height:1.25;font-family:var(--pw-font-heading);opacity:0" class="pw-ri">${escHtml(headline)}</h2>
4623
+ <ul style="margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:var(--pw-spacing-xs)">
4624
+ ${changes.map((c) => `<li class="pw-change-item" style="display:flex;align-items:flex-start;gap:var(--pw-spacing-xs);font-size:.9rem;opacity:0"><span style="color:${tc};flex-shrink:0;margin-top:2px">\u2192</span><span>${escHtml(c)}</span></li>`).join("\n ")}
4625
+ </ul>
4626
+ </div>
4627
+ <script>
4628
+ (function(){
4629
+ anime({ targets: ".pw-ri", translateY: [-16, 0], opacity: [0, 1], delay: anime.stagger(120), duration: 500, easing: "easeOutCubic" });
4630
+ anime({ targets: ".pw-change-item", translateX: [-20, 0], opacity: [0, 1], delay: anime.stagger(80, { start: 350 }), duration: 450, easing: "easeOutCubic" });
4631
+ })();
4632
+ </script>`;
4633
+ }
4634
+ function renderChartBarsHtml(slots, t) {
4635
+ const title = getSlotValue(slots, t, "title");
4636
+ const labelsRaw = getSlotValue(slots, t, "labels").split("\n").filter(Boolean);
4637
+ const valuesRaw = getSlotValue(slots, t, "values").split("\n").filter(Boolean).map(Number);
4638
+ const orientation = getSlotValue(slots, t, "orientation") || "vertical";
4639
+ getSlotValue(slots, t, "accent_color") || "var(--pw-brand-primary)";
4640
+ const labelsJson = JSON.stringify(labelsRaw);
4641
+ const valuesJson = JSON.stringify(valuesRaw);
4642
+ const chartType = orientation === "horizontal" ? "bar" : "bar";
4643
+ const indexAxis = orientation === "horizontal" ? `indexAxis: "y",` : "";
4644
+ return `${title ? `<h3 style="margin:0 0 var(--pw-spacing-md);font-size:1rem;font-weight:700;font-family:var(--pw-font-heading);color:var(--pw-brand-text)">${escHtml(title)}</h3>` : ""}
4645
+ <div style="position:relative;width:100%;height:220px">
4646
+ <canvas id="pw-chart" style="width:100%;height:100%"></canvas>
4647
+ </div>
4648
+ <script>
4649
+ (function(){
4650
+ var ctx = document.getElementById("pw-chart").getContext("2d");
4651
+ var accent = getComputedStyle(document.querySelector(".pw-widget")).getPropertyValue("--pw-brand-primary").trim() || "#f97316";
4652
+ new Chart(ctx, {
4653
+ type: "${chartType}",
4654
+ data: {
4655
+ labels: ${labelsJson},
4656
+ datasets: [{
4657
+ data: ${valuesJson},
4658
+ backgroundColor: accent + "cc",
4659
+ borderColor: accent,
4660
+ borderWidth: 2,
4661
+ borderRadius: 6,
4662
+ }]
4663
+ },
4664
+ options: {
4665
+ ${indexAxis}
4666
+ responsive: true,
4667
+ maintainAspectRatio: false,
4668
+ animation: { duration: 1500, easing: "easeOutQuart" },
4669
+ plugins: { legend: { display: false } },
4670
+ scales: {
4671
+ x: { grid: { color: "rgba(255,255,255,.06)" }, ticks: { color: "rgba(255,255,255,.5)", font: { size: 11 } } },
4672
+ y: { grid: { color: "rgba(255,255,255,.06)" }, ticks: { color: "rgba(255,255,255,.5)", font: { size: 11 } } }
4673
+ }
4674
+ }
4675
+ });
4676
+ })();
4677
+ </script>`;
4678
+ }
4679
+ var LUCIDE_ICON_MAP = {
4680
+ typescript: "code-2",
4681
+ javascript: "code-2",
4682
+ "js": "code-2",
4683
+ "ts": "code-2",
4684
+ react: "atom",
4685
+ next: "triangle",
4686
+ nextjs: "triangle",
4687
+ "next.js": "triangle",
4688
+ node: "server",
4689
+ nodejs: "server",
4690
+ "node.js": "server",
4691
+ postgres: "database",
4692
+ postgresql: "database",
4693
+ mysql: "database",
4694
+ sqlite: "database",
4695
+ supabase: "database",
4696
+ prisma: "layers",
4697
+ tailwind: "palette",
4698
+ css: "palette",
4699
+ sass: "palette",
4700
+ docker: "box",
4701
+ kubernetes: "cloud",
4702
+ aws: "cloud",
4703
+ vercel: "triangle",
4704
+ github: "git-branch",
4705
+ git: "git-branch",
4706
+ graphql: "share-2",
4707
+ rest: "globe",
4708
+ api: "globe",
4709
+ redis: "zap",
4710
+ kafka: "radio",
4711
+ python: "terminal",
4712
+ rust: "cpu",
4713
+ go: "activity",
4714
+ java: "coffee",
4715
+ vue: "layers",
4716
+ svelte: "layers",
4717
+ angular: "layers",
4718
+ figma: "pen-tool",
4719
+ linear: "layers",
4720
+ default: "layers"
4721
+ };
4722
+ function renderArchitectureBadgeHtml(slots, t) {
4723
+ const title = getSlotValue(slots, t, "title");
4724
+ const subtitle = getSlotValue(slots, t, "subtitle");
4725
+ const items = getSlotValue(slots, t, "items").split("\n").filter(Boolean);
4726
+ const palette = ["#60a5fa", "#f97316", "#34d399", "#a78bfa", "#fb923c", "#38bdf8", "#facc15", "#f472b6"];
4727
+ const chips = items.map((item, i) => {
4728
+ const key = item.trim().toLowerCase().replace(/[^a-z0-9.]/g, "");
4729
+ const icon = LUCIDE_ICON_MAP[key] ?? LUCIDE_ICON_MAP.default;
4730
+ const color = palette[i % palette.length];
4731
+ return `<div class="pw-arch-item" style="display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:var(--pw-radius-full);border:1px solid ${color}33;background:${color}11;opacity:0">
4732
+ <i data-lucide="${icon}" style="width:14px;height:14px;color:${color};flex-shrink:0"></i>
4733
+ <span style="font-size:.8rem;font-weight:600;color:${color};letter-spacing:.02em">${escHtml(item.trim())}</span>
4734
+ </div>`;
4735
+ }).join("\n ");
4736
+ return `<div style="display:flex;flex-direction:column;gap:var(--pw-spacing-md)">
4737
+ ${title ? `<h3 style="margin:0;font-size:1.1rem;font-weight:700;font-family:var(--pw-font-heading);color:var(--pw-brand-text);opacity:0" id="pw-arch-title">${escHtml(title)}</h3>` : ""}
4738
+ <div style="display:flex;flex-wrap:wrap;gap:var(--pw-spacing-xs)">
4739
+ ${chips}
4740
+ </div>
4741
+ ${subtitle ? `<p style="margin:0;font-size:.75rem;color:var(--pw-brand-muted);opacity:0" class="pw-arch-sub">${escHtml(subtitle)}</p>` : ""}
4742
+ </div>
4743
+ <script>
4744
+ (function(){
4745
+ lucide.createIcons();
4746
+ var title = document.getElementById("pw-arch-title");
4747
+ if(title) anime({ targets: title, opacity: [0,1], translateY: [-10,0], duration: 400, easing: "easeOutCubic" });
4748
+ anime({ targets: ".pw-arch-item", opacity: [0,1], scale: [0.85,1], delay: anime.stagger(70, { start: 200 }), duration: 400, easing: "easeOutBack" });
4749
+ var sub = document.querySelector(".pw-arch-sub");
4750
+ if(sub) anime({ targets: sub, opacity: [0,1], delay: 600, duration: 400, easing: "easeOutCubic" });
4751
+ })();
4752
+ </script>`;
4753
+ }
2075
4754
  var HTML_BODY_FN = {
2076
4755
  "stat-card": renderStatCardHtml,
2077
4756
  "feature-grid": renderFeatureGridHtml,
@@ -2080,7 +4759,11 @@ var HTML_BODY_FN = {
2080
4759
  "metric-badge": renderMetricBadgeHtml,
2081
4760
  "pricing-tier": renderPricingTierHtml,
2082
4761
  "changelog-row": renderChangelogRowHtml,
2083
- "social-proof": renderSocialProofHtml
4762
+ "social-proof": renderSocialProofHtml,
4763
+ "animated-stat": renderAnimatedStatHtml,
4764
+ "release-card": renderReleaseCardHtml,
4765
+ "chart-bars": renderChartBarsHtml,
4766
+ "architecture-badge": renderArchitectureBadgeHtml
2084
4767
  };
2085
4768
  function renderStatCardMd(slots, t) {
2086
4769
  const value = getSlotValue(slots, t, "value");
@@ -2151,6 +4834,43 @@ function renderSocialProofMd(slots, t) {
2151
4834
  const logos = getSlotValue(slots, t, "logos").split("\n").filter(Boolean);
2152
4835
  return `${caption ? caption + "\n\n" : ""}${logos.join(" \xB7 ")}`;
2153
4836
  }
4837
+ function renderAnimatedStatMd(slots, t) {
4838
+ const value = getSlotValue(slots, t, "value");
4839
+ const label = getSlotValue(slots, t, "label");
4840
+ const unit = getSlotValue(slots, t, "unit");
4841
+ const delta = getSlotValue(slots, t, "delta");
4842
+ const dir = getSlotValue(slots, t, "delta_direction");
4843
+ const arrow = dir === "up" ? "\u2191" : dir === "down" ? "\u2193" : "";
4844
+ return `**${value}${unit ? " " + unit : ""}** \u2014 ${label}${delta ? ` ${arrow} ${delta}` : ""}`;
4845
+ }
4846
+ function renderReleaseCardMd(slots, t) {
4847
+ const version = getSlotValue(slots, t, "version");
4848
+ const headline = getSlotValue(slots, t, "headline");
4849
+ const changes = getSlotValue(slots, t, "changes").split("\n").filter(Boolean);
4850
+ const tag = getSlotValue(slots, t, "tag");
4851
+ return `### ${version}${tag ? ` \`${tag}\`` : ""} \u2014 ${headline}
4852
+
4853
+ ${changes.map((c) => `- ${c}`).join("\n")}`;
4854
+ }
4855
+ function renderChartBarsMd(slots, t) {
4856
+ const title = getSlotValue(slots, t, "title");
4857
+ const labels = getSlotValue(slots, t, "labels").split("\n").filter(Boolean);
4858
+ const values = getSlotValue(slots, t, "values").split("\n").filter(Boolean);
4859
+ const rows = labels.map((l, i) => `- **${l}**: ${values[i] ?? "0"}`).join("\n");
4860
+ return `${title ? `## ${title}
4861
+
4862
+ ` : ""}${rows}`;
4863
+ }
4864
+ function renderArchitectureBadgeMd(slots, t) {
4865
+ const title = getSlotValue(slots, t, "title");
4866
+ const items = getSlotValue(slots, t, "items").split("\n").filter(Boolean);
4867
+ const subtitle = getSlotValue(slots, t, "subtitle");
4868
+ return `${title ? `**${title}**
4869
+
4870
+ ` : ""}${items.map((i) => `- ${i.trim()}`).join("\n")}${subtitle ? `
4871
+
4872
+ *${subtitle}*` : ""}`;
4873
+ }
2154
4874
  var MD_FN = {
2155
4875
  "stat-card": renderStatCardMd,
2156
4876
  "feature-grid": renderFeatureGridMd,
@@ -2159,7 +4879,11 @@ var MD_FN = {
2159
4879
  "metric-badge": renderMetricBadgeMd,
2160
4880
  "pricing-tier": renderPricingTierMd,
2161
4881
  "changelog-row": renderChangelogRowMd,
2162
- "social-proof": renderSocialProofMd
4882
+ "social-proof": renderSocialProofMd,
4883
+ "animated-stat": renderAnimatedStatMd,
4884
+ "release-card": renderReleaseCardMd,
4885
+ "chart-bars": renderChartBarsMd,
4886
+ "architecture-badge": renderArchitectureBadgeMd
2163
4887
  };
2164
4888
  function toReactStyle(vars) {
2165
4889
  return "{\n" + Object.entries(vars).map(([k, v]) => ` "${k}": "${v.replace(/"/g, '\\"')}"`).join(",\n") + "\n }";
@@ -2191,6 +4915,89 @@ ${slotDecls}
2191
4915
  }
2192
4916
  `;
2193
4917
  }
4918
+ function getRequiredLibs(templateId) {
4919
+ const libs = /* @__PURE__ */ new Set(["tailwind"]);
4920
+ if (["animated-stat", "release-card"].includes(templateId)) {
4921
+ libs.add("anime");
4922
+ }
4923
+ if (templateId === "chart-bars") {
4924
+ libs.add("anime");
4925
+ libs.add("chartjs");
4926
+ }
4927
+ if (templateId === "architecture-badge") {
4928
+ libs.add("anime");
4929
+ libs.add("lucide");
4930
+ }
4931
+ return Array.from(libs);
4932
+ }
4933
+ function buildCdnScripts(libs) {
4934
+ const tags = [];
4935
+ if (libs.includes("tailwind")) {
4936
+ tags.push(`<script>window.tailwind={config:{},silent:true}</script>`);
4937
+ tags.push(`<script src="https://cdn.tailwindcss.com"></script>`);
4938
+ }
4939
+ if (libs.includes("chartjs")) {
4940
+ tags.push(`<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.2/dist/chart.umd.min.js"></script>`);
4941
+ }
4942
+ if (libs.includes("anime")) {
4943
+ tags.push(`<script src="https://cdn.jsdelivr.net/npm/animejs@3.2.2/lib/anime.min.js"></script>`);
4944
+ }
4945
+ if (libs.includes("lucide")) {
4946
+ tags.push(`<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>`);
4947
+ }
4948
+ if (libs.includes("alpine")) {
4949
+ tags.push(`<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>`);
4950
+ }
4951
+ return tags.join("\n ");
4952
+ }
4953
+ function renderStandalone(template, slots, brand, style) {
4954
+ const bodyFn = HTML_BODY_FN[template.id];
4955
+ const body = bodyFn ? bodyFn(slots, template) : "";
4956
+ const allVars = { ...brandVars(brand), ...styleVars(style) };
4957
+ const inlineStyle = buildInlineStyle(allVars);
4958
+ const customCss = style.customCss ?? "";
4959
+ const override = style.templateOverrides?.[template.id]?.html;
4960
+ const finalBody = override ?? body;
4961
+ const libs = getRequiredLibs(template.id);
4962
+ const scripts = buildCdnScripts(libs);
4963
+ const w = template.defaultWidth ?? 600;
4964
+ const h = template.defaultHeight ?? 400;
4965
+ return `<!DOCTYPE html>
4966
+ <html lang="en">
4967
+ <head>
4968
+ <meta charset="UTF-8" />
4969
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
4970
+ <title>${escHtml(template.name)} \u2014 forge0x2B</title>
4971
+ ${scripts}
4972
+ <style>
4973
+ *, *::before, *::after { box-sizing: border-box; }
4974
+ html, body {
4975
+ margin: 0; padding: 0;
4976
+ width: ${w}px; height: ${h}px;
4977
+ overflow: hidden;
4978
+ background: transparent;
4979
+ display: flex; align-items: center; justify-content: center;
4980
+ }
4981
+ .pw-widget {
4982
+ font-family: var(--pw-font-body);
4983
+ color: var(--pw-brand-text);
4984
+ background: var(--pw-brand-surface);
4985
+ border-radius: var(--pw-radius-md);
4986
+ padding: var(--pw-spacing-md);
4987
+ border: var(--pw-border-width) solid rgba(255,255,255,.08);
4988
+ box-sizing: border-box;
4989
+ width: 100%; max-width: ${w}px;
4990
+ ${customCss}
4991
+ }
4992
+ </style>
4993
+ </head>
4994
+ <body>
4995
+ <div class="pw-widget pw-${template.id}" style="${inlineStyle}">
4996
+ ${finalBody}
4997
+ </div>
4998
+ </body>
4999
+ </html>`;
5000
+ }
2194
5001
  function renderWidget(input) {
2195
5002
  const { template, slots, brandKit, format } = input;
2196
5003
  const style = !input.style ? STYLE_PRESET_DEFAULT : typeof input.style === "string" ? getStyleById(input.style) : input.style;
@@ -2201,6 +5008,9 @@ function renderWidget(input) {
2201
5008
  if (format === "react") {
2202
5009
  return renderReact(template, slots, brandKit ?? null, style);
2203
5010
  }
5011
+ if (format === "standalone") {
5012
+ return renderStandalone(template, slots, brandKit ?? null, style);
5013
+ }
2204
5014
  const bodyFn = HTML_BODY_FN[template.id];
2205
5015
  const body = bodyFn ? bodyFn(slots, template) : "";
2206
5016
  const allVars = { ...brandVars(brandKit ?? null), ...styleVars(style) };
@@ -2556,7 +5366,7 @@ function getDispatchChannel(id) {
2556
5366
  }
2557
5367
 
2558
5368
  // src/forge/dispatch.ts
2559
- function buildSystemPrompt7(channel, audience, brand, blueprintContext, toneOffset) {
5369
+ function buildSystemPrompt14(channel, audience, brand, blueprintContext, toneOffset) {
2560
5370
  const lines = [
2561
5371
  `You are a skilled content writer producing a ${channel.name} post.`,
2562
5372
  "",
@@ -2607,7 +5417,7 @@ function truncate(content, maxLength) {
2607
5417
  async function generateForChannel(ask, channel, provider, opts) {
2608
5418
  const now = (/* @__PURE__ */ new Date()).toISOString();
2609
5419
  try {
2610
- const system = buildSystemPrompt7(
5420
+ const system = buildSystemPrompt14(
2611
5421
  channel,
2612
5422
  opts.audience,
2613
5423
  opts.brand,
@@ -2752,7 +5562,7 @@ function nextVersionNumber(versions) {
2752
5562
  }
2753
5563
 
2754
5564
  // src/forge/refineAsset.ts
2755
- function buildSystemPrompt8(input) {
5565
+ function buildSystemPrompt15(input) {
2756
5566
  const parts = [
2757
5567
  `You are a content refinement assistant for a developer-focused content tool (forge0x2B).`,
2758
5568
  `You are refining an existing ${input.assetType.replace(/-/g, " ")} asset.`,
@@ -2791,7 +5601,7 @@ function parseRefinedResponse(raw) {
2791
5601
  return { newContent, llmReply };
2792
5602
  }
2793
5603
  async function refineAsset(input, provider) {
2794
- const systemPrompt = buildSystemPrompt8(input);
5604
+ const systemPrompt = buildSystemPrompt15(input);
2795
5605
  if (scanForSecrets(systemPrompt)) {
2796
5606
  throw new Error("refineAsset: secret pattern detected in asset content. Refusing to send to LLM.");
2797
5607
  }
@@ -2953,4 +5763,4 @@ function previewExport(entries, format) {
2953
5763
  }
2954
5764
  }
2955
5765
 
2956
- export { ANIMATION_DURATION_PRESETS, BRAND_CONTENT_SLOT_KEYS, BUNDLED_DISPATCH_CHANNELS, BUNDLED_STYLE_PRESETS, BUNDLED_WIDGET_TEMPLATES, DEFAULT_ANIMATION_DURATION_SECONDS, DEFAULT_BRAND_KIT_FONTS, DEFAULT_BRAND_KIT_PALETTE, DEFAULT_BRAND_KIT_VOICE, DISPATCH_CHANNEL_BLOG, DISPATCH_CHANNEL_EMAIL, DISPATCH_CHANNEL_HN, DISPATCH_CHANNEL_INSTAGRAM, DISPATCH_CHANNEL_LINKEDIN, DISPATCH_CHANNEL_NEWSLETTER, DISPATCH_CHANNEL_REDDIT, DISPATCH_CHANNEL_SLACK, DISPATCH_CHANNEL_TWEET, FORGE_AUDIENCES, FORGE_BRAND_THEME_ID, FORGE_TEMPLATES, FREE_TIER_WIDGET_IDS, MAX_ANIMATION_DURATION_SECONDS, MIN_ANIMATION_DURATION_SECONDS, PRISM_TEMPLATES, PRISM_TEMPLATE_ARCHITECTURE_OVERVIEW, PRISM_TEMPLATE_REFACTOR_RATIONALE, PRISM_TEMPLATE_RELEASE_ANNOUNCEMENT, PRISM_TEMPLATE_SHIPPING_DIGEST, PRISM_TEMPLATE_ZONE_DEEPDIVE, REFINE_COUNTDOWN_THRESHOLD, REFINE_SESSION_SOFT_CAP, REFINE_SUGGESTIONS, STYLE_PRESET_BRUTALIST, STYLE_PRESET_DEFAULT, STYLE_PRESET_GLASSY, STYLE_PRESET_MINIMAL, TEMPLATE_SCHEMA_EXAMPLES, TEMPLATE_SCHEMA_GENERIC, WIDGET_TEMPLATE_CHANGELOG_ROW, WIDGET_TEMPLATE_CTA_BANNER, WIDGET_TEMPLATE_FEATURE_GRID, WIDGET_TEMPLATE_METRIC_BADGE, WIDGET_TEMPLATE_PRICING_TIER, WIDGET_TEMPLATE_SOCIAL_PROOF, WIDGET_TEMPLATE_STAT_CARD, WIDGET_TEMPLATE_TESTIMONIAL, applyEntryPatch, asAudienceId, assembleBrandUrlExtractionPrompt, assembleForgePrompt, brandThemeConfigToEntry, buildRevertVersion, buildScheduledEntry, buildVersion, cascadingScheduledFor, clampAnimationDuration, computeDiff, defaultBrandKit, defaultValueForField, distill, entryInRange, exportToBufferCsv, exportToHypefuryCsv, exportToICalendar, generateArchitectureWalkthrough, generateAskDrivenAsset, generateChangesSince, generateOnboardingDoc, generateRefactoringReport, generateReleaseNotes, getDispatchChannel, getPrismTemplate, getSlotValue, getStyleById, getWidgetTemplate, initialFormValues, next7DaysRange, nextVersionNumber, orchestrateDispatch, parseAndValidateSchemaDefinition, parseBrandKitFromLlmResponse, parseBrandThemeContent, parseStyleFromCss, parseStyleFromTailwindConfig, parseStyleFromTokensJson, parseThemeConfigContent, previewExport, refineAsset, refineLimitState, renderWidget, resolveAnimatedChoice, resolveAnimationDuration, resolveBrandPalette, runForgeGeneration, scanForSecrets, schemaExampleFor, schemaToForm, templateAnimatedDefault, themeEntryToPalette, tryParseJsonObject, validateAgainstTemplateSchema, validateAssetSlots, validateFormValues, validateSchemaDefinition };
5766
+ export { ANIMATION_DURATION_PRESETS, BRAND_CONTENT_SLOT_KEYS, BUNDLED_DISPATCH_CHANNELS, BUNDLED_STYLE_PRESETS, BUNDLED_WIDGET_TEMPLATES, DEFAULT_ANIMATION_DURATION_SECONDS, DEFAULT_BRAND_KIT_FONTS, DEFAULT_BRAND_KIT_PALETTE, DEFAULT_BRAND_KIT_VOICE, DISPATCH_CHANNEL_BLOG, DISPATCH_CHANNEL_EMAIL, DISPATCH_CHANNEL_HN, DISPATCH_CHANNEL_INSTAGRAM, DISPATCH_CHANNEL_LINKEDIN, DISPATCH_CHANNEL_NEWSLETTER, DISPATCH_CHANNEL_REDDIT, DISPATCH_CHANNEL_SLACK, DISPATCH_CHANNEL_TWEET, FORGE_AUDIENCES, FORGE_BRAND_THEME_ID, FORGE_TEMPLATES, FREE_TIER_WIDGET_IDS, MAX_ANIMATION_DURATION_SECONDS, MIN_ANIMATION_DURATION_SECONDS, PRISM_TEMPLATES, PRISM_TEMPLATE_ARCHITECTURE_OVERVIEW, PRISM_TEMPLATE_REFACTOR_RATIONALE, PRISM_TEMPLATE_RELEASE_ANNOUNCEMENT, PRISM_TEMPLATE_SHIPPING_DIGEST, PRISM_TEMPLATE_ZONE_DEEPDIVE, REFINE_COUNTDOWN_THRESHOLD, REFINE_SESSION_SOFT_CAP, REFINE_SUGGESTIONS, STYLE_PRESET_BRUTALIST, STYLE_PRESET_DEFAULT, STYLE_PRESET_GLASSY, STYLE_PRESET_MINIMAL, TEMPLATE_SCHEMA_EXAMPLES, TEMPLATE_SCHEMA_GENERIC, WIDGET_TEMPLATE_CHANGELOG_ROW, WIDGET_TEMPLATE_CTA_BANNER, WIDGET_TEMPLATE_FEATURE_GRID, WIDGET_TEMPLATE_METRIC_BADGE, WIDGET_TEMPLATE_PRICING_TIER, WIDGET_TEMPLATE_SOCIAL_PROOF, WIDGET_TEMPLATE_STAT_CARD, WIDGET_TEMPLATE_TESTIMONIAL, applyEntryPatch, asAudienceId, assembleBrandUrlExtractionPrompt, assembleForgePrompt, brandThemeConfigToEntry, buildRevertVersion, buildScheduledEntry, buildVersion, cascadingScheduledFor, clampAnimationDuration, computeDiff, defaultBrandKit, defaultValueForField, distill, entryInRange, exportToBufferCsv, exportToHypefuryCsv, exportToICalendar, generateADR, generateArc42, generateArchitectureWalkthrough, generateAskDrivenAsset, generateChangesSince, generateComplianceDoc, generateKnowledgeCapture, generateNewsletter, generateOnboardingDoc, generatePresentation, generateRadio, generateRefactoringReport, generateReleaseNotes, generateSprintRetro, getDispatchChannel, getPrismTemplate, getSlotValue, getStyleById, getWidgetTemplate, initialFormValues, next7DaysRange, nextVersionNumber, orchestrateDispatch, parseAndValidateSchemaDefinition, parseBrandKitFromLlmResponse, parseBrandThemeContent, parseStyleFromCss, parseStyleFromTailwindConfig, parseStyleFromTokensJson, parseThemeConfigContent, previewExport, refineAsset, refineLimitState, renderWidget, resolveAnimatedChoice, resolveAnimationDuration, resolveBrandPalette, runForgeGeneration, scanForSecrets, schemaExampleFor, schemaToForm, templateAnimatedDefault, themeEntryToPalette, tryParseJsonObject, validateAgainstTemplateSchema, validateAssetSlots, validateFormValues, validateSchemaDefinition };