forgesmith 0.7.0 → 0.7.1

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/server.mjs CHANGED
@@ -3533,6 +3533,583 @@ async function generateKnowledgeCapture(options) {
3533
3533
  return buildFallback7(options);
3534
3534
  }
3535
3535
 
3536
+ // src/generators/generateApiChangelog.ts
3537
+ function buildPromptContext(opts) {
3538
+ const { apiChangeContext: ctx, amberContext, gitContext } = opts;
3539
+ const lines = [
3540
+ `## Change Range`,
3541
+ `From: ${ctx.from} To: ${ctx.to}`,
3542
+ `Total API changes: ${ctx.totalChanged}`,
3543
+ ``
3544
+ ];
3545
+ if (ctx.breakingChanges.length > 0) {
3546
+ lines.push(`## Breaking Changes (${ctx.breakingChanges.length})`);
3547
+ for (const c of ctx.breakingChanges) {
3548
+ const capHint = c.capabilityName ? ` [${c.capabilityName}]` : "";
3549
+ lines.push(`- ${c.endpoint}${capHint} \u2014 ${c.description ?? "no description"} (file: ${c.file})`);
3550
+ }
3551
+ lines.push("");
3552
+ }
3553
+ if (ctx.newEndpoints.length > 0) {
3554
+ lines.push(`## New Endpoints (${ctx.newEndpoints.length})`);
3555
+ for (const c of ctx.newEndpoints) {
3556
+ const capHint = c.capabilityName ? ` [${c.capabilityName}]` : "";
3557
+ lines.push(`- ${c.endpoint}${capHint} \u2014 ${c.description ?? "new"} (file: ${c.file})`);
3558
+ }
3559
+ lines.push("");
3560
+ }
3561
+ if (ctx.removedEndpoints.length > 0) {
3562
+ lines.push(`## Removed Endpoints (${ctx.removedEndpoints.length})`);
3563
+ for (const c of ctx.removedEndpoints) {
3564
+ lines.push(`- ${c.endpoint} \u2014 removed from ${c.file}`);
3565
+ }
3566
+ lines.push("");
3567
+ }
3568
+ const modified = ctx.changes.filter((c) => c.changeType === "modified");
3569
+ if (modified.length > 0) {
3570
+ lines.push(`## Modified Endpoints (${modified.length})`);
3571
+ for (const c of modified) {
3572
+ const capHint = c.capabilityName ? ` [${c.capabilityName}]` : "";
3573
+ lines.push(`- ${c.endpoint}${capHint} \u2014 ${c.description ?? "modified"} (file: ${c.file})`);
3574
+ }
3575
+ lines.push("");
3576
+ }
3577
+ if (amberContext) {
3578
+ lines.push(`## AMBER Capability Context`, amberContext.summary, "");
3579
+ }
3580
+ if (gitContext && gitContext.commitMessages.length > 0) {
3581
+ lines.push(
3582
+ `## Recent Commits`,
3583
+ ...gitContext.commitMessages.slice(0, 8).map((m) => `- ${m}`),
3584
+ ""
3585
+ );
3586
+ }
3587
+ return lines.join("\n");
3588
+ }
3589
+ function buildSystemPrompt14(audience, format) {
3590
+ const audienceDesc = audience === "external" ? "external API consumers (third-party developers)" : audience === "partner" ? "integration partners with existing API contracts" : "internal engineering teams";
3591
+ const formatDesc = format === "migration-guide" ? "a step-by-step migration guide with code examples" : format === "release-notes" ? "polished release notes suitable for a public changelog page" : "a structured API changelog";
3592
+ return `You are a developer relations engineer writing ${formatDesc} for ${audienceDesc}. Be precise about endpoint paths, HTTP methods, and request/response changes. For breaking changes, always include a migration path. Generate ONLY valid JSON \u2014 no markdown fences, no prose outside the JSON object.`;
3593
+ }
3594
+ function buildUserPrompt14(opts, context) {
3595
+ const { projectName, version, targetAudience, format } = opts;
3596
+ const v = version || opts.apiChangeContext.to;
3597
+ return [
3598
+ `Generate an API ${format} for: ${projectName}`,
3599
+ `Version: ${v}`,
3600
+ `Target audience: ${targetAudience}`,
3601
+ `Format: ${format}`,
3602
+ ``,
3603
+ context,
3604
+ ``,
3605
+ `## Output Format`,
3606
+ `Return a single JSON object with this EXACT shape:`,
3607
+ `{`,
3608
+ ` "version": "<version string>",`,
3609
+ ` "date": "<ISO date YYYY-MM-DD>",`,
3610
+ ` "targetAudience": "<audience>",`,
3611
+ ` "breakingChanges": [`,
3612
+ ` { "endpoint": "<METHOD /path>", "description": "<what changed>", "migration": "<how to migrate>" }`,
3613
+ ` ],`,
3614
+ ` "newFeatures": [`,
3615
+ ` { "endpoint": "<METHOD /path>", "description": "<what it does>", "example": "<optional curl/code hint>" }`,
3616
+ ` ],`,
3617
+ ` "improvements": [`,
3618
+ ` { "endpoint": "<METHOD /path>", "description": "<what improved>" }`,
3619
+ ` ],`,
3620
+ ` "deprecations": [`,
3621
+ ` { "endpoint": "<METHOD /path>", "description": "<what is deprecated>", "sunset": "<optional date>" }`,
3622
+ ` ],`,
3623
+ ` "migrationGuide": "<full migration guidance as prose \u2014 2\u20135 paragraphs>",`,
3624
+ ` "consumerImpactSummary": "<1 paragraph copy-ready summary for consumers>"`,
3625
+ `}`,
3626
+ ``,
3627
+ `Rules:`,
3628
+ `- Every breaking change MUST have a non-empty migration string.`,
3629
+ `- For external/partner audiences, omit internal implementation details.`,
3630
+ `- migrationGuide should be empty string if no breaking changes.`,
3631
+ `- Return ONLY valid JSON. No markdown fences.`
3632
+ ].join("\n");
3633
+ }
3634
+ function buildFallback8(opts) {
3635
+ const ctx = opts.apiChangeContext;
3636
+ const v = opts.version ?? ctx.to;
3637
+ return {
3638
+ version: v,
3639
+ date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
3640
+ targetAudience: opts.targetAudience,
3641
+ breakingChanges: ctx.breakingChanges.map((c) => ({
3642
+ endpoint: c.endpoint,
3643
+ description: c.description ?? "Breaking change detected",
3644
+ migration: "Review the updated API contract and update your client accordingly."
3645
+ })),
3646
+ newFeatures: ctx.newEndpoints.map((c) => ({
3647
+ endpoint: c.endpoint,
3648
+ description: c.description ?? "New endpoint added"
3649
+ })),
3650
+ improvements: ctx.changes.filter((c) => c.changeType === "modified").map((c) => ({
3651
+ endpoint: c.endpoint,
3652
+ description: c.description ?? "Endpoint updated"
3653
+ })),
3654
+ deprecations: ctx.removedEndpoints.map((c) => ({
3655
+ endpoint: c.endpoint,
3656
+ description: "This endpoint has been removed."
3657
+ })),
3658
+ migrationGuide: ctx.breakingChanges.length > 0 ? `This release contains ${ctx.breakingChanges.length} breaking change(s). Review the breaking changes section and update your API client before deploying. Test all affected endpoints in a staging environment.` : "",
3659
+ consumerImpactSummary: `Version ${v} includes ${ctx.newEndpoints.length} new endpoint(s), ${ctx.breakingChanges.length} breaking change(s), and ${ctx.changes.filter((c) => c.changeType === "modified").length} improvement(s). ` + (ctx.breakingChanges.length > 0 ? "Migration is required before upgrading." : "This release is backward-compatible.")
3660
+ };
3661
+ }
3662
+ function parseLlmResponse8(raw) {
3663
+ let text = raw.trim();
3664
+ if (text.startsWith("```")) {
3665
+ text = text.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
3666
+ }
3667
+ let parsed;
3668
+ try {
3669
+ parsed = JSON.parse(text);
3670
+ } catch {
3671
+ return null;
3672
+ }
3673
+ function parseChangeArray(key, required) {
3674
+ if (!Array.isArray(parsed[key])) return [];
3675
+ return parsed[key].filter((item) => {
3676
+ if (typeof item !== "object" || item === null) return false;
3677
+ return required.every((k) => k in item);
3678
+ });
3679
+ }
3680
+ return {
3681
+ version: typeof parsed.version === "string" ? parsed.version : "",
3682
+ date: typeof parsed.date === "string" ? parsed.date : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
3683
+ targetAudience: typeof parsed.targetAudience === "string" ? parsed.targetAudience : "",
3684
+ breakingChanges: parseChangeArray("breakingChanges", ["endpoint", "description", "migration"]),
3685
+ newFeatures: parseChangeArray("newFeatures", ["endpoint", "description"]),
3686
+ improvements: parseChangeArray("improvements", ["endpoint", "description"]),
3687
+ deprecations: parseChangeArray("deprecations", ["endpoint", "description"]),
3688
+ migrationGuide: typeof parsed.migrationGuide === "string" ? parsed.migrationGuide : "",
3689
+ consumerImpactSummary: typeof parsed.consumerImpactSummary === "string" ? parsed.consumerImpactSummary : ""
3690
+ };
3691
+ }
3692
+ async function generateApiChangelog(opts) {
3693
+ const context = buildPromptContext(opts);
3694
+ try {
3695
+ const response = await opts.llm.complete({
3696
+ systemPrompt: buildSystemPrompt14(opts.targetAudience, opts.format),
3697
+ messages: [{ role: "user", content: buildUserPrompt14(opts, context) }],
3698
+ maxTokens: 3072
3699
+ });
3700
+ const parsed = parseLlmResponse8(response.content);
3701
+ if (parsed) return parsed;
3702
+ } catch {
3703
+ }
3704
+ return buildFallback8(opts);
3705
+ }
3706
+
3707
+ // src/generators/generateChangeImpactBrief.ts
3708
+ function buildPromptContext2(opts) {
3709
+ const { gitContext, changeImpactContext: ctx, amberContext, greenContext } = opts;
3710
+ const lines = [
3711
+ `## Change Range`,
3712
+ `From: ${ctx.from}`,
3713
+ `To: ${ctx.to}`,
3714
+ `Commits: ${ctx.commitCount}`,
3715
+ `Files changed: ${ctx.filesChanged.length}`,
3716
+ ``,
3717
+ `## Changed Files (sample)`,
3718
+ ...ctx.filesChanged.slice(0, 20).map((f) => `- ${f}`)
3719
+ ];
3720
+ if (ctx.breakingChanges.length > 0) {
3721
+ lines.push(``, `## Breaking Changes`, ...ctx.breakingChanges.map((b) => `- ${b}`));
3722
+ }
3723
+ if (ctx.affectedCapabilities.length > 0) {
3724
+ lines.push(``, `## Affected Business Capabilities`);
3725
+ for (const cap of ctx.affectedCapabilities) {
3726
+ lines.push(
3727
+ `- [${cap.criticality.toUpperCase()}] ${cap.name} (${cap.changedFiles.length} files changed, drift: ${cap.driftCount})`
3728
+ );
3729
+ }
3730
+ }
3731
+ if (ctx.regulatoryCapabilities.length > 0) {
3732
+ lines.push(
3733
+ ``,
3734
+ `## Critical Capabilities Touched (require approval)`,
3735
+ ...ctx.regulatoryCapabilities.map((c) => `- ${c}`)
3736
+ );
3737
+ }
3738
+ if (ctx.coherenceScoreBefore !== null || ctx.coherenceScoreAfter !== null) {
3739
+ lines.push(
3740
+ ``,
3741
+ `## Architecture Coherence`,
3742
+ `Score before: ${ctx.coherenceScoreBefore ?? "unknown"}`,
3743
+ `Score after: ${ctx.coherenceScoreAfter ?? "unknown"}`,
3744
+ `Delta: ${ctx.coherenceDelta !== null ? (ctx.coherenceDelta > 0 ? "+" : "") + ctx.coherenceDelta : "unknown"}`
3745
+ );
3746
+ }
3747
+ if (ctx.topRisks.length > 0) {
3748
+ lines.push(``, `## Top Risks`, ...ctx.topRisks.map((r) => `- ${r}`));
3749
+ }
3750
+ if (amberContext) {
3751
+ lines.push(``, `## AMBER Layer Context`, amberContext.summary);
3752
+ }
3753
+ if (greenContext) {
3754
+ lines.push(``, `## GREEN Layer Context`, greenContext.summary);
3755
+ }
3756
+ if (gitContext.commitMessages.length > 0) {
3757
+ lines.push(
3758
+ ``,
3759
+ `## Recent Commit Messages (sample)`,
3760
+ ...gitContext.commitMessages.slice(0, 10).map((m) => `- ${m}`)
3761
+ );
3762
+ }
3763
+ return lines.join("\n");
3764
+ }
3765
+ function buildSystemPrompt15(audience) {
3766
+ if (audience === "executive") {
3767
+ return "You are a VP of Engineering preparing a release briefing for the CTO and board. Write in plain business language \u2014 no jargon, no technical acronyms. Be direct about risks. Generate ONLY valid JSON \u2014 no markdown fences, no prose outside the JSON.";
3768
+ }
3769
+ if (audience === "cab") {
3770
+ return "You are a senior engineering manager preparing a Change Advisory Board (CAB) submission. Balance technical precision with business impact. Highlight rollback complexity and compliance implications. Generate ONLY valid JSON \u2014 no markdown fences, no prose outside the JSON.";
3771
+ }
3772
+ return "You are a principal engineer preparing a technical change impact brief. Be precise about files, capabilities, and risk vectors. Generate ONLY valid JSON \u2014 no markdown fences, no prose outside the JSON.";
3773
+ }
3774
+ function buildUserPrompt15(opts, context) {
3775
+ const { projectName, releaseVersion, audience } = opts;
3776
+ const version = releaseVersion || opts.changeImpactContext.to;
3777
+ return [
3778
+ `Generate a change impact brief for: ${projectName}`,
3779
+ `Release version: ${version}`,
3780
+ `Audience: ${audience}`,
3781
+ ``,
3782
+ context,
3783
+ ``,
3784
+ `## Output Format`,
3785
+ `Return a single JSON object with this EXACT shape:`,
3786
+ `{`,
3787
+ ` "title": "<brief title>",`,
3788
+ ` "releaseVersion": "<version string>",`,
3789
+ ` "date": "<ISO date>",`,
3790
+ ` "audience": "<audience>",`,
3791
+ ` "executiveSummary": "<3 bullet points separated by \\n, plain language, max 120 chars each>",`,
3792
+ ` "technicalSummary": "<2-3 sentences for CAB technical reviewers>",`,
3793
+ ` "affectedBusinessAreas": ["<area 1>", "<area 2>"],`,
3794
+ ` "riskLevel": "<one of: low | medium | high | critical>",`,
3795
+ ` "riskJustification": "<1-2 sentences explaining the risk rating>",`,
3796
+ ` "complianceImpact": "<which regulated capabilities are touched, or 'None' if clean>",`,
3797
+ ` "rollbackComplexity": "<one of: simple | moderate | complex>",`,
3798
+ ` "rollbackGuidance": "<concise rollback instructions>",`,
3799
+ ` "recommendedActions": ["<action 1>", "<action 2>", "<action 3>"],`,
3800
+ ` "approvalRequired": <true | false>,`,
3801
+ ` "metrics": {`,
3802
+ ` "commits": <number>,`,
3803
+ ` "filesChanged": <number>,`,
3804
+ ` "capabilitiesAffected": <number>,`,
3805
+ ` "coherenceDelta": <number | null>`,
3806
+ ` }`,
3807
+ `}`,
3808
+ ``,
3809
+ `Rules:`,
3810
+ `- riskLevel = "critical" if any critical capabilities touched OR breaking changes exist.`,
3811
+ `- riskLevel = "high" if 3+ capabilities affected or coherenceDelta < -5.`,
3812
+ `- approvalRequired = true if riskLevel is "critical" or "high".`,
3813
+ `- rollbackComplexity = "complex" if breaking changes or critical capabilities touched.`,
3814
+ `- executiveSummary must be EXACTLY 3 bullet lines joined by \\n \u2014 each starting with a dash.`,
3815
+ `- Return ONLY valid JSON. No markdown. No prose outside the JSON object.`
3816
+ ].join("\n");
3817
+ }
3818
+ function buildFallback9(opts) {
3819
+ const ctx = opts.changeImpactContext;
3820
+ const hasCritical = ctx.regulatoryCapabilities.length > 0 || ctx.breakingChanges.length > 0;
3821
+ const hasHigh = ctx.affectedCapabilities.filter((c) => c.criticality === "high").length >= 2;
3822
+ const riskLevel = hasCritical ? "critical" : hasHigh || ctx.affectedCapabilities.length >= 3 ? "high" : ctx.affectedCapabilities.length >= 1 ? "medium" : "low";
3823
+ const approvalRequired = riskLevel === "critical" || riskLevel === "high";
3824
+ const rollbackComplexity = hasCritical ? "complex" : ctx.affectedCapabilities.length >= 3 ? "moderate" : "simple";
3825
+ return {
3826
+ title: `Change Impact Brief \u2014 ${opts.projectName}`,
3827
+ releaseVersion: opts.releaseVersion ?? ctx.to,
3828
+ date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
3829
+ audience: opts.audience,
3830
+ executiveSummary: `- ${ctx.commitCount} commit(s) deployed across ${ctx.filesChanged.length} files
3831
+ - ${ctx.affectedCapabilities.length} business capability(ies) affected
3832
+ - Risk level: ${riskLevel.toUpperCase()}${hasCritical ? " \u2014 critical capabilities touched" : ""}`,
3833
+ technicalSummary: `${ctx.commitCount} commit(s) between ${ctx.from} and ${ctx.to}, changing ${ctx.filesChanged.length} file(s). ` + (ctx.breakingChanges.length > 0 ? `${ctx.breakingChanges.length} breaking change(s) detected. ` : "") + (ctx.coherenceDelta !== null ? `Coherence score moved ${ctx.coherenceDelta > 0 ? "+" : ""}${ctx.coherenceDelta}.` : ""),
3834
+ affectedBusinessAreas: ctx.affectedCapabilities.map((c) => c.name),
3835
+ riskLevel,
3836
+ riskJustification: hasCritical ? "Critical business capabilities were modified, requiring formal CAB sign-off." : `${ctx.affectedCapabilities.length} capabilities affected with potential for regression.`,
3837
+ complianceImpact: ctx.regulatoryCapabilities.length > 0 ? ctx.regulatoryCapabilities.join(", ") : "None",
3838
+ rollbackComplexity,
3839
+ rollbackGuidance: rollbackComplexity === "complex" ? "Prepare a full environment snapshot before deploying. Coordinate rollback with database team if schema changes exist." : rollbackComplexity === "moderate" ? "Tag the current deployment. Roll back using git revert on the merge commit." : "Revert the feature flag or redeploy the previous container image.",
3840
+ recommendedActions: [
3841
+ approvalRequired ? "Obtain formal CAB sign-off before deploying to production" : "Deploy during low-traffic window",
3842
+ ctx.breakingChanges.length > 0 ? "Notify API consumers of breaking changes at least 48h in advance" : "Run full regression suite",
3843
+ "Monitor error rates and coherence score for 30 minutes post-deploy"
3844
+ ],
3845
+ approvalRequired,
3846
+ metrics: {
3847
+ commits: ctx.commitCount,
3848
+ filesChanged: ctx.filesChanged.length,
3849
+ capabilitiesAffected: ctx.affectedCapabilities.length,
3850
+ coherenceDelta: ctx.coherenceDelta
3851
+ }
3852
+ };
3853
+ }
3854
+ var RISK_LEVELS = ["low", "medium", "high", "critical"];
3855
+ var ROLLBACK_LEVELS = ["simple", "moderate", "complex"];
3856
+ function parseLlmResponse9(raw) {
3857
+ let text = raw.trim();
3858
+ if (text.startsWith("```")) {
3859
+ text = text.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
3860
+ }
3861
+ let parsed;
3862
+ try {
3863
+ parsed = JSON.parse(text);
3864
+ } catch {
3865
+ return null;
3866
+ }
3867
+ const riskLevel = RISK_LEVELS.includes(parsed.riskLevel) ? parsed.riskLevel : "medium";
3868
+ const rollbackComplexity = ROLLBACK_LEVELS.includes(
3869
+ parsed.rollbackComplexity
3870
+ ) ? parsed.rollbackComplexity : "moderate";
3871
+ const recommendedActions = Array.isArray(parsed.recommendedActions) ? parsed.recommendedActions.filter((a) => typeof a === "string") : [];
3872
+ const affectedBusinessAreas = Array.isArray(parsed.affectedBusinessAreas) ? parsed.affectedBusinessAreas.filter((a) => typeof a === "string") : [];
3873
+ const metricsRaw = parsed.metrics && typeof parsed.metrics === "object" ? parsed.metrics : {};
3874
+ return {
3875
+ title: typeof parsed.title === "string" ? parsed.title : "Change Impact Brief",
3876
+ releaseVersion: typeof parsed.releaseVersion === "string" ? parsed.releaseVersion : "",
3877
+ date: typeof parsed.date === "string" ? parsed.date : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
3878
+ audience: typeof parsed.audience === "string" ? parsed.audience : "",
3879
+ executiveSummary: typeof parsed.executiveSummary === "string" ? parsed.executiveSummary : "",
3880
+ technicalSummary: typeof parsed.technicalSummary === "string" ? parsed.technicalSummary : "",
3881
+ affectedBusinessAreas,
3882
+ riskLevel,
3883
+ riskJustification: typeof parsed.riskJustification === "string" ? parsed.riskJustification : "",
3884
+ complianceImpact: typeof parsed.complianceImpact === "string" ? parsed.complianceImpact : "None",
3885
+ rollbackComplexity,
3886
+ rollbackGuidance: typeof parsed.rollbackGuidance === "string" ? parsed.rollbackGuidance : "",
3887
+ recommendedActions,
3888
+ approvalRequired: parsed.approvalRequired === true,
3889
+ metrics: {
3890
+ commits: typeof metricsRaw.commits === "number" ? metricsRaw.commits : 0,
3891
+ filesChanged: typeof metricsRaw.filesChanged === "number" ? metricsRaw.filesChanged : 0,
3892
+ capabilitiesAffected: typeof metricsRaw.capabilitiesAffected === "number" ? metricsRaw.capabilitiesAffected : 0,
3893
+ coherenceDelta: typeof metricsRaw.coherenceDelta === "number" ? metricsRaw.coherenceDelta : null
3894
+ }
3895
+ };
3896
+ }
3897
+ async function generateChangeImpactBrief(opts) {
3898
+ const context = buildPromptContext2(opts);
3899
+ try {
3900
+ const response = await opts.llm.complete({
3901
+ systemPrompt: buildSystemPrompt15(opts.audience),
3902
+ messages: [{ role: "user", content: buildUserPrompt15(opts, context) }],
3903
+ maxTokens: 2048
3904
+ });
3905
+ const parsed = parseLlmResponse9(response.content);
3906
+ if (parsed) return parsed;
3907
+ } catch {
3908
+ }
3909
+ return buildFallback9(opts);
3910
+ }
3911
+
3912
+ // src/generators/generateRoiSlide.ts
3913
+ var CURRENCY_SYMBOL = {
3914
+ EUR: "\u20AC",
3915
+ USD: "$",
3916
+ GBP: "\xA3"
3917
+ };
3918
+ function fmtK(n, sym) {
3919
+ if (n >= 1e6) return `${sym}${(n / 1e6).toFixed(1)}M`;
3920
+ if (n >= 1e3) return `${sym}${Math.round(n / 1e3)}k`;
3921
+ return `${sym}${Math.round(n)}`;
3922
+ }
3923
+ function buildFallback10(opts) {
3924
+ const { roiEstimate: r, organizationName, currency } = opts;
3925
+ const sym = CURRENCY_SYMBOL[currency] ?? "\u20AC";
3926
+ const org = organizationName ?? "Your Organization";
3927
+ const costYr = fmtK(r.maintenanceCostPerYear, sym);
3928
+ const saveYr = fmtK(r.savedCostPerYear, sym);
3929
+ const saveMo = fmtK(r.savedCostPerMonth, sym);
3930
+ const slides = [
3931
+ {
3932
+ type: "title",
3933
+ title: "Technical Debt Cost Analysis",
3934
+ content: `${org} \xB7 Architecture Health Report \xB7 ${(/* @__PURE__ */ new Date()).toLocaleDateString("en-GB", { month: "long", year: "numeric" })}`,
3935
+ highlight: r.currentGrade,
3936
+ highlightLabel: "Current Architecture Grade"
3937
+ },
3938
+ {
3939
+ type: "problem",
3940
+ title: "The Hidden Cost of Architecture Debt",
3941
+ content: `Your codebase currently has grade ${r.currentGrade} (${r.currentScore}/100). This translates directly to developer time lost to maintenance overhead every month.`,
3942
+ highlight: fmtK(r.maintenanceCostPerMonth, sym),
3943
+ highlightLabel: "Estimated monthly maintenance cost",
3944
+ bullets: [
3945
+ `Import cycles: ${r.breakdown.importCycles.count} cycles consuming ${r.breakdown.importCycles.hoursPerMonth}h/mo`,
3946
+ `Documentation drift: ${r.breakdown.documentationDrift.count} drifted capabilities \u2014 ${r.breakdown.documentationDrift.hoursPerMonth}h/mo in context-searching`,
3947
+ `High-churn files: ${r.breakdown.highChurnFiles.count} hot files adding ${r.breakdown.highChurnFiles.hoursPerMonth}h/mo in review overhead`,
3948
+ `Orphaned capabilities: ${r.breakdown.orphanedCode.count} untracked modules \u2014 ${r.breakdown.orphanedCode.hoursPerMonth}h/mo wasted`
3949
+ ]
3950
+ },
3951
+ {
3952
+ type: "cost-breakdown",
3953
+ title: "Where Developer Time Goes",
3954
+ content: `Total: ${r.estimatedMaintenanceHoursPerMonth}h/month in maintenance overhead = ${costYr}/year at current state`,
3955
+ bullets: [
3956
+ `Import cycles \u2192 ${fmtK(r.breakdown.importCycles.cost, sym)}/mo (${r.breakdown.importCycles.hoursPerMonth}h debugging)`,
3957
+ `Doc drift \u2192 ${fmtK(r.breakdown.documentationDrift.cost, sym)}/mo (${r.breakdown.documentationDrift.hoursPerMonth}h searching context)`,
3958
+ `Churn overhead \u2192 ${fmtK(r.breakdown.highChurnFiles.cost, sym)}/mo (${r.breakdown.highChurnFiles.hoursPerMonth}h in review)`,
3959
+ `Orphaned code \u2192 ${fmtK(r.breakdown.orphanedCode.cost, sym)}/mo (${r.breakdown.orphanedCode.hoursPerMonth}h ownership gaps)`
3960
+ ]
3961
+ },
3962
+ {
3963
+ type: "scenarios",
3964
+ title: "What Reaching Grade B Saves",
3965
+ content: `Moving from ${r.currentGrade} to ${r.targetGrade} cuts maintenance overhead by ~${Math.round(r.savedCostPerMonth / Math.max(r.maintenanceCostPerMonth, 1) * 100)}%.`,
3966
+ highlight: saveYr,
3967
+ highlightLabel: `Annual savings at grade ${r.targetGrade}`,
3968
+ bullets: [
3969
+ `Monthly savings: ${saveMo}/mo`,
3970
+ `ROI multiple: ${r.roiMultiple}\xD7 return on remediation investment`,
3971
+ `Break-even: ~${r.breakEvenMonths < 1 ? `${Math.round(r.breakEvenMonths * 30)} days` : `${r.breakEvenMonths} months`}`,
3972
+ `Remediation effort: ~${r.estimatedRemediationDays} engineering days`
3973
+ ]
3974
+ },
3975
+ {
3976
+ type: "recommendation",
3977
+ title: "Recommended Remediation Path",
3978
+ content: r.recommendation,
3979
+ bullets: [
3980
+ `Step 1: Resolve ${r.breakdown.importCycles.count} import cycles (est. ${Math.round(r.estimatedRemediationDays * 0.4)} days)`,
3981
+ `Step 2: Fix ${r.breakdown.documentationDrift.count} drifted capability docs (est. ${Math.round(r.estimatedRemediationDays * 0.3)} days)`,
3982
+ `Step 3: Assign ownership for ${r.breakdown.orphanedCode.count} orphaned capabilities (est. ${Math.round(r.estimatedRemediationDays * 0.2)} days)`,
3983
+ `Step 4: Add coverage to top churn files (est. ${Math.round(r.estimatedRemediationDays * 0.1)} days)`
3984
+ ]
3985
+ },
3986
+ {
3987
+ type: "cta",
3988
+ title: `Approve ${(/* @__PURE__ */ new Date()).toLocaleString("default", { month: "short" })} Refactoring Budget`,
3989
+ content: `Investment: ~${fmtK(r.estimatedRemediationDays * 8 * 150, sym)} (${r.estimatedRemediationDays} days of engineering effort)
3990
+ Return: ${saveYr}/year in recovered developer capacity`,
3991
+ highlight: `${r.roiMultiple}\xD7`,
3992
+ highlightLabel: "ROI multiple",
3993
+ bullets: [
3994
+ `${saveYr}/year recurring savings`,
3995
+ `Break-even in ${r.breakEvenMonths < 1 ? `${Math.round(r.breakEvenMonths * 30)} days` : `${r.breakEvenMonths} months`}`,
3996
+ `Measurable via PRISM architecture grade (${r.currentGrade} \u2192 ${r.targetGrade})`
3997
+ ]
3998
+ }
3999
+ ];
4000
+ const executiveSummary = [
4001
+ `\u2022 Current architecture grade ${r.currentGrade} (${r.currentScore}/100) costs ~${costYr}/year in maintenance overhead.`,
4002
+ `\u2022 Improving to grade ${r.targetGrade} saves ~${saveYr}/year \u2014 ${r.roiMultiple}\xD7 return on a ${r.estimatedRemediationDays}-day remediation effort.`,
4003
+ `\u2022 Break-even: ~${r.breakEvenMonths < 1 ? `${Math.round(r.breakEvenMonths * 30)} days` : `${r.breakEvenMonths} months`}. Primary sources: ${r.breakdown.importCycles.count} import cycles, ${r.breakdown.documentationDrift.count} drifted docs, ${r.breakdown.orphanedCode.count} orphaned capabilities.`
4004
+ ].join("\n");
4005
+ const oneLinePitch = `Moving from ${r.currentGrade} to ${r.targetGrade} saves ${saveYr} annually \u2014 ${r.roiMultiple}\xD7 ROI on a ${r.estimatedRemediationDays}-day investment.`;
4006
+ return {
4007
+ title: `Technical Debt ROI \u2014 ${org}`,
4008
+ slides,
4009
+ executiveSummary,
4010
+ oneLinePitch
4011
+ };
4012
+ }
4013
+ function buildSystemPrompt16() {
4014
+ return "You are a VP of Engineering creating a concise, business-value-focused presentation for C-suite audiences. Generate ONLY valid JSON \u2014 no markdown fences, no prose outside the JSON object.";
4015
+ }
4016
+ function buildUserPrompt16(opts) {
4017
+ const { roiEstimate: r, organizationName, currency } = opts;
4018
+ const sym = CURRENCY_SYMBOL[currency] ?? "\u20AC";
4019
+ const org = organizationName ?? "the organization";
4020
+ return [
4021
+ `Generate a 6-slide executive ROI presentation for ${org}.`,
4022
+ ``,
4023
+ `## ROI Data`,
4024
+ `Current grade: ${r.currentGrade} (score ${r.currentScore}/100)`,
4025
+ `Target grade: ${r.targetGrade} (score ${r.targetScore}/100)`,
4026
+ `Monthly maintenance cost: ${sym}${r.maintenanceCostPerMonth.toLocaleString()}`,
4027
+ `Annual maintenance cost: ${sym}${r.maintenanceCostPerYear.toLocaleString()}`,
4028
+ `Monthly savings if improved: ${sym}${r.savedCostPerMonth.toLocaleString()}`,
4029
+ `Annual savings if improved: ${sym}${r.savedCostPerYear.toLocaleString()}`,
4030
+ `ROI multiple: ${r.roiMultiple}x`,
4031
+ `Break-even: ${r.breakEvenMonths} months`,
4032
+ `Remediation days: ${r.estimatedRemediationDays}`,
4033
+ ``,
4034
+ `## Pain sources`,
4035
+ `Import cycles: ${r.breakdown.importCycles.count} cycles, ${r.breakdown.importCycles.hoursPerMonth}h/mo, ${sym}${r.breakdown.importCycles.cost.toLocaleString()}/mo`,
4036
+ `Doc drift: ${r.breakdown.documentationDrift.count} drifted, ${r.breakdown.documentationDrift.hoursPerMonth}h/mo, ${sym}${r.breakdown.documentationDrift.cost.toLocaleString()}/mo`,
4037
+ `High churn: ${r.breakdown.highChurnFiles.count} files, ${r.breakdown.highChurnFiles.hoursPerMonth}h/mo, ${sym}${r.breakdown.highChurnFiles.cost.toLocaleString()}/mo`,
4038
+ `Orphaned: ${r.breakdown.orphanedCode.count} capabilities, ${r.breakdown.orphanedCode.hoursPerMonth}h/mo, ${sym}${r.breakdown.orphanedCode.cost.toLocaleString()}/mo`,
4039
+ ``,
4040
+ `## Output Format`,
4041
+ `Return a single JSON object with this exact shape:`,
4042
+ `{`,
4043
+ ` "title": "<deck title>",`,
4044
+ ` "slides": [`,
4045
+ ` {`,
4046
+ ` "type": "<title|problem|cost-breakdown|scenarios|recommendation|cta>",`,
4047
+ ` "title": "<slide title>",`,
4048
+ ` "content": "<1-2 sentences of slide body copy>",`,
4049
+ ` "highlight": "<optional large callout \u2014 a number or grade>",`,
4050
+ ` "highlightLabel": "<label for the highlight>",`,
4051
+ ` "bullets": ["<bullet 1>", "<bullet 2>", "<bullet 3>"]`,
4052
+ ` }`,
4053
+ ` ],`,
4054
+ ` "executiveSummary": "<3-bullet executive summary as a single string, bullets separated by \\n>",`,
4055
+ ` "oneLinePitch": "<one sentence pitch for email subject line>"`,
4056
+ `}`,
4057
+ ``,
4058
+ `Rules:`,
4059
+ `- Exactly 6 slides in order: title, problem, cost-breakdown, scenarios, recommendation, cta`,
4060
+ `- bullets: max 4 per slide, each \u2264 90 characters`,
4061
+ `- content: 1\u20132 sentences, specific numbers from the data above`,
4062
+ `- Use business language \u2014 avoid engineering jargon`,
4063
+ `- Return ONLY valid JSON. No markdown. No prose outside the JSON.`
4064
+ ].join("\n");
4065
+ }
4066
+ function parseResponse(raw) {
4067
+ let text = raw.trim();
4068
+ if (text.startsWith("```")) {
4069
+ text = text.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
4070
+ }
4071
+ try {
4072
+ const parsed = JSON.parse(text);
4073
+ const rawSlides = Array.isArray(parsed.slides) ? parsed.slides : [];
4074
+ if (rawSlides.length === 0) return null;
4075
+ const VALID_TYPES = ["title", "problem", "cost-breakdown", "scenarios", "recommendation", "cta"];
4076
+ const slides = rawSlides.map((s, i) => {
4077
+ const slide = s ?? {};
4078
+ const type = typeof slide.type === "string" && VALID_TYPES.includes(slide.type) ? slide.type : VALID_TYPES[i] ?? "recommendation";
4079
+ const bullets = Array.isArray(slide.bullets) ? slide.bullets.filter((b) => typeof b === "string").slice(0, 4) : void 0;
4080
+ return {
4081
+ type,
4082
+ title: typeof slide.title === "string" ? slide.title : `Slide ${i + 1}`,
4083
+ content: typeof slide.content === "string" ? slide.content : "",
4084
+ ...typeof slide.highlight === "string" && slide.highlight ? { highlight: slide.highlight } : {},
4085
+ ...typeof slide.highlightLabel === "string" && slide.highlightLabel ? { highlightLabel: slide.highlightLabel } : {},
4086
+ ...bullets && bullets.length > 0 ? { bullets } : {}
4087
+ };
4088
+ });
4089
+ return {
4090
+ title: typeof parsed.title === "string" ? parsed.title : "Technical Debt ROI",
4091
+ slides,
4092
+ executiveSummary: typeof parsed.executiveSummary === "string" ? parsed.executiveSummary : "",
4093
+ oneLinePitch: typeof parsed.oneLinePitch === "string" ? parsed.oneLinePitch : ""
4094
+ };
4095
+ } catch {
4096
+ return null;
4097
+ }
4098
+ }
4099
+ async function generateRoiSlide(opts) {
4100
+ try {
4101
+ const response = await opts.llm.complete({
4102
+ systemPrompt: buildSystemPrompt16(),
4103
+ messages: [{ role: "user", content: buildUserPrompt16(opts) }],
4104
+ maxTokens: 3e3
4105
+ });
4106
+ const parsed = parseResponse(response.content);
4107
+ if (parsed) return parsed;
4108
+ } catch {
4109
+ }
4110
+ return buildFallback10(opts);
4111
+ }
4112
+
3536
4113
  // src/forge/types.ts
3537
4114
  var asAudienceId = (id) => id;
3538
4115
 
@@ -5993,7 +6570,7 @@ function getDispatchChannel(id) {
5993
6570
  }
5994
6571
 
5995
6572
  // src/forge/dispatch.ts
5996
- function buildSystemPrompt14(channel, audience, brand, blueprintContext, toneOffset) {
6573
+ function buildSystemPrompt17(channel, audience, brand, blueprintContext, toneOffset) {
5997
6574
  const lines = [
5998
6575
  `You are a skilled content writer producing a ${channel.name} post.`,
5999
6576
  "",
@@ -6044,7 +6621,7 @@ function truncate(content, maxLength) {
6044
6621
  async function generateForChannel(ask, channel, provider, opts) {
6045
6622
  const now = (/* @__PURE__ */ new Date()).toISOString();
6046
6623
  try {
6047
- const system = buildSystemPrompt14(
6624
+ const system = buildSystemPrompt17(
6048
6625
  channel,
6049
6626
  opts.audience,
6050
6627
  opts.brand,
@@ -6189,7 +6766,7 @@ function nextVersionNumber(versions) {
6189
6766
  }
6190
6767
 
6191
6768
  // src/forge/refineAsset.ts
6192
- function buildSystemPrompt15(input) {
6769
+ function buildSystemPrompt18(input) {
6193
6770
  const parts = [
6194
6771
  `You are a content refinement assistant for a developer-focused content tool (forge0x2B).`,
6195
6772
  `You are refining an existing ${input.assetType.replace(/-/g, " ")} asset.`,
@@ -6228,7 +6805,7 @@ function parseRefinedResponse(raw) {
6228
6805
  return { newContent, llmReply };
6229
6806
  }
6230
6807
  async function refineAsset(input, provider) {
6231
- const systemPrompt = buildSystemPrompt15(input);
6808
+ const systemPrompt = buildSystemPrompt18(input);
6232
6809
  if (scanForSecrets(systemPrompt)) {
6233
6810
  throw new Error("refineAsset: secret pattern detected in asset content. Refusing to send to LLM.");
6234
6811
  }
@@ -6390,4 +6967,4 @@ function previewExport(entries, format) {
6390
6967
  }
6391
6968
  }
6392
6969
 
6393
- export { ANIMATION_DURATION_PRESETS, BRAND_CONTENT_SLOT_KEYS, BUNDLED_DISPATCH_CHANNELS, BUNDLED_STYLE_PRESETS, BUNDLED_WIDGET_TEMPLATES, DEFAULT_ANIMATION_DURATION_SECONDS, DEFAULT_BRAND_KIT_FONTS, DEFAULT_BRAND_KIT_PALETTE, DEFAULT_BRAND_KIT_VOICE, DISPATCH_CHANNEL_BLOG, DISPATCH_CHANNEL_EMAIL, DISPATCH_CHANNEL_HN, DISPATCH_CHANNEL_INSTAGRAM, DISPATCH_CHANNEL_LINKEDIN, DISPATCH_CHANNEL_NEWSLETTER, DISPATCH_CHANNEL_REDDIT, DISPATCH_CHANNEL_SLACK, DISPATCH_CHANNEL_TWEET, FORGE_AUDIENCES, FORGE_BRAND_THEME_ID, FORGE_TEMPLATES, FREE_TIER_WIDGET_IDS, MAX_ANIMATION_DURATION_SECONDS, MIN_ANIMATION_DURATION_SECONDS, PRISM_TEMPLATES, PRISM_TEMPLATE_ARCHITECTURE_OVERVIEW, PRISM_TEMPLATE_REFACTOR_RATIONALE, PRISM_TEMPLATE_RELEASE_ANNOUNCEMENT, PRISM_TEMPLATE_SHIPPING_DIGEST, PRISM_TEMPLATE_ZONE_DEEPDIVE, REFINE_COUNTDOWN_THRESHOLD, REFINE_SESSION_SOFT_CAP, REFINE_SUGGESTIONS, STYLE_PRESET_BRUTALIST, STYLE_PRESET_DEFAULT, STYLE_PRESET_GLASSY, STYLE_PRESET_MINIMAL, TEMPLATE_SCHEMA_EXAMPLES, TEMPLATE_SCHEMA_GENERIC, WIDGET_TEMPLATE_CHANGELOG_ROW, WIDGET_TEMPLATE_CTA_BANNER, WIDGET_TEMPLATE_FEATURE_GRID, WIDGET_TEMPLATE_METRIC_BADGE, WIDGET_TEMPLATE_PRICING_TIER, WIDGET_TEMPLATE_SOCIAL_PROOF, WIDGET_TEMPLATE_STAT_CARD, WIDGET_TEMPLATE_TESTIMONIAL, applyEntryPatch, asAudienceId, assembleBrandUrlExtractionPrompt, assembleForgePrompt, brandThemeConfigToEntry, buildPrismContextPrompt, buildRevertVersion, buildScheduledEntry, buildVersion, cascadingScheduledFor, clampAnimationDuration, computeDiff, defaultBrandKit, defaultValueForField, deriveContextSummary, distill, entryInRange, exportToBufferCsv, exportToHypefuryCsv, exportToICalendar, extractBrandFromPrismBlueprint, extractDependencyHotspots, extractTopChurnFiles, extractZones, generateADR, generateArc42, generateArchitectureWalkthrough, generateAskDrivenAsset, generateChangesSince, generateComplianceDoc, generateKnowledgeCapture, generateNewsletter, generateOnboardingDoc, generatePresentation, generateRadio, generateRefactoringReport, generateReleaseNotes, generateSprintRetro, getDispatchChannel, getPrismTemplate, getSlotValue, getStyleById, getWidgetTemplate, initialFormValues, next7DaysRange, nextVersionNumber, normalizePrismlensBlueprint, orchestrateDispatch, parseAndValidateSchemaDefinition, parseBrandKitFromLlmResponse, parseBrandThemeContent, parseStyleFromCss, parseStyleFromTailwindConfig, parseStyleFromTokensJson, parseThemeConfigContent, previewExport, readAmberLayer, readBlueprintData, readBlueprintFromTarget, readGreenLayer, readPrismDirectory, refineAsset, refineLimitState, renderWidget, resolveAnimatedChoice, resolveAnimationDuration, resolveBrandPalette, runForgeGeneration, scanForSecrets, schemaExampleFor, schemaToForm, templateAnimatedDefault, themeEntryToPalette, tryParseJsonObject, validateAgainstTemplateSchema, validateAssetSlots, validateFormValues, validateSchemaDefinition };
6970
+ export { ANIMATION_DURATION_PRESETS, BRAND_CONTENT_SLOT_KEYS, BUNDLED_DISPATCH_CHANNELS, BUNDLED_STYLE_PRESETS, BUNDLED_WIDGET_TEMPLATES, DEFAULT_ANIMATION_DURATION_SECONDS, DEFAULT_BRAND_KIT_FONTS, DEFAULT_BRAND_KIT_PALETTE, DEFAULT_BRAND_KIT_VOICE, DISPATCH_CHANNEL_BLOG, DISPATCH_CHANNEL_EMAIL, DISPATCH_CHANNEL_HN, DISPATCH_CHANNEL_INSTAGRAM, DISPATCH_CHANNEL_LINKEDIN, DISPATCH_CHANNEL_NEWSLETTER, DISPATCH_CHANNEL_REDDIT, DISPATCH_CHANNEL_SLACK, DISPATCH_CHANNEL_TWEET, FORGE_AUDIENCES, FORGE_BRAND_THEME_ID, FORGE_TEMPLATES, FREE_TIER_WIDGET_IDS, MAX_ANIMATION_DURATION_SECONDS, MIN_ANIMATION_DURATION_SECONDS, PRISM_TEMPLATES, PRISM_TEMPLATE_ARCHITECTURE_OVERVIEW, PRISM_TEMPLATE_REFACTOR_RATIONALE, PRISM_TEMPLATE_RELEASE_ANNOUNCEMENT, PRISM_TEMPLATE_SHIPPING_DIGEST, PRISM_TEMPLATE_ZONE_DEEPDIVE, REFINE_COUNTDOWN_THRESHOLD, REFINE_SESSION_SOFT_CAP, REFINE_SUGGESTIONS, STYLE_PRESET_BRUTALIST, STYLE_PRESET_DEFAULT, STYLE_PRESET_GLASSY, STYLE_PRESET_MINIMAL, TEMPLATE_SCHEMA_EXAMPLES, TEMPLATE_SCHEMA_GENERIC, WIDGET_TEMPLATE_CHANGELOG_ROW, WIDGET_TEMPLATE_CTA_BANNER, WIDGET_TEMPLATE_FEATURE_GRID, WIDGET_TEMPLATE_METRIC_BADGE, WIDGET_TEMPLATE_PRICING_TIER, WIDGET_TEMPLATE_SOCIAL_PROOF, WIDGET_TEMPLATE_STAT_CARD, WIDGET_TEMPLATE_TESTIMONIAL, applyEntryPatch, asAudienceId, assembleBrandUrlExtractionPrompt, assembleForgePrompt, brandThemeConfigToEntry, buildPrismContextPrompt, buildRevertVersion, buildScheduledEntry, buildVersion, cascadingScheduledFor, clampAnimationDuration, computeDiff, defaultBrandKit, defaultValueForField, deriveContextSummary, distill, entryInRange, exportToBufferCsv, exportToHypefuryCsv, exportToICalendar, extractBrandFromPrismBlueprint, extractDependencyHotspots, extractTopChurnFiles, extractZones, generateADR, generateApiChangelog, generateArc42, generateArchitectureWalkthrough, generateAskDrivenAsset, generateChangeImpactBrief, generateChangesSince, generateComplianceDoc, generateKnowledgeCapture, generateNewsletter, generateOnboardingDoc, generatePresentation, generateRadio, generateRefactoringReport, generateReleaseNotes, generateRoiSlide, generateSprintRetro, getDispatchChannel, getPrismTemplate, getSlotValue, getStyleById, getWidgetTemplate, initialFormValues, next7DaysRange, nextVersionNumber, normalizePrismlensBlueprint, orchestrateDispatch, parseAndValidateSchemaDefinition, parseBrandKitFromLlmResponse, parseBrandThemeContent, parseStyleFromCss, parseStyleFromTailwindConfig, parseStyleFromTokensJson, parseThemeConfigContent, previewExport, readAmberLayer, readBlueprintData, readBlueprintFromTarget, readGreenLayer, readPrismDirectory, refineAsset, refineLimitState, renderWidget, resolveAnimatedChoice, resolveAnimationDuration, resolveBrandPalette, runForgeGeneration, scanForSecrets, schemaExampleFor, schemaToForm, templateAnimatedDefault, themeEntryToPalette, tryParseJsonObject, validateAgainstTemplateSchema, validateAssetSlots, validateFormValues, validateSchemaDefinition };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgesmith",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "forgesmith — content & asset-generation engine. Forge release notes, blog posts, social copy from prism0x2A data.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",