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/index.mjs CHANGED
@@ -2842,6 +2842,583 @@ async function generateKnowledgeCapture(options) {
2842
2842
  return buildFallback7(options);
2843
2843
  }
2844
2844
 
2845
+ // src/generators/generateApiChangelog.ts
2846
+ function buildPromptContext(opts) {
2847
+ const { apiChangeContext: ctx, amberContext, gitContext } = opts;
2848
+ const lines = [
2849
+ `## Change Range`,
2850
+ `From: ${ctx.from} To: ${ctx.to}`,
2851
+ `Total API changes: ${ctx.totalChanged}`,
2852
+ ``
2853
+ ];
2854
+ if (ctx.breakingChanges.length > 0) {
2855
+ lines.push(`## Breaking Changes (${ctx.breakingChanges.length})`);
2856
+ for (const c of ctx.breakingChanges) {
2857
+ const capHint = c.capabilityName ? ` [${c.capabilityName}]` : "";
2858
+ lines.push(`- ${c.endpoint}${capHint} \u2014 ${c.description ?? "no description"} (file: ${c.file})`);
2859
+ }
2860
+ lines.push("");
2861
+ }
2862
+ if (ctx.newEndpoints.length > 0) {
2863
+ lines.push(`## New Endpoints (${ctx.newEndpoints.length})`);
2864
+ for (const c of ctx.newEndpoints) {
2865
+ const capHint = c.capabilityName ? ` [${c.capabilityName}]` : "";
2866
+ lines.push(`- ${c.endpoint}${capHint} \u2014 ${c.description ?? "new"} (file: ${c.file})`);
2867
+ }
2868
+ lines.push("");
2869
+ }
2870
+ if (ctx.removedEndpoints.length > 0) {
2871
+ lines.push(`## Removed Endpoints (${ctx.removedEndpoints.length})`);
2872
+ for (const c of ctx.removedEndpoints) {
2873
+ lines.push(`- ${c.endpoint} \u2014 removed from ${c.file}`);
2874
+ }
2875
+ lines.push("");
2876
+ }
2877
+ const modified = ctx.changes.filter((c) => c.changeType === "modified");
2878
+ if (modified.length > 0) {
2879
+ lines.push(`## Modified Endpoints (${modified.length})`);
2880
+ for (const c of modified) {
2881
+ const capHint = c.capabilityName ? ` [${c.capabilityName}]` : "";
2882
+ lines.push(`- ${c.endpoint}${capHint} \u2014 ${c.description ?? "modified"} (file: ${c.file})`);
2883
+ }
2884
+ lines.push("");
2885
+ }
2886
+ if (amberContext) {
2887
+ lines.push(`## AMBER Capability Context`, amberContext.summary, "");
2888
+ }
2889
+ if (gitContext && gitContext.commitMessages.length > 0) {
2890
+ lines.push(
2891
+ `## Recent Commits`,
2892
+ ...gitContext.commitMessages.slice(0, 8).map((m) => `- ${m}`),
2893
+ ""
2894
+ );
2895
+ }
2896
+ return lines.join("\n");
2897
+ }
2898
+ function buildSystemPrompt14(audience, format) {
2899
+ const audienceDesc = audience === "external" ? "external API consumers (third-party developers)" : audience === "partner" ? "integration partners with existing API contracts" : "internal engineering teams";
2900
+ 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";
2901
+ 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.`;
2902
+ }
2903
+ function buildUserPrompt14(opts, context) {
2904
+ const { projectName, version, targetAudience, format } = opts;
2905
+ const v = version || opts.apiChangeContext.to;
2906
+ return [
2907
+ `Generate an API ${format} for: ${projectName}`,
2908
+ `Version: ${v}`,
2909
+ `Target audience: ${targetAudience}`,
2910
+ `Format: ${format}`,
2911
+ ``,
2912
+ context,
2913
+ ``,
2914
+ `## Output Format`,
2915
+ `Return a single JSON object with this EXACT shape:`,
2916
+ `{`,
2917
+ ` "version": "<version string>",`,
2918
+ ` "date": "<ISO date YYYY-MM-DD>",`,
2919
+ ` "targetAudience": "<audience>",`,
2920
+ ` "breakingChanges": [`,
2921
+ ` { "endpoint": "<METHOD /path>", "description": "<what changed>", "migration": "<how to migrate>" }`,
2922
+ ` ],`,
2923
+ ` "newFeatures": [`,
2924
+ ` { "endpoint": "<METHOD /path>", "description": "<what it does>", "example": "<optional curl/code hint>" }`,
2925
+ ` ],`,
2926
+ ` "improvements": [`,
2927
+ ` { "endpoint": "<METHOD /path>", "description": "<what improved>" }`,
2928
+ ` ],`,
2929
+ ` "deprecations": [`,
2930
+ ` { "endpoint": "<METHOD /path>", "description": "<what is deprecated>", "sunset": "<optional date>" }`,
2931
+ ` ],`,
2932
+ ` "migrationGuide": "<full migration guidance as prose \u2014 2\u20135 paragraphs>",`,
2933
+ ` "consumerImpactSummary": "<1 paragraph copy-ready summary for consumers>"`,
2934
+ `}`,
2935
+ ``,
2936
+ `Rules:`,
2937
+ `- Every breaking change MUST have a non-empty migration string.`,
2938
+ `- For external/partner audiences, omit internal implementation details.`,
2939
+ `- migrationGuide should be empty string if no breaking changes.`,
2940
+ `- Return ONLY valid JSON. No markdown fences.`
2941
+ ].join("\n");
2942
+ }
2943
+ function buildFallback8(opts) {
2944
+ const ctx = opts.apiChangeContext;
2945
+ const v = opts.version ?? ctx.to;
2946
+ return {
2947
+ version: v,
2948
+ date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
2949
+ targetAudience: opts.targetAudience,
2950
+ breakingChanges: ctx.breakingChanges.map((c) => ({
2951
+ endpoint: c.endpoint,
2952
+ description: c.description ?? "Breaking change detected",
2953
+ migration: "Review the updated API contract and update your client accordingly."
2954
+ })),
2955
+ newFeatures: ctx.newEndpoints.map((c) => ({
2956
+ endpoint: c.endpoint,
2957
+ description: c.description ?? "New endpoint added"
2958
+ })),
2959
+ improvements: ctx.changes.filter((c) => c.changeType === "modified").map((c) => ({
2960
+ endpoint: c.endpoint,
2961
+ description: c.description ?? "Endpoint updated"
2962
+ })),
2963
+ deprecations: ctx.removedEndpoints.map((c) => ({
2964
+ endpoint: c.endpoint,
2965
+ description: "This endpoint has been removed."
2966
+ })),
2967
+ 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.` : "",
2968
+ 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.")
2969
+ };
2970
+ }
2971
+ function parseLlmResponse8(raw) {
2972
+ let text = raw.trim();
2973
+ if (text.startsWith("```")) {
2974
+ text = text.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
2975
+ }
2976
+ let parsed;
2977
+ try {
2978
+ parsed = JSON.parse(text);
2979
+ } catch {
2980
+ return null;
2981
+ }
2982
+ function parseChangeArray(key, required) {
2983
+ if (!Array.isArray(parsed[key])) return [];
2984
+ return parsed[key].filter((item) => {
2985
+ if (typeof item !== "object" || item === null) return false;
2986
+ return required.every((k) => k in item);
2987
+ });
2988
+ }
2989
+ return {
2990
+ version: typeof parsed.version === "string" ? parsed.version : "",
2991
+ date: typeof parsed.date === "string" ? parsed.date : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
2992
+ targetAudience: typeof parsed.targetAudience === "string" ? parsed.targetAudience : "",
2993
+ breakingChanges: parseChangeArray("breakingChanges", ["endpoint", "description", "migration"]),
2994
+ newFeatures: parseChangeArray("newFeatures", ["endpoint", "description"]),
2995
+ improvements: parseChangeArray("improvements", ["endpoint", "description"]),
2996
+ deprecations: parseChangeArray("deprecations", ["endpoint", "description"]),
2997
+ migrationGuide: typeof parsed.migrationGuide === "string" ? parsed.migrationGuide : "",
2998
+ consumerImpactSummary: typeof parsed.consumerImpactSummary === "string" ? parsed.consumerImpactSummary : ""
2999
+ };
3000
+ }
3001
+ async function generateApiChangelog(opts) {
3002
+ const context = buildPromptContext(opts);
3003
+ try {
3004
+ const response = await opts.llm.complete({
3005
+ systemPrompt: buildSystemPrompt14(opts.targetAudience, opts.format),
3006
+ messages: [{ role: "user", content: buildUserPrompt14(opts, context) }],
3007
+ maxTokens: 3072
3008
+ });
3009
+ const parsed = parseLlmResponse8(response.content);
3010
+ if (parsed) return parsed;
3011
+ } catch {
3012
+ }
3013
+ return buildFallback8(opts);
3014
+ }
3015
+
3016
+ // src/generators/generateChangeImpactBrief.ts
3017
+ function buildPromptContext2(opts) {
3018
+ const { gitContext, changeImpactContext: ctx, amberContext, greenContext } = opts;
3019
+ const lines = [
3020
+ `## Change Range`,
3021
+ `From: ${ctx.from}`,
3022
+ `To: ${ctx.to}`,
3023
+ `Commits: ${ctx.commitCount}`,
3024
+ `Files changed: ${ctx.filesChanged.length}`,
3025
+ ``,
3026
+ `## Changed Files (sample)`,
3027
+ ...ctx.filesChanged.slice(0, 20).map((f) => `- ${f}`)
3028
+ ];
3029
+ if (ctx.breakingChanges.length > 0) {
3030
+ lines.push(``, `## Breaking Changes`, ...ctx.breakingChanges.map((b) => `- ${b}`));
3031
+ }
3032
+ if (ctx.affectedCapabilities.length > 0) {
3033
+ lines.push(``, `## Affected Business Capabilities`);
3034
+ for (const cap of ctx.affectedCapabilities) {
3035
+ lines.push(
3036
+ `- [${cap.criticality.toUpperCase()}] ${cap.name} (${cap.changedFiles.length} files changed, drift: ${cap.driftCount})`
3037
+ );
3038
+ }
3039
+ }
3040
+ if (ctx.regulatoryCapabilities.length > 0) {
3041
+ lines.push(
3042
+ ``,
3043
+ `## Critical Capabilities Touched (require approval)`,
3044
+ ...ctx.regulatoryCapabilities.map((c) => `- ${c}`)
3045
+ );
3046
+ }
3047
+ if (ctx.coherenceScoreBefore !== null || ctx.coherenceScoreAfter !== null) {
3048
+ lines.push(
3049
+ ``,
3050
+ `## Architecture Coherence`,
3051
+ `Score before: ${ctx.coherenceScoreBefore ?? "unknown"}`,
3052
+ `Score after: ${ctx.coherenceScoreAfter ?? "unknown"}`,
3053
+ `Delta: ${ctx.coherenceDelta !== null ? (ctx.coherenceDelta > 0 ? "+" : "") + ctx.coherenceDelta : "unknown"}`
3054
+ );
3055
+ }
3056
+ if (ctx.topRisks.length > 0) {
3057
+ lines.push(``, `## Top Risks`, ...ctx.topRisks.map((r) => `- ${r}`));
3058
+ }
3059
+ if (amberContext) {
3060
+ lines.push(``, `## AMBER Layer Context`, amberContext.summary);
3061
+ }
3062
+ if (greenContext) {
3063
+ lines.push(``, `## GREEN Layer Context`, greenContext.summary);
3064
+ }
3065
+ if (gitContext.commitMessages.length > 0) {
3066
+ lines.push(
3067
+ ``,
3068
+ `## Recent Commit Messages (sample)`,
3069
+ ...gitContext.commitMessages.slice(0, 10).map((m) => `- ${m}`)
3070
+ );
3071
+ }
3072
+ return lines.join("\n");
3073
+ }
3074
+ function buildSystemPrompt15(audience) {
3075
+ if (audience === "executive") {
3076
+ 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.";
3077
+ }
3078
+ if (audience === "cab") {
3079
+ 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.";
3080
+ }
3081
+ 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.";
3082
+ }
3083
+ function buildUserPrompt15(opts, context) {
3084
+ const { projectName, releaseVersion, audience } = opts;
3085
+ const version = releaseVersion || opts.changeImpactContext.to;
3086
+ return [
3087
+ `Generate a change impact brief for: ${projectName}`,
3088
+ `Release version: ${version}`,
3089
+ `Audience: ${audience}`,
3090
+ ``,
3091
+ context,
3092
+ ``,
3093
+ `## Output Format`,
3094
+ `Return a single JSON object with this EXACT shape:`,
3095
+ `{`,
3096
+ ` "title": "<brief title>",`,
3097
+ ` "releaseVersion": "<version string>",`,
3098
+ ` "date": "<ISO date>",`,
3099
+ ` "audience": "<audience>",`,
3100
+ ` "executiveSummary": "<3 bullet points separated by \\n, plain language, max 120 chars each>",`,
3101
+ ` "technicalSummary": "<2-3 sentences for CAB technical reviewers>",`,
3102
+ ` "affectedBusinessAreas": ["<area 1>", "<area 2>"],`,
3103
+ ` "riskLevel": "<one of: low | medium | high | critical>",`,
3104
+ ` "riskJustification": "<1-2 sentences explaining the risk rating>",`,
3105
+ ` "complianceImpact": "<which regulated capabilities are touched, or 'None' if clean>",`,
3106
+ ` "rollbackComplexity": "<one of: simple | moderate | complex>",`,
3107
+ ` "rollbackGuidance": "<concise rollback instructions>",`,
3108
+ ` "recommendedActions": ["<action 1>", "<action 2>", "<action 3>"],`,
3109
+ ` "approvalRequired": <true | false>,`,
3110
+ ` "metrics": {`,
3111
+ ` "commits": <number>,`,
3112
+ ` "filesChanged": <number>,`,
3113
+ ` "capabilitiesAffected": <number>,`,
3114
+ ` "coherenceDelta": <number | null>`,
3115
+ ` }`,
3116
+ `}`,
3117
+ ``,
3118
+ `Rules:`,
3119
+ `- riskLevel = "critical" if any critical capabilities touched OR breaking changes exist.`,
3120
+ `- riskLevel = "high" if 3+ capabilities affected or coherenceDelta < -5.`,
3121
+ `- approvalRequired = true if riskLevel is "critical" or "high".`,
3122
+ `- rollbackComplexity = "complex" if breaking changes or critical capabilities touched.`,
3123
+ `- executiveSummary must be EXACTLY 3 bullet lines joined by \\n \u2014 each starting with a dash.`,
3124
+ `- Return ONLY valid JSON. No markdown. No prose outside the JSON object.`
3125
+ ].join("\n");
3126
+ }
3127
+ function buildFallback9(opts) {
3128
+ const ctx = opts.changeImpactContext;
3129
+ const hasCritical = ctx.regulatoryCapabilities.length > 0 || ctx.breakingChanges.length > 0;
3130
+ const hasHigh = ctx.affectedCapabilities.filter((c) => c.criticality === "high").length >= 2;
3131
+ const riskLevel = hasCritical ? "critical" : hasHigh || ctx.affectedCapabilities.length >= 3 ? "high" : ctx.affectedCapabilities.length >= 1 ? "medium" : "low";
3132
+ const approvalRequired = riskLevel === "critical" || riskLevel === "high";
3133
+ const rollbackComplexity = hasCritical ? "complex" : ctx.affectedCapabilities.length >= 3 ? "moderate" : "simple";
3134
+ return {
3135
+ title: `Change Impact Brief \u2014 ${opts.projectName}`,
3136
+ releaseVersion: opts.releaseVersion ?? ctx.to,
3137
+ date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
3138
+ audience: opts.audience,
3139
+ executiveSummary: `- ${ctx.commitCount} commit(s) deployed across ${ctx.filesChanged.length} files
3140
+ - ${ctx.affectedCapabilities.length} business capability(ies) affected
3141
+ - Risk level: ${riskLevel.toUpperCase()}${hasCritical ? " \u2014 critical capabilities touched" : ""}`,
3142
+ 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}.` : ""),
3143
+ affectedBusinessAreas: ctx.affectedCapabilities.map((c) => c.name),
3144
+ riskLevel,
3145
+ riskJustification: hasCritical ? "Critical business capabilities were modified, requiring formal CAB sign-off." : `${ctx.affectedCapabilities.length} capabilities affected with potential for regression.`,
3146
+ complianceImpact: ctx.regulatoryCapabilities.length > 0 ? ctx.regulatoryCapabilities.join(", ") : "None",
3147
+ rollbackComplexity,
3148
+ 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.",
3149
+ recommendedActions: [
3150
+ approvalRequired ? "Obtain formal CAB sign-off before deploying to production" : "Deploy during low-traffic window",
3151
+ ctx.breakingChanges.length > 0 ? "Notify API consumers of breaking changes at least 48h in advance" : "Run full regression suite",
3152
+ "Monitor error rates and coherence score for 30 minutes post-deploy"
3153
+ ],
3154
+ approvalRequired,
3155
+ metrics: {
3156
+ commits: ctx.commitCount,
3157
+ filesChanged: ctx.filesChanged.length,
3158
+ capabilitiesAffected: ctx.affectedCapabilities.length,
3159
+ coherenceDelta: ctx.coherenceDelta
3160
+ }
3161
+ };
3162
+ }
3163
+ var RISK_LEVELS = ["low", "medium", "high", "critical"];
3164
+ var ROLLBACK_LEVELS = ["simple", "moderate", "complex"];
3165
+ function parseLlmResponse9(raw) {
3166
+ let text = raw.trim();
3167
+ if (text.startsWith("```")) {
3168
+ text = text.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
3169
+ }
3170
+ let parsed;
3171
+ try {
3172
+ parsed = JSON.parse(text);
3173
+ } catch {
3174
+ return null;
3175
+ }
3176
+ const riskLevel = RISK_LEVELS.includes(parsed.riskLevel) ? parsed.riskLevel : "medium";
3177
+ const rollbackComplexity = ROLLBACK_LEVELS.includes(
3178
+ parsed.rollbackComplexity
3179
+ ) ? parsed.rollbackComplexity : "moderate";
3180
+ const recommendedActions = Array.isArray(parsed.recommendedActions) ? parsed.recommendedActions.filter((a) => typeof a === "string") : [];
3181
+ const affectedBusinessAreas = Array.isArray(parsed.affectedBusinessAreas) ? parsed.affectedBusinessAreas.filter((a) => typeof a === "string") : [];
3182
+ const metricsRaw = parsed.metrics && typeof parsed.metrics === "object" ? parsed.metrics : {};
3183
+ return {
3184
+ title: typeof parsed.title === "string" ? parsed.title : "Change Impact Brief",
3185
+ releaseVersion: typeof parsed.releaseVersion === "string" ? parsed.releaseVersion : "",
3186
+ date: typeof parsed.date === "string" ? parsed.date : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
3187
+ audience: typeof parsed.audience === "string" ? parsed.audience : "",
3188
+ executiveSummary: typeof parsed.executiveSummary === "string" ? parsed.executiveSummary : "",
3189
+ technicalSummary: typeof parsed.technicalSummary === "string" ? parsed.technicalSummary : "",
3190
+ affectedBusinessAreas,
3191
+ riskLevel,
3192
+ riskJustification: typeof parsed.riskJustification === "string" ? parsed.riskJustification : "",
3193
+ complianceImpact: typeof parsed.complianceImpact === "string" ? parsed.complianceImpact : "None",
3194
+ rollbackComplexity,
3195
+ rollbackGuidance: typeof parsed.rollbackGuidance === "string" ? parsed.rollbackGuidance : "",
3196
+ recommendedActions,
3197
+ approvalRequired: parsed.approvalRequired === true,
3198
+ metrics: {
3199
+ commits: typeof metricsRaw.commits === "number" ? metricsRaw.commits : 0,
3200
+ filesChanged: typeof metricsRaw.filesChanged === "number" ? metricsRaw.filesChanged : 0,
3201
+ capabilitiesAffected: typeof metricsRaw.capabilitiesAffected === "number" ? metricsRaw.capabilitiesAffected : 0,
3202
+ coherenceDelta: typeof metricsRaw.coherenceDelta === "number" ? metricsRaw.coherenceDelta : null
3203
+ }
3204
+ };
3205
+ }
3206
+ async function generateChangeImpactBrief(opts) {
3207
+ const context = buildPromptContext2(opts);
3208
+ try {
3209
+ const response = await opts.llm.complete({
3210
+ systemPrompt: buildSystemPrompt15(opts.audience),
3211
+ messages: [{ role: "user", content: buildUserPrompt15(opts, context) }],
3212
+ maxTokens: 2048
3213
+ });
3214
+ const parsed = parseLlmResponse9(response.content);
3215
+ if (parsed) return parsed;
3216
+ } catch {
3217
+ }
3218
+ return buildFallback9(opts);
3219
+ }
3220
+
3221
+ // src/generators/generateRoiSlide.ts
3222
+ var CURRENCY_SYMBOL = {
3223
+ EUR: "\u20AC",
3224
+ USD: "$",
3225
+ GBP: "\xA3"
3226
+ };
3227
+ function fmtK(n, sym) {
3228
+ if (n >= 1e6) return `${sym}${(n / 1e6).toFixed(1)}M`;
3229
+ if (n >= 1e3) return `${sym}${Math.round(n / 1e3)}k`;
3230
+ return `${sym}${Math.round(n)}`;
3231
+ }
3232
+ function buildFallback10(opts) {
3233
+ const { roiEstimate: r, organizationName, currency } = opts;
3234
+ const sym = CURRENCY_SYMBOL[currency] ?? "\u20AC";
3235
+ const org = organizationName ?? "Your Organization";
3236
+ const costYr = fmtK(r.maintenanceCostPerYear, sym);
3237
+ const saveYr = fmtK(r.savedCostPerYear, sym);
3238
+ const saveMo = fmtK(r.savedCostPerMonth, sym);
3239
+ const slides = [
3240
+ {
3241
+ type: "title",
3242
+ title: "Technical Debt Cost Analysis",
3243
+ content: `${org} \xB7 Architecture Health Report \xB7 ${(/* @__PURE__ */ new Date()).toLocaleDateString("en-GB", { month: "long", year: "numeric" })}`,
3244
+ highlight: r.currentGrade,
3245
+ highlightLabel: "Current Architecture Grade"
3246
+ },
3247
+ {
3248
+ type: "problem",
3249
+ title: "The Hidden Cost of Architecture Debt",
3250
+ content: `Your codebase currently has grade ${r.currentGrade} (${r.currentScore}/100). This translates directly to developer time lost to maintenance overhead every month.`,
3251
+ highlight: fmtK(r.maintenanceCostPerMonth, sym),
3252
+ highlightLabel: "Estimated monthly maintenance cost",
3253
+ bullets: [
3254
+ `Import cycles: ${r.breakdown.importCycles.count} cycles consuming ${r.breakdown.importCycles.hoursPerMonth}h/mo`,
3255
+ `Documentation drift: ${r.breakdown.documentationDrift.count} drifted capabilities \u2014 ${r.breakdown.documentationDrift.hoursPerMonth}h/mo in context-searching`,
3256
+ `High-churn files: ${r.breakdown.highChurnFiles.count} hot files adding ${r.breakdown.highChurnFiles.hoursPerMonth}h/mo in review overhead`,
3257
+ `Orphaned capabilities: ${r.breakdown.orphanedCode.count} untracked modules \u2014 ${r.breakdown.orphanedCode.hoursPerMonth}h/mo wasted`
3258
+ ]
3259
+ },
3260
+ {
3261
+ type: "cost-breakdown",
3262
+ title: "Where Developer Time Goes",
3263
+ content: `Total: ${r.estimatedMaintenanceHoursPerMonth}h/month in maintenance overhead = ${costYr}/year at current state`,
3264
+ bullets: [
3265
+ `Import cycles \u2192 ${fmtK(r.breakdown.importCycles.cost, sym)}/mo (${r.breakdown.importCycles.hoursPerMonth}h debugging)`,
3266
+ `Doc drift \u2192 ${fmtK(r.breakdown.documentationDrift.cost, sym)}/mo (${r.breakdown.documentationDrift.hoursPerMonth}h searching context)`,
3267
+ `Churn overhead \u2192 ${fmtK(r.breakdown.highChurnFiles.cost, sym)}/mo (${r.breakdown.highChurnFiles.hoursPerMonth}h in review)`,
3268
+ `Orphaned code \u2192 ${fmtK(r.breakdown.orphanedCode.cost, sym)}/mo (${r.breakdown.orphanedCode.hoursPerMonth}h ownership gaps)`
3269
+ ]
3270
+ },
3271
+ {
3272
+ type: "scenarios",
3273
+ title: "What Reaching Grade B Saves",
3274
+ content: `Moving from ${r.currentGrade} to ${r.targetGrade} cuts maintenance overhead by ~${Math.round(r.savedCostPerMonth / Math.max(r.maintenanceCostPerMonth, 1) * 100)}%.`,
3275
+ highlight: saveYr,
3276
+ highlightLabel: `Annual savings at grade ${r.targetGrade}`,
3277
+ bullets: [
3278
+ `Monthly savings: ${saveMo}/mo`,
3279
+ `ROI multiple: ${r.roiMultiple}\xD7 return on remediation investment`,
3280
+ `Break-even: ~${r.breakEvenMonths < 1 ? `${Math.round(r.breakEvenMonths * 30)} days` : `${r.breakEvenMonths} months`}`,
3281
+ `Remediation effort: ~${r.estimatedRemediationDays} engineering days`
3282
+ ]
3283
+ },
3284
+ {
3285
+ type: "recommendation",
3286
+ title: "Recommended Remediation Path",
3287
+ content: r.recommendation,
3288
+ bullets: [
3289
+ `Step 1: Resolve ${r.breakdown.importCycles.count} import cycles (est. ${Math.round(r.estimatedRemediationDays * 0.4)} days)`,
3290
+ `Step 2: Fix ${r.breakdown.documentationDrift.count} drifted capability docs (est. ${Math.round(r.estimatedRemediationDays * 0.3)} days)`,
3291
+ `Step 3: Assign ownership for ${r.breakdown.orphanedCode.count} orphaned capabilities (est. ${Math.round(r.estimatedRemediationDays * 0.2)} days)`,
3292
+ `Step 4: Add coverage to top churn files (est. ${Math.round(r.estimatedRemediationDays * 0.1)} days)`
3293
+ ]
3294
+ },
3295
+ {
3296
+ type: "cta",
3297
+ title: `Approve ${(/* @__PURE__ */ new Date()).toLocaleString("default", { month: "short" })} Refactoring Budget`,
3298
+ content: `Investment: ~${fmtK(r.estimatedRemediationDays * 8 * 150, sym)} (${r.estimatedRemediationDays} days of engineering effort)
3299
+ Return: ${saveYr}/year in recovered developer capacity`,
3300
+ highlight: `${r.roiMultiple}\xD7`,
3301
+ highlightLabel: "ROI multiple",
3302
+ bullets: [
3303
+ `${saveYr}/year recurring savings`,
3304
+ `Break-even in ${r.breakEvenMonths < 1 ? `${Math.round(r.breakEvenMonths * 30)} days` : `${r.breakEvenMonths} months`}`,
3305
+ `Measurable via PRISM architecture grade (${r.currentGrade} \u2192 ${r.targetGrade})`
3306
+ ]
3307
+ }
3308
+ ];
3309
+ const executiveSummary = [
3310
+ `\u2022 Current architecture grade ${r.currentGrade} (${r.currentScore}/100) costs ~${costYr}/year in maintenance overhead.`,
3311
+ `\u2022 Improving to grade ${r.targetGrade} saves ~${saveYr}/year \u2014 ${r.roiMultiple}\xD7 return on a ${r.estimatedRemediationDays}-day remediation effort.`,
3312
+ `\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.`
3313
+ ].join("\n");
3314
+ const oneLinePitch = `Moving from ${r.currentGrade} to ${r.targetGrade} saves ${saveYr} annually \u2014 ${r.roiMultiple}\xD7 ROI on a ${r.estimatedRemediationDays}-day investment.`;
3315
+ return {
3316
+ title: `Technical Debt ROI \u2014 ${org}`,
3317
+ slides,
3318
+ executiveSummary,
3319
+ oneLinePitch
3320
+ };
3321
+ }
3322
+ function buildSystemPrompt16() {
3323
+ 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.";
3324
+ }
3325
+ function buildUserPrompt16(opts) {
3326
+ const { roiEstimate: r, organizationName, currency } = opts;
3327
+ const sym = CURRENCY_SYMBOL[currency] ?? "\u20AC";
3328
+ const org = organizationName ?? "the organization";
3329
+ return [
3330
+ `Generate a 6-slide executive ROI presentation for ${org}.`,
3331
+ ``,
3332
+ `## ROI Data`,
3333
+ `Current grade: ${r.currentGrade} (score ${r.currentScore}/100)`,
3334
+ `Target grade: ${r.targetGrade} (score ${r.targetScore}/100)`,
3335
+ `Monthly maintenance cost: ${sym}${r.maintenanceCostPerMonth.toLocaleString()}`,
3336
+ `Annual maintenance cost: ${sym}${r.maintenanceCostPerYear.toLocaleString()}`,
3337
+ `Monthly savings if improved: ${sym}${r.savedCostPerMonth.toLocaleString()}`,
3338
+ `Annual savings if improved: ${sym}${r.savedCostPerYear.toLocaleString()}`,
3339
+ `ROI multiple: ${r.roiMultiple}x`,
3340
+ `Break-even: ${r.breakEvenMonths} months`,
3341
+ `Remediation days: ${r.estimatedRemediationDays}`,
3342
+ ``,
3343
+ `## Pain sources`,
3344
+ `Import cycles: ${r.breakdown.importCycles.count} cycles, ${r.breakdown.importCycles.hoursPerMonth}h/mo, ${sym}${r.breakdown.importCycles.cost.toLocaleString()}/mo`,
3345
+ `Doc drift: ${r.breakdown.documentationDrift.count} drifted, ${r.breakdown.documentationDrift.hoursPerMonth}h/mo, ${sym}${r.breakdown.documentationDrift.cost.toLocaleString()}/mo`,
3346
+ `High churn: ${r.breakdown.highChurnFiles.count} files, ${r.breakdown.highChurnFiles.hoursPerMonth}h/mo, ${sym}${r.breakdown.highChurnFiles.cost.toLocaleString()}/mo`,
3347
+ `Orphaned: ${r.breakdown.orphanedCode.count} capabilities, ${r.breakdown.orphanedCode.hoursPerMonth}h/mo, ${sym}${r.breakdown.orphanedCode.cost.toLocaleString()}/mo`,
3348
+ ``,
3349
+ `## Output Format`,
3350
+ `Return a single JSON object with this exact shape:`,
3351
+ `{`,
3352
+ ` "title": "<deck title>",`,
3353
+ ` "slides": [`,
3354
+ ` {`,
3355
+ ` "type": "<title|problem|cost-breakdown|scenarios|recommendation|cta>",`,
3356
+ ` "title": "<slide title>",`,
3357
+ ` "content": "<1-2 sentences of slide body copy>",`,
3358
+ ` "highlight": "<optional large callout \u2014 a number or grade>",`,
3359
+ ` "highlightLabel": "<label for the highlight>",`,
3360
+ ` "bullets": ["<bullet 1>", "<bullet 2>", "<bullet 3>"]`,
3361
+ ` }`,
3362
+ ` ],`,
3363
+ ` "executiveSummary": "<3-bullet executive summary as a single string, bullets separated by \\n>",`,
3364
+ ` "oneLinePitch": "<one sentence pitch for email subject line>"`,
3365
+ `}`,
3366
+ ``,
3367
+ `Rules:`,
3368
+ `- Exactly 6 slides in order: title, problem, cost-breakdown, scenarios, recommendation, cta`,
3369
+ `- bullets: max 4 per slide, each \u2264 90 characters`,
3370
+ `- content: 1\u20132 sentences, specific numbers from the data above`,
3371
+ `- Use business language \u2014 avoid engineering jargon`,
3372
+ `- Return ONLY valid JSON. No markdown. No prose outside the JSON.`
3373
+ ].join("\n");
3374
+ }
3375
+ function parseResponse(raw) {
3376
+ let text = raw.trim();
3377
+ if (text.startsWith("```")) {
3378
+ text = text.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
3379
+ }
3380
+ try {
3381
+ const parsed = JSON.parse(text);
3382
+ const rawSlides = Array.isArray(parsed.slides) ? parsed.slides : [];
3383
+ if (rawSlides.length === 0) return null;
3384
+ const VALID_TYPES = ["title", "problem", "cost-breakdown", "scenarios", "recommendation", "cta"];
3385
+ const slides = rawSlides.map((s, i) => {
3386
+ const slide = s ?? {};
3387
+ const type = typeof slide.type === "string" && VALID_TYPES.includes(slide.type) ? slide.type : VALID_TYPES[i] ?? "recommendation";
3388
+ const bullets = Array.isArray(slide.bullets) ? slide.bullets.filter((b) => typeof b === "string").slice(0, 4) : void 0;
3389
+ return {
3390
+ type,
3391
+ title: typeof slide.title === "string" ? slide.title : `Slide ${i + 1}`,
3392
+ content: typeof slide.content === "string" ? slide.content : "",
3393
+ ...typeof slide.highlight === "string" && slide.highlight ? { highlight: slide.highlight } : {},
3394
+ ...typeof slide.highlightLabel === "string" && slide.highlightLabel ? { highlightLabel: slide.highlightLabel } : {},
3395
+ ...bullets && bullets.length > 0 ? { bullets } : {}
3396
+ };
3397
+ });
3398
+ return {
3399
+ title: typeof parsed.title === "string" ? parsed.title : "Technical Debt ROI",
3400
+ slides,
3401
+ executiveSummary: typeof parsed.executiveSummary === "string" ? parsed.executiveSummary : "",
3402
+ oneLinePitch: typeof parsed.oneLinePitch === "string" ? parsed.oneLinePitch : ""
3403
+ };
3404
+ } catch {
3405
+ return null;
3406
+ }
3407
+ }
3408
+ async function generateRoiSlide(opts) {
3409
+ try {
3410
+ const response = await opts.llm.complete({
3411
+ systemPrompt: buildSystemPrompt16(),
3412
+ messages: [{ role: "user", content: buildUserPrompt16(opts) }],
3413
+ maxTokens: 3e3
3414
+ });
3415
+ const parsed = parseResponse(response.content);
3416
+ if (parsed) return parsed;
3417
+ } catch {
3418
+ }
3419
+ return buildFallback10(opts);
3420
+ }
3421
+
2845
3422
  // src/forge/types.ts
