forgesmith 0.2.0 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 — Ask-Driven Asset Generation
4
+
5
+ - `generateAskDrivenAsset(blueprint, question, opts, provider)` — generate any content format from a natural-language question about your codebase architecture
6
+ - Formats: `markdown` | `blog` | `social` | `email` | `slack` | `slide`
7
+ - Tones: `professional` | `casual` | `technical` | `executive`
8
+ - Grounded in Blueprint dependency graph (top files by usage, edge sample, category breakdown)
9
+ - Format-aware token budgets (512 for social/slack, 3072 for blog/markdown, 2048 for slides)
10
+ - Use-cases: blog posts, tweets, investor-update bullets, Slack status, slide decks
11
+ - New exported types: `AskDrivenAssetOpts`, `AskDrivenAssetFormat`
12
+ - 52 unit tests, all green (+8 new)
13
+
3
14
  ## 0.2.0 — Architecture Narrative Generators
4
15
 
5
16
  - `generateArchitectureWalkthrough(blueprint, opts, provider)` — long-form narrative explaining structure, key files, and important relationships. Tones: `technical` | `casual` | `onboarding`
package/dist/index.cjs CHANGED
@@ -383,7 +383,90 @@ async function readBlueprintData(targetPath) {
383
383
  }
384
384
  }
385
385
 
386
+ // src/generators/askDrivenAsset.ts
387
+ var NO_DATA_MSG5 = "No Blueprint data available. Run prism scan first.";
388
+ var FORMAT_GUIDES = {
389
+ markdown: { name: "Markdown document", structure: "Use headers, bullet lists, and code blocks where appropriate.", maxTokens: 3072 },
390
+ blog: { name: "blog post", structure: "Write with an engaging intro, clear sections, a conclusion, and a call-to-action.", maxTokens: 3072 },
391
+ social: { name: "social media post", structure: "Write concise, punchy content suitable for Twitter/LinkedIn. Max 280 characters for Twitter mode.", maxTokens: 512 },
392
+ email: { name: "email", structure: "Use Subject:, greeting, body paragraphs, and a sign-off.", maxTokens: 1024 },
393
+ slack: { name: "Slack message", structure: "Keep it conversational, use *bold* for emphasis, bullet points for lists. Max 3 paragraphs.", maxTokens: 512 },
394
+ slide: { name: "presentation outline", structure: "Structure as slide titles with 3-5 bullet points each. Include a title slide and summary slide.", maxTokens: 2048 }
395
+ };
396
+ function buildSystemPrompt6(format, tone) {
397
+ const toneMap = {
398
+ professional: "You are a professional technical writer and developer advocate.",
399
+ casual: "You are a friendly engineering blogger who writes in an approachable, conversational style.",
400
+ technical: "You are a senior software engineer writing precise, implementation-focused content.",
401
+ executive: "You are a VP of Engineering writing high-level, business-value-focused content for leadership."
402
+ };
403
+ const guide = FORMAT_GUIDES[format];
404
+ 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.`;
405
+ }
406
+ function buildUserPrompt6(blueprint, question, opts) {
407
+ const format = opts.format ?? "markdown";
408
+ const tone = opts.tone ?? "professional";
409
+ const length = opts.length ?? "medium";
410
+ const guide = FORMAT_GUIDES[format];
411
+ const lengthGuide = {
412
+ short: "Keep it concise \u2014 1-2 paragraphs or equivalent.",
413
+ medium: "Medium length \u2014 3-5 paragraphs or equivalent.",
414
+ long: "Detailed and comprehensive \u2014 cover the topic thoroughly."
415
+ }[length];
416
+ const topFiles = blueprint.files.slice().sort((a, b) => (b.importedByCount ?? 0) - (a.importedByCount ?? 0)).slice(0, 12);
417
+ const lines = [
418
+ `## User's Question`,
419
+ question,
420
+ ``,
421
+ `## Output Requirements`,
422
+ `- Format: ${guide.name}`,
423
+ `- Tone: ${tone}`,
424
+ `- Length: ${length} (${lengthGuide})`,
425
+ ``,
426
+ `## Codebase Architecture Context`,
427
+ `Target: ${blueprint.targetPath}`,
428
+ `Total files: ${blueprint.stats.totalFiles} | Dependency edges: ${blueprint.stats.runtimeEdges}`,
429
+ `Categories: app=${blueprint.categories.app ?? 0}, components=${blueprint.categories.component ?? 0}, lib=${blueprint.categories.lib ?? 0}, hooks=${blueprint.categories.hook ?? 0}`,
430
+ ``,
431
+ `Key files (by usage):`,
432
+ ...topFiles.map((f) => `- ${f.path} [${f.category ?? "?"}] \u2014 imported by ${f.importedByCount ?? 0} files, ${f.lineCount ?? "?"} lines`)
433
+ ];
434
+ if (blueprint.edges.length > 0) {
435
+ const edgeSample = blueprint.edges.slice(0, 15);
436
+ lines.push(``, `Dependency edges (sample):`, ...edgeSample.map((e) => `- ${e.from} \u2192 ${e.to}`));
437
+ }
438
+ lines.push(``, `Answer the user's question using the architecture context above. Generate the ${guide.name} now.`);
439
+ return lines.join("\n");
440
+ }
441
+ async function generateAskDrivenAsset(blueprint, question, opts, provider) {
442
+ if (!blueprint) {
443
+ return {
444
+ text: NO_DATA_MSG5,
445
+ metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), usedTokens: 0, generator: "forgesmith" }
446
+ };
447
+ }
448
+ if (!question || !question.trim()) {
449
+ return {
450
+ text: "No question provided. Please ask something about your codebase.",
451
+ metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), usedTokens: 0, generator: "forgesmith" }
452
+ };
453
+ }
454
+ const format = opts.format ?? "markdown";
455
+ const tone = opts.tone ?? "professional";
456
+ const maxTokens = FORMAT_GUIDES[format].maxTokens;
457
+ const response = await provider.complete({
458
+ systemPrompt: buildSystemPrompt6(format, tone),
459
+ messages: [{ role: "user", content: buildUserPrompt6(blueprint, question, opts) }],
460
+ maxTokens
461
+ });
462
+ return {
463
+ text: response.content,
464
+ metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), usedTokens: response.usedTokens, generator: "forgesmith" }
465
+ };
466
+ }
467
+
386
468
  exports.generateArchitectureWalkthrough = generateArchitectureWalkthrough;
469
+ exports.generateAskDrivenAsset = generateAskDrivenAsset;
387
470
  exports.generateChangesSince = generateChangesSince;
388
471
  exports.generateOnboardingDoc = generateOnboardingDoc;
389
472
  exports.generateRefactoringReport = generateRefactoringReport;
package/dist/index.d.cts CHANGED
@@ -79,6 +79,12 @@ interface RefactoringReportOpts {
79
79
  length?: "short" | "medium" | "long";
80
80
  format?: "markdown" | "plain";
81
81
  }
82
+ type AskDrivenAssetFormat = "markdown" | "blog" | "social" | "email" | "slack" | "slide";
83
+ interface AskDrivenAssetOpts {
84
+ format?: AskDrivenAssetFormat;
85
+ tone?: "professional" | "casual" | "technical" | "executive";
86
+ length?: "short" | "medium" | "long";
87
+ }
82
88
  interface GenerationResult {
83
89
  text: string;
84
90
  metadata: {
@@ -119,4 +125,6 @@ declare function generateRefactoringReport(blueprint: BlueprintData | null, opts
119
125
  declare function readPrismDirectory(prismPath: string): Promise<PrismData>;
120
126
  declare function readBlueprintData(targetPath: string): Promise<BlueprintData | null>;
121
127
 
122
- export { type ArchitectureWalkthroughOpts, type BlueprintData, type BlueprintEdge, type BlueprintFile, type ChangesSinceOpts, type GenerationResult, type LlmMessage, type LlmProvider, type LlmRequest, type LlmResponse, type OnboardingDocOpts, type PrismData, type PrismInsight, type PrismRecommendation, type PrismSession, type RefactoringReportOpts, type ReleaseNotesOpts, generateArchitectureWalkthrough, generateChangesSince, generateOnboardingDoc, generateRefactoringReport, generateReleaseNotes, readBlueprintData, readPrismDirectory };
128
+ declare function generateAskDrivenAsset(blueprint: BlueprintData | null, question: string, opts: AskDrivenAssetOpts, provider: LlmProvider): Promise<GenerationResult>;
129
+
130
+ export { type ArchitectureWalkthroughOpts, type AskDrivenAssetFormat, type AskDrivenAssetOpts, type BlueprintData, type BlueprintEdge, type BlueprintFile, type ChangesSinceOpts, type GenerationResult, type LlmMessage, type LlmProvider, type LlmRequest, type LlmResponse, type OnboardingDocOpts, type PrismData, type PrismInsight, type PrismRecommendation, type PrismSession, type RefactoringReportOpts, type ReleaseNotesOpts, generateArchitectureWalkthrough, generateAskDrivenAsset, generateChangesSince, generateOnboardingDoc, generateRefactoringReport, generateReleaseNotes, readBlueprintData, readPrismDirectory };
package/dist/index.d.ts CHANGED
@@ -79,6 +79,12 @@ interface RefactoringReportOpts {
79
79
  length?: "short" | "medium" | "long";
80
80
  format?: "markdown" | "plain";
81
81
  }
82
+ type AskDrivenAssetFormat = "markdown" | "blog" | "social" | "email" | "slack" | "slide";
83
+ interface AskDrivenAssetOpts {
84
+ format?: AskDrivenAssetFormat;
85
+ tone?: "professional" | "casual" | "technical" | "executive";
86
+ length?: "short" | "medium" | "long";
87
+ }
82
88
  interface GenerationResult {
83
89
  text: string;
84
90
  metadata: {
@@ -119,4 +125,6 @@ declare function generateRefactoringReport(blueprint: BlueprintData | null, opts
119
125
  declare function readPrismDirectory(prismPath: string): Promise<PrismData>;
120
126
  declare function readBlueprintData(targetPath: string): Promise<BlueprintData | null>;
121
127
 
122
- export { type ArchitectureWalkthroughOpts, type BlueprintData, type BlueprintEdge, type BlueprintFile, type ChangesSinceOpts, type GenerationResult, type LlmMessage, type LlmProvider, type LlmRequest, type LlmResponse, type OnboardingDocOpts, type PrismData, type PrismInsight, type PrismRecommendation, type PrismSession, type RefactoringReportOpts, type ReleaseNotesOpts, generateArchitectureWalkthrough, generateChangesSince, generateOnboardingDoc, generateRefactoringReport, generateReleaseNotes, readBlueprintData, readPrismDirectory };
128
+ declare function generateAskDrivenAsset(blueprint: BlueprintData | null, question: string, opts: AskDrivenAssetOpts, provider: LlmProvider): Promise<GenerationResult>;
129
+
130
+ export { type ArchitectureWalkthroughOpts, type AskDrivenAssetFormat, type AskDrivenAssetOpts, type BlueprintData, type BlueprintEdge, type BlueprintFile, type ChangesSinceOpts, type GenerationResult, type LlmMessage, type LlmProvider, type LlmRequest, type LlmResponse, type OnboardingDocOpts, type PrismData, type PrismInsight, type PrismRecommendation, type PrismSession, type RefactoringReportOpts, type ReleaseNotesOpts, generateArchitectureWalkthrough, generateAskDrivenAsset, generateChangesSince, generateOnboardingDoc, generateRefactoringReport, generateReleaseNotes, readBlueprintData, readPrismDirectory };
package/dist/index.mjs CHANGED
@@ -376,4 +376,86 @@ async function readBlueprintData(targetPath) {
376
376
  }
377
377
  }
378
378
 
379
- export { generateArchitectureWalkthrough, generateChangesSince, generateOnboardingDoc, generateRefactoringReport, generateReleaseNotes, readBlueprintData, readPrismDirectory };
379
+ // src/generators/askDrivenAsset.ts
380
+ var NO_DATA_MSG5 = "No Blueprint data available. Run prism scan first.";
381
+ var FORMAT_GUIDES = {
382
+ markdown: { name: "Markdown document", structure: "Use headers, bullet lists, and code blocks where appropriate.", maxTokens: 3072 },
383
+ blog: { name: "blog post", structure: "Write with an engaging intro, clear sections, a conclusion, and a call-to-action.", maxTokens: 3072 },
384
+ social: { name: "social media post", structure: "Write concise, punchy content suitable for Twitter/LinkedIn. Max 280 characters for Twitter mode.", maxTokens: 512 },
385
+ email: { name: "email", structure: "Use Subject:, greeting, body paragraphs, and a sign-off.", maxTokens: 1024 },
386
+ slack: { name: "Slack message", structure: "Keep it conversational, use *bold* for emphasis, bullet points for lists. Max 3 paragraphs.", maxTokens: 512 },
387
+ slide: { name: "presentation outline", structure: "Structure as slide titles with 3-5 bullet points each. Include a title slide and summary slide.", maxTokens: 2048 }
388
+ };
389
+ function buildSystemPrompt6(format, tone) {
390
+ const toneMap = {
391
+ professional: "You are a professional technical writer and developer advocate.",
392
+ casual: "You are a friendly engineering blogger who writes in an approachable, conversational style.",
393
+ technical: "You are a senior software engineer writing precise, implementation-focused content.",
394
+ executive: "You are a VP of Engineering writing high-level, business-value-focused content for leadership."
395
+ };
396
+ const guide = FORMAT_GUIDES[format];
397
+ 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.`;
398
+ }
399
+ function buildUserPrompt6(blueprint, question, opts) {
400
+ const format = opts.format ?? "markdown";
401
+ const tone = opts.tone ?? "professional";
402
+ const length = opts.length ?? "medium";
403
+ const guide = FORMAT_GUIDES[format];
404
+ const lengthGuide = {
405
+ short: "Keep it concise \u2014 1-2 paragraphs or equivalent.",
406
+ medium: "Medium length \u2014 3-5 paragraphs or equivalent.",
407
+ long: "Detailed and comprehensive \u2014 cover the topic thoroughly."
408
+ }[length];
409
+ const topFiles = blueprint.files.slice().sort((a, b) => (b.importedByCount ?? 0) - (a.importedByCount ?? 0)).slice(0, 12);
410
+ const lines = [
411
+ `## User's Question`,
412
+ question,
413
+ ``,
414
+ `## Output Requirements`,
415
+ `- Format: ${guide.name}`,
416
+ `- Tone: ${tone}`,
417
+ `- Length: ${length} (${lengthGuide})`,
418
+ ``,
419
+ `## Codebase Architecture Context`,
420
+ `Target: ${blueprint.targetPath}`,
421
+ `Total files: ${blueprint.stats.totalFiles} | Dependency edges: ${blueprint.stats.runtimeEdges}`,
422
+ `Categories: app=${blueprint.categories.app ?? 0}, components=${blueprint.categories.component ?? 0}, lib=${blueprint.categories.lib ?? 0}, hooks=${blueprint.categories.hook ?? 0}`,
423
+ ``,
424
+ `Key files (by usage):`,
425
+ ...topFiles.map((f) => `- ${f.path} [${f.category ?? "?"}] \u2014 imported by ${f.importedByCount ?? 0} files, ${f.lineCount ?? "?"} lines`)
426
+ ];
427
+ if (blueprint.edges.length > 0) {
428
+ const edgeSample = blueprint.edges.slice(0, 15);
429
+ lines.push(``, `Dependency edges (sample):`, ...edgeSample.map((e) => `- ${e.from} \u2192 ${e.to}`));
430
+ }
431
+ lines.push(``, `Answer the user's question using the architecture context above. Generate the ${guide.name} now.`);
432
+ return lines.join("\n");
433
+ }
434
+ async function generateAskDrivenAsset(blueprint, question, opts, provider) {
435
+ if (!blueprint) {
436
+ return {
437
+ text: NO_DATA_MSG5,
438
+ metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), usedTokens: 0, generator: "forgesmith" }
439
+ };
440
+ }
441
+ if (!question || !question.trim()) {
442
+ return {
443
+ text: "No question provided. Please ask something about your codebase.",
444
+ metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), usedTokens: 0, generator: "forgesmith" }
445
+ };
446
+ }
447
+ const format = opts.format ?? "markdown";
448
+ const tone = opts.tone ?? "professional";
449
+ const maxTokens = FORMAT_GUIDES[format].maxTokens;
450
+ const response = await provider.complete({
451
+ systemPrompt: buildSystemPrompt6(format, tone),
452
+ messages: [{ role: "user", content: buildUserPrompt6(blueprint, question, opts) }],
453
+ maxTokens
454
+ });
455
+ return {
456
+ text: response.content,
457
+ metadata: { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), usedTokens: response.usedTokens, generator: "forgesmith" }
458
+ };
459
+ }
460
+
461
+ export { generateArchitectureWalkthrough, generateAskDrivenAsset, generateChangesSince, generateOnboardingDoc, generateRefactoringReport, generateReleaseNotes, readBlueprintData, readPrismDirectory };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgesmith",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
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",