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