2846
3423
  var asAudienceId = (id) => id;
2847
3424
 
@@ -5366,7 +5943,7 @@ function getDispatchChannel(id) {
5366
5943
  }
5367
5944
 
5368
5945
  // src/forge/dispatch.ts
5369
- function buildSystemPrompt14(channel, audience, brand, blueprintContext, toneOffset) {
5946
+ function buildSystemPrompt17(channel, audience, brand, blueprintContext, toneOffset) {
5370
5947
  const lines = [
5371
5948
  `You are a skilled content writer producing a ${channel.name} post.`,
5372
5949
  "",
@@ -5417,7 +5994,7 @@ function truncate(content, maxLength) {
5417
5994
  async function generateForChannel(ask, channel, provider, opts) {
5418
5995
  const now = (/* @__PURE__ */ new Date()).toISOString();
5419
5996
  try {
5420
- const system = buildSystemPrompt14(
5997
+ const system = buildSystemPrompt17(
5421
5998
  channel,
5422
5999
  opts.audience,
5423
6000
  opts.brand,
@@ -5562,7 +6139,7 @@ function nextVersionNumber(versions) {
5562
6139
  }
5563
6140
 
5564
6141
  // src/forge/refineAsset.ts
5565
- function buildSystemPrompt15(input) {
6142
+ function buildSystemPrompt18(input) {
5566
6143
  const parts = [
5567
6144
  `You are a content refinement assistant for a developer-focused content tool (forge0x2B).`,
5568
6145
  `You are refining an existing ${input.assetType.replace(/-/g, " ")} asset.`,
@@ -5601,7 +6178,7 @@ function parseRefinedResponse(raw) {
5601
6178
  return { newContent, llmReply };
5602
6179
  }
5603
6180
  async function refineAsset(input, provider) {
5604
- const systemPrompt = buildSystemPrompt15(input);
6181
+ const systemPrompt = buildSystemPrompt18(input);
5605
6182
  if (scanForSecrets(systemPrompt)) {
5606
6183
  throw new Error("refineAsset: secret pattern detected in asset content. Refusing to send to LLM.");
5607
6184
  }
@@ -5763,4 +6340,4 @@ function previewExport(entries, format) {
5763
6340
  }
5764
6341
  }
5765
6342
 
5766
- export { ANIMATION_DURATION_PRESETS, BRAND_CONTENT_SLOT_KEYS, BUNDLED_DISPATCH_CHANNELS, BUNDLED_STYLE_PRESETS, BUNDLED_WIDGET_TEMPLATES, DEFAULT_ANIMATION_DURATION_SECONDS, DEFAULT_BRAND_KIT_FONTS, DEFAULT_BRAND_KIT_PALETTE, DEFAULT_BRAND_KIT_VOICE, DISPATCH_CHANNEL_BLOG, DISPATCH_CHANNEL_EMAIL, DISPATCH_CHANNEL_HN, DISPATCH_CHANNEL_INSTAGRAM, DISPATCH_CHANNEL_LINKEDIN, DISPATCH_CHANNEL_NEWSLETTER, DISPATCH_CHANNEL_REDDIT, DISPATCH_CHANNEL_SLACK, DISPATCH_CHANNEL_TWEET, FORGE_AUDIENCES, FORGE_BRAND_THEME_ID, FORGE_TEMPLATES, FREE_TIER_WIDGET_IDS, MAX_ANIMATION_DURATION_SECONDS, MIN_ANIMATION_DURATION_SECONDS, PRISM_TEMPLATES, PRISM_TEMPLATE_ARCHITECTURE_OVERVIEW, PRISM_TEMPLATE_REFACTOR_RATIONALE, PRISM_TEMPLATE_RELEASE_ANNOUNCEMENT, PRISM_TEMPLATE_SHIPPING_DIGEST, PRISM_TEMPLATE_ZONE_DEEPDIVE, REFINE_COUNTDOWN_THRESHOLD, REFINE_SESSION_SOFT_CAP, REFINE_SUGGESTIONS, STYLE_PRESET_BRUTALIST, STYLE_PRESET_DEFAULT, STYLE_PRESET_GLASSY, STYLE_PRESET_MINIMAL, TEMPLATE_SCHEMA_EXAMPLES, TEMPLATE_SCHEMA_GENERIC, WIDGET_TEMPLATE_CHANGELOG_ROW, WIDGET_TEMPLATE_CTA_BANNER, WIDGET_TEMPLATE_FEATURE_GRID, WIDGET_TEMPLATE_METRIC_BADGE, WIDGET_TEMPLATE_PRICING_TIER, WIDGET_TEMPLATE_SOCIAL_PROOF, WIDGET_TEMPLATE_STAT_CARD, WIDGET_TEMPLATE_TESTIMONIAL, applyEntryPatch, asAudienceId, assembleBrandUrlExtractionPrompt, assembleForgePrompt, brandThemeConfigToEntry, buildRevertVersion, buildScheduledEntry, buildVersion, cascadingScheduledFor, clampAnimationDuration, computeDiff, defaultBrandKit, defaultValueForField, distill, entryInRange, exportToBufferCsv, exportToHypefuryCsv, exportToICalendar, generateADR, generateArc42, generateArchitectureWalkthrough, generateAskDrivenAsset, generateChangesSince, generateComplianceDoc, generateKnowledgeCapture, generateNewsletter, generateOnboardingDoc, generatePresentation, generateRadio, generateRefactoringReport, generateReleaseNotes, generateSprintRetro, getDispatchChannel, getPrismTemplate, getSlotValue, getStyleById, getWidgetTemplate, initialFormValues, next7DaysRange, nextVersionNumber, orchestrateDispatch, parseAndValidateSchemaDefinition, parseBrandKitFromLlmResponse, parseBrandThemeContent, parseStyleFromCss, parseStyleFromTailwindConfig, parseStyleFromTokensJson, parseThemeConfigContent, previewExport, refineAsset, refineLimitState, renderWidget, resolveAnimatedChoice, resolveAnimationDuration, resolveBrandPalette, runForgeGeneration, scanForSecrets, schemaExampleFor, schemaToForm, templateAnimatedDefault, themeEntryToPalette, tryParseJsonObject, validateAgainstTemplateSchema, validateAssetSlots, validateFormValues, validateSchemaDefinition };
6343
+ export { ANIMATION_DURATION_PRESETS, BRAND_CONTENT_SLOT_KEYS, BUNDLED_DISPATCH_CHANNELS, BUNDLED_STYLE_PRESETS, BUNDLED_WIDGET_TEMPLATES, DEFAULT_ANIMATION_DURATION_SECONDS, DEFAULT_BRAND_KIT_FONTS, DEFAULT_BRAND_KIT_PALETTE, DEFAULT_BRAND_KIT_VOICE, DISPATCH_CHANNEL_BLOG, DISPATCH_CHANNEL_EMAIL, DISPATCH_CHANNEL_HN, DISPATCH_CHANNEL_INSTAGRAM, DISPATCH_CHANNEL_LINKEDIN, DISPATCH_CHANNEL_NEWSLETTER, DISPATCH_CHANNEL_REDDIT, DISPATCH_CHANNEL_SLACK, DISPATCH_CHANNEL_TWEET, FORGE_AUDIENCES, FORGE_BRAND_THEME_ID, FORGE_TEMPLATES, FREE_TIER_WIDGET_IDS, MAX_ANIMATION_DURATION_SECONDS, MIN_ANIMATION_DURATION_SECONDS, PRISM_TEMPLATES, PRISM_TEMPLATE_ARCHITECTURE_OVERVIEW, PRISM_TEMPLATE_REFACTOR_RATIONALE, PRISM_TEMPLATE_RELEASE_ANNOUNCEMENT, PRISM_TEMPLATE_SHIPPING_DIGEST, PRISM_TEMPLATE_ZONE_DEEPDIVE, REFINE_COUNTDOWN_THRESHOLD, REFINE_SESSION_SOFT_CAP, REFINE_SUGGESTIONS, STYLE_PRESET_BRUTALIST, STYLE_PRESET_DEFAULT, STYLE_PRESET_GLASSY, STYLE_PRESET_MINIMAL, TEMPLATE_SCHEMA_EXAMPLES, TEMPLATE_SCHEMA_GENERIC, WIDGET_TEMPLATE_CHANGELOG_ROW, WIDGET_TEMPLATE_CTA_BANNER, WIDGET_TEMPLATE_FEATURE_GRID, WIDGET_TEMPLATE_METRIC_BADGE, WIDGET_TEMPLATE_PRICING_TIER, WIDGET_TEMPLATE_SOCIAL_PROOF, WIDGET_TEMPLATE_STAT_CARD, WIDGET_TEMPLATE_TESTIMONIAL, applyEntryPatch, asAudienceId, assembleBrandUrlExtractionPrompt, assembleForgePrompt, brandThemeConfigToEntry, buildRevertVersion, buildScheduledEntry, buildVersion, cascadingScheduledFor, clampAnimationDuration, computeDiff, defaultBrandKit, defaultValueForField, distill, entryInRange, exportToBufferCsv, exportToHypefuryCsv, exportToICalendar, generateADR, generateApiChangelog, generateArc42, generateArchitectureWalkthrough, generateAskDrivenAsset, generateChangeImpactBrief, generateChangesSince, generateComplianceDoc, generateKnowledgeCapture, generateNewsletter, generateOnboardingDoc, generatePresentation, generateRadio, generateRefactoringReport, generateReleaseNotes, generateRoiSlide, generateSprintRetro, getDispatchChannel, getPrismTemplate, getSlotValue, getStyleById, getWidgetTemplate, initialFormValues, next7DaysRange, nextVersionNumber, orchestrateDispatch, parseAndValidateSchemaDefinition, parseBrandKitFromLlmResponse, parseBrandThemeContent, parseStyleFromCss, parseStyleFromTailwindConfig, parseStyleFromTokensJson, parseThemeConfigContent, previewExport, refineAsset, refineLimitState, renderWidget, resolveAnimatedChoice, resolveAnimationDuration, resolveBrandPalette, runForgeGeneration, scanForSecrets, schemaExampleFor, schemaToForm, templateAnimatedDefault, themeEntryToPalette, tryParseJsonObject, validateAgainstTemplateSchema, validateAssetSlots, validateFormValues, validateSchemaDefinition };