From 2fd42a7c1e7992fca70767347b9694bbb3cf1fa9 Mon Sep 17 00:00:00 2001 From: Wilbur Suero Date: Sun, 24 Aug 2025 01:25:40 -0400 Subject: [PATCH 1/3] feat: Add prompt version metadata --- app/prompts/test/prompt_with_metadata.en.erb | 6 +++ lib/promptly.rb | 39 +++++++++++++++++++- spec/prompt_helper_spec.rb | 8 ++++ spec/support/prompt_helper.rb | 2 +- 4 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 app/prompts/test/prompt_with_metadata.en.erb diff --git a/app/prompts/test/prompt_with_metadata.en.erb b/app/prompts/test/prompt_with_metadata.en.erb new file mode 100644 index 0000000..c00c4b5 --- /dev/null +++ b/app/prompts/test/prompt_with_metadata.en.erb @@ -0,0 +1,6 @@ +--- +version: 1.0 +author: Test Author +change_notes: Initial version +--- +This is a test prompt with metadata. \ No newline at end of file diff --git a/lib/promptly.rb b/lib/promptly.rb index 20da8c3..538eaa2 100644 --- a/lib/promptly.rb +++ b/lib/promptly.rb @@ -6,10 +6,26 @@ require_relative "promptly/cache" require_relative "promptly/helper" require_relative "promptly/validator" +require "yaml" module Promptly class Error < StandardError; end + class Prompt + attr_reader :content, :version, :author, :change_notes + + def initialize(content:, version: nil, author: nil, change_notes: nil) + @content = content + @version = version + @author = author + @change_notes = change_notes + end + + def to_s + content + end + end + def self.render_template(template, locals: {}, engine: :erb) Renderer.render(template, locals: locals, engine: engine) end @@ -51,8 +67,27 @@ def self.render(identifier, locale: nil, locals: {}, cache: true, ttl: nil) raise Error, "Template not found for '#{identifier}' (locale: #{locale.inspect}) under #{prompts_path}" unless path engine = Locator.engine_for(path) - template = File.read(path) - Renderer.render(template, locals: locals, engine: engine) + file_content = File.read(path) + + # Extract YAML front matter + match = file_content.match(/\A---\n(.*) +---\s*\n/m) + if match + metadata = YAML.safe_load(match[1]) + template = match.post_match + else + metadata = {} + template = file_content + end + + content = Renderer.render(template, locals: locals, engine: engine) + + Prompt.new( + content: content, + version: metadata["version"], + author: metadata["author"], + change_notes: metadata["change_notes"] + ) end def self.default_prompts_path diff --git a/spec/prompt_helper_spec.rb b/spec/prompt_helper_spec.rb index 0b1fa47..eb7b7c7 100644 --- a/spec/prompt_helper_spec.rb +++ b/spec/prompt_helper_spec.rb @@ -5,5 +5,13 @@ it "renders the prompt" do expect("This is a test prompt.").to expect_prompt_render("test/prompt") end + + it "renders the prompt with metadata" do + prompt = Promptly.render("test/prompt_with_metadata") + expect(prompt.to_s).to eq("This is a test prompt with metadata.") + expect(prompt.version).to eq(1.0) + expect(prompt.author).to eq("Test Author") + expect(prompt.change_notes).to eq("Initial version") + end end end diff --git a/spec/support/prompt_helper.rb b/spec/support/prompt_helper.rb index 9871d42..0ba5ff4 100644 --- a/spec/support/prompt_helper.rb +++ b/spec/support/prompt_helper.rb @@ -1,5 +1,5 @@ module PromptHelper def expect_prompt_render(identifier, locals: {}) - RSpec::Matchers::BuiltIn::Include.new(Promptly.render(identifier, locals: locals)) + RSpec::Matchers::BuiltIn::Include.new(Promptly.render(identifier, locals: locals).to_s) end end From 5a44ed9229769c7224c9fcda03b407f1f37e0e41 Mon Sep 17 00:00:00 2001 From: Wilbur Suero Date: Sun, 24 Aug 2025 01:25:49 -0400 Subject: [PATCH 2/3] docs: Add documentation for prompt version metadata --- wiki/Prompt-Version-Metadata.md | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 wiki/Prompt-Version-Metadata.md diff --git a/wiki/Prompt-Version-Metadata.md b/wiki/Prompt-Version-Metadata.md new file mode 100644 index 0000000..d8d837c --- /dev/null +++ b/wiki/Prompt-Version-Metadata.md @@ -0,0 +1,34 @@ +## Prompt Version Metadata + +Promptly allows you to add optional metadata fields to your prompt templates, such as `version`, `author`, and `change_notes`. This metadata can be useful for tracking changes to your prompts and for documentation purposes. + +### Adding Metadata to Prompts + +To add metadata to a prompt, include a YAML front matter block at the beginning of your template file. The front matter block must start and end with `---`. + +**Example:** + +```erb +--- +version: 1.0 +author: John Doe +change_notes: Initial version of the welcome email. +--- +You are a friendly customer success manager writing a personalized welcome email. +... +``` + +### Accessing Metadata + +When you render a prompt, the `Promptly.render` method returns a `Prompt` object. This object contains the rendered content of the prompt, as well as the metadata fields. + +**Example:** + +```ruby +prompt = Promptly.render("user_onboarding/welcome_email") + +puts prompt.content # The rendered content of the email +puts prompt.version # 1.0 +puts prompt.author # John Doe +puts prompt.change_notes # Initial version of the welcome email. +``` From 947aff8dc303cf1bf0a5ae2a32876e4bc22d053e Mon Sep 17 00:00:00 2001 From: Wilbur Suero Date: Sun, 24 Aug 2025 01:28:26 -0400 Subject: [PATCH 3/3] fix: Update render_with_cache_spec to handle Prompt object --- spec/render_with_cache_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/render_with_cache_spec.rb b/spec/render_with_cache_spec.rb index 5217294..072c357 100644 --- a/spec/render_with_cache_spec.rb +++ b/spec/render_with_cache_spec.rb @@ -66,16 +66,16 @@ def keys # First call should read file and cache result result1 = Promptly.render("test", locals: {name: "John"}) - expect(result1).to eq("Hello John!") + expect(result1.content).to eq("Hello John!") expect(cache_store.size).to eq(1) # Second call should use cached result result2 = Promptly.render("test", locals: {name: "John"}) - expect(result2).to eq("Hello John!") + expect(result2.content).to eq("Hello John!") # Verify file was only read once by checking cache was used cache_key = cache_store.keys.first - expect(cache_store.read(cache_key)).to eq("Hello John!") + expect(cache_store.read(cache_key).content).to eq("Hello John!") end it "generates different cache keys for different parameters" do @@ -105,7 +105,7 @@ def keys # This should not use cache result = Promptly.render("test", locals: {name: "John"}, cache: false) - expect(result).to eq("Hello John!") + expect(result.content).to eq("Hello John!") expect(cache_store.size).to eq(0) end @@ -122,8 +122,8 @@ def keys result_en = Promptly.render("greeting", locale: :en, locals: {name: "John"}) result_es = Promptly.render("greeting", locale: :es, locals: {name: "Juan"}) - expect(result_en).to eq("Hello John!") - expect(result_es).to eq("¡Hola Juan!") + expect(result_en.content).to eq("Hello John!") + expect(result_es.content).to eq("¡Hola Juan!") expect(cache_store.size).to eq(2) end end