prompt_manager 0.5.8 → 1.0.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.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/README.md +206 -516
  4. data/Rakefile +0 -8
  5. data/docs/api/configuration.md +31 -327
  6. data/docs/api/constants.md +60 -0
  7. data/docs/api/index.md +14 -0
  8. data/docs/api/metadata.md +99 -0
  9. data/docs/api/parsed.md +98 -0
  10. data/docs/api/pm-module.md +131 -0
  11. data/docs/api/render-context.md +51 -0
  12. data/docs/architecture/design-decisions.md +70 -0
  13. data/docs/architecture/index.md +6 -0
  14. data/docs/architecture/processing-pipeline.md +112 -0
  15. data/docs/assets/css/custom.css +1 -0
  16. data/docs/assets/images/prompt_manager.gif +0 -0
  17. data/docs/assets/images/prompt_manager.mp4 +0 -0
  18. data/docs/examples/ai-agent-prompts.md +173 -0
  19. data/docs/examples/code-review-prompt.md +107 -0
  20. data/docs/examples/index.md +7 -0
  21. data/docs/examples/multi-file-composition.md +123 -0
  22. data/docs/getting-started/configuration.md +106 -0
  23. data/docs/getting-started/index.md +7 -0
  24. data/docs/getting-started/installation.md +10 -73
  25. data/docs/getting-started/quick-start.md +50 -225
  26. data/docs/guides/comment-stripping.md +64 -0
  27. data/docs/guides/custom-directives.md +115 -0
  28. data/docs/guides/erb-rendering.md +102 -0
  29. data/docs/guides/includes.md +146 -0
  30. data/docs/guides/index.md +11 -0
  31. data/docs/guides/parameters.md +96 -0
  32. data/docs/guides/parsing.md +127 -0
  33. data/docs/guides/shell-expansion.md +108 -0
  34. data/docs/index.md +54 -214
  35. data/lib/pm/configuration.rb +17 -0
  36. data/lib/pm/directives.rb +61 -0
  37. data/lib/pm/metadata.rb +17 -0
  38. data/lib/pm/parsed.rb +59 -0
  39. data/lib/pm/shell.rb +57 -0
  40. data/lib/pm/version.rb +5 -0
  41. data/lib/pm.rb +121 -0
  42. data/lib/prompt_manager.rb +2 -27
  43. data/mkdocs.yml +101 -66
  44. metadata +42 -101
  45. data/docs/.keep +0 -0
  46. data/docs/advanced/custom-keywords.md +0 -421
  47. data/docs/advanced/dynamic-directives.md +0 -535
  48. data/docs/advanced/performance.md +0 -612
  49. data/docs/advanced/search-integration.md +0 -635
  50. data/docs/api/directive-processor.md +0 -431
  51. data/docs/api/prompt-class.md +0 -354
  52. data/docs/api/storage-adapters.md +0 -462
  53. data/docs/assets/favicon.ico +0 -1
  54. data/docs/assets/logo.svg +0 -24
  55. data/docs/core-features/comments.md +0 -48
  56. data/docs/core-features/directive-processing.md +0 -38
  57. data/docs/core-features/erb-integration.md +0 -68
  58. data/docs/core-features/error-handling.md +0 -197
  59. data/docs/core-features/parameter-history.md +0 -76
  60. data/docs/core-features/parameterized-prompts.md +0 -500
  61. data/docs/core-features/shell-integration.md +0 -79
  62. data/docs/development/architecture.md +0 -544
  63. data/docs/development/contributing.md +0 -425
  64. data/docs/development/roadmap.md +0 -234
  65. data/docs/development/testing.md +0 -822
  66. data/docs/examples/advanced.md +0 -523
  67. data/docs/examples/basic.md +0 -688
  68. data/docs/examples/real-world.md +0 -776
  69. data/docs/examples.md +0 -337
  70. data/docs/getting-started/basic-concepts.md +0 -318
  71. data/docs/migration/v0.9.0.md +0 -459
  72. data/docs/migration/v1.0.0.md +0 -591
  73. data/docs/storage/activerecord-adapter.md +0 -348
  74. data/docs/storage/custom-adapters.md +0 -176
  75. data/docs/storage/filesystem-adapter.md +0 -236
  76. data/docs/storage/overview.md +0 -427
  77. data/examples/advanced_integrations.rb +0 -52
  78. data/examples/directives.rb +0 -102
  79. data/examples/prompts_dir/advanced_demo.txt +0 -79
  80. data/examples/prompts_dir/directive_example.json +0 -1
  81. data/examples/prompts_dir/directive_example.txt +0 -8
  82. data/examples/prompts_dir/todo.json +0 -1
  83. data/examples/prompts_dir/todo.txt +0 -7
  84. data/examples/prompts_dir/toy/8-ball.txt +0 -4
  85. data/examples/rgfzf +0 -44
  86. data/examples/simple.rb +0 -160
  87. data/examples/using_search_proc.rb +0 -68
  88. data/improvement_plan.md +0 -996
  89. data/lib/prompt_manager/directive_processor.rb +0 -47
  90. data/lib/prompt_manager/prompt.rb +0 -195
  91. data/lib/prompt_manager/storage/active_record_adapter.rb +0 -157
  92. data/lib/prompt_manager/storage/file_system_adapter.rb +0 -339
  93. data/lib/prompt_manager/storage.rb +0 -34
  94. data/lib/prompt_manager/version.rb +0 -5
  95. data/prompt_manager_logo.png +0 -0
@@ -0,0 +1,131 @@
1
+ # PM Module
2
+
3
+ The top-level `PM` module is the primary interface. All public methods are class methods on `PM`.
4
+
5
+ ## Parsing
6
+
7
+ ### PM.parse(source) → Parsed
8
+
9
+ Parse a file or string and return a `PM::Parsed` object.
10
+
11
+ **Parameters:**
12
+
13
+ | Name | Type | Description |
14
+ |------|------|-------------|
15
+ | `source` | String, Symbol, Pathname | File path (`.md` extension or Pathname), Symbol, single word, or raw string |
16
+
17
+ **Returns:** `PM::Parsed` (Struct with `metadata` and `content`)
18
+
19
+ **Behavior:**
20
+
21
+ - If `source` is a Symbol or a single word (letters, digits, underscores only), `.md` is appended and it is treated as a file basename
22
+ - If `source` is a Pathname, responds to `to_path`, or is a String ending in `.md`, it is treated as a file path
23
+ - File paths are resolved relative to `PM.config.prompts_dir` (unless absolute)
24
+ - File parsing adds `directory`, `name`, `created_at`, `modified_at` to metadata
25
+ - Processing pipeline: strip comments → extract YAML → shell expansion
26
+
27
+ ```ruby
28
+ # File
29
+ parsed = PM.parse('review.md')
30
+
31
+ # Symbol or single word (basename — .md appended)
32
+ parsed = PM.parse(:review) #=> parses review.md
33
+ parsed = PM.parse('review') #=> parses review.md
34
+
35
+ # String
36
+ parsed = PM.parse("---\ntitle: Hello\n---\nContent")
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Configuration
42
+
43
+ ### PM.config → Configuration
44
+
45
+ Returns the singleton `PM::Configuration` instance.
46
+
47
+ ```ruby
48
+ PM.config.prompts_dir #=> ''
49
+ PM.config.shell #=> true
50
+ ```
51
+
52
+ ### PM.configure { |config| } → Configuration
53
+
54
+ Yields the configuration instance for block-style configuration.
55
+
56
+ ```ruby
57
+ PM.configure do |config|
58
+ config.prompts_dir = '~/.prompts'
59
+ config.shell = true
60
+ config.erb = true
61
+ end
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Utilities
67
+
68
+ ### PM.strip_comments(string) → String
69
+
70
+ Remove all HTML comments (`<!-- ... -->`) from the string, including multiline.
71
+
72
+ ```ruby
73
+ PM.strip_comments("Hello <!-- removed --> World")
74
+ #=> "Hello World"
75
+ ```
76
+
77
+ ### PM.expand_shell(string) → String
78
+
79
+ Expand shell references in the string.
80
+
81
+ - `$VAR` and `${VAR}` → environment variable value (empty string if unset)
82
+ - `$(command)` → command stdout (trailing newline stripped)
83
+
84
+ Only UPPERCASE variable names are expanded.
85
+
86
+ ```ruby
87
+ PM.expand_shell("User: $USER, Date: $(date +%Y-%m-%d)")
88
+ #=> "User: dewayne, Date: 2025-01-15"
89
+ ```
90
+
91
+ **Raises:** `RuntimeError` if a command exits with non-zero status.
92
+
93
+ ---
94
+
95
+ ## Directives
96
+
97
+ ### PM.register(*names, &block) → nil
98
+
99
+ Register one or more named directives available in ERB templates. Multiple names register the same block under each name (aliases).
100
+
101
+ **Parameters:**
102
+
103
+ | Name | Type | Description |
104
+ |------|------|-------------|
105
+ | `*names` | Symbols, Strings | One or more directive names |
106
+ | `block` | Proc | Receives `RenderContext` as first arg, then user args |
107
+
108
+ **Raises:** `RuntimeError` if any name is already registered.
109
+
110
+ ```ruby
111
+ PM.register(:env) { |_ctx, key| ENV.fetch(key, '') }
112
+ PM.register(:webpage, :website, :web) { |_ctx, url| fetch_page(url) }
113
+ ```
114
+
115
+ ### PM.directives → Hash
116
+
117
+ Returns the current directive registry.
118
+
119
+ ```ruby
120
+ PM.directives
121
+ #=> { include: #<Proc>, env: #<Proc> }
122
+ ```
123
+
124
+ ### PM.reset_directives! → nil
125
+
126
+ Remove all custom directives and re-register only the built-in `include` directive.
127
+
128
+ ```ruby
129
+ PM.reset_directives!
130
+ PM.directives.keys #=> [:include]
131
+ ```
@@ -0,0 +1,51 @@
1
+ # PM::RenderContext
2
+
3
+ Context object passed as the first argument to every directive block during ERB rendering.
4
+
5
+ ## Source
6
+
7
+ `lib/pm/parsed.rb`
8
+
9
+ ## Structure
10
+
11
+ ```ruby
12
+ PM::RenderContext = Struct.new(:directory, :params, :included, :depth, :metadata)
13
+ ```
14
+
15
+ ## Fields
16
+
17
+ | Field | Type | Description |
18
+ |-------|------|-------------|
19
+ | `directory` | String | Absolute path to the directory of the file being rendered |
20
+ | `params` | Hash | Merged parameter values (defaults + overrides) |
21
+ | `included` | Set | File paths already in the include chain (for circular detection) |
22
+ | `depth` | Integer | Include nesting depth (0 for top-level file) |
23
+ | `metadata` | PM::Metadata | Metadata of the file being rendered |
24
+
25
+ ## Usage in Directives
26
+
27
+ Every directive block receives a RenderContext as its first argument:
28
+
29
+ ```ruby
30
+ PM.register(:current_file) { |ctx| ctx.metadata.name || 'unknown' }
31
+
32
+ PM.register(:nesting) { |ctx| ctx.depth.to_s }
33
+
34
+ PM.register(:sibling) do |ctx, filename|
35
+ File.read(File.join(ctx.directory, filename))
36
+ end
37
+
38
+ PM.register(:param_dump) do |ctx|
39
+ ctx.params.map { |k, v| "#{k}: #{v}" }.join(', ')
40
+ end
41
+ ```
42
+
43
+ ## Context During Includes
44
+
45
+ When a file is included, the RenderContext reflects the included file's state:
46
+
47
+ - `directory` is the included file's directory
48
+ - `metadata` is the included file's metadata
49
+ - `depth` increments by 1 for each nesting level
50
+ - `included` contains all ancestor file paths
51
+ - `params` carries the parent's merged parameter values
@@ -0,0 +1,70 @@
1
+ # Design Decisions
2
+
3
+ Key architectural choices and their rationale.
4
+
5
+ ## OpenStruct for Metadata
6
+
7
+ `PM::Metadata` extends `OpenStruct`, providing dynamic dot-notation access to any YAML key without predeclaring attributes.
8
+
9
+ **Why:** Prompt metadata is inherently schemaless. Different prompts carry different keys (`provider`, `model`, `temperature`, `parameters`, custom keys). OpenStruct allows any key without configuration.
10
+
11
+ **Trade-off:** Slightly slower than a fixed Struct for attribute access, but metadata objects are small and accessed infrequently relative to the parsing work.
12
+
13
+ ## ERB for Templates
14
+
15
+ ERB was chosen as the template engine rather than Mustache, Liquid, or a custom syntax.
16
+
17
+ **Why:**
18
+
19
+ - Ships with Ruby's standard library -- no additional dependencies
20
+ - Familiar to every Ruby developer
21
+ - Full Ruby expressions available (not just variable substitution)
22
+ - Custom directives integrate naturally as method calls
23
+
24
+ **Trade-off:** ERB can execute arbitrary Ruby, which is more powerful than needed for simple variable substitution. PM mitigates this by encouraging the use of parameters and directives rather than inline Ruby logic.
25
+
26
+ ## Parse-Time vs Render-Time Split
27
+
28
+ Shell expansion runs at parse time. ERB rendering runs on demand at `to_s` time.
29
+
30
+ **Why:**
31
+
32
+ - Shell references (`$USER`, `$(date)`) represent the environment at the time the prompt is loaded. They should reflect current state when the file is read.
33
+ - ERB parameters represent per-invocation values that may change between calls. Deferring rendering allows the same parsed prompt to be rendered multiple times with different values.
34
+
35
+ ## Separate Shell and ERB Flags
36
+
37
+ Both `shell` and `erb` can be independently enabled or disabled per file.
38
+
39
+ **Why:** Some prompts are documentation templates that should show `$VAR` syntax literally. Others need shell expansion but contain ERB syntax meant for a different renderer. Independent flags give full control.
40
+
41
+ ## Include via ERB Directive
42
+
43
+ File inclusion uses `<%= include 'path.md' %>` rather than a preprocessor directive or custom syntax.
44
+
45
+ **Why:**
46
+
47
+ - Consistent with the ERB rendering model -- includes are just another method call
48
+ - Parameters flow naturally from parent to child
49
+ - Custom directives use the same mechanism, so the system is uniform
50
+ - No new syntax to learn
51
+
52
+ **Trade-off:** Includes only work when ERB is enabled. Setting `erb: false` disables includes.
53
+
54
+ ## Circular Include Detection
55
+
56
+ PM tracks included file paths in a Set passed through the render chain.
57
+
58
+ **Why:** Circular includes would cause infinite recursion. The Set provides O(1) lookup and is threaded through the `RenderContext` without global state.
59
+
60
+ ## Configuration as Singleton
61
+
62
+ `PM.config` returns a single `PM::Configuration` instance. Per-file metadata overrides it.
63
+
64
+ **Why:** A global default makes sense for project-wide settings like `prompts_dir`. The override mechanism means individual files always have the final say, avoiding surprises.
65
+
66
+ ## UPPERCASE-Only Shell Variables
67
+
68
+ Only `$UPPER_CASE` variable names are expanded by shell expansion. `$lowercase` is left as-is.
69
+
70
+ **Why:** This avoids conflicts with ERB and other syntaxes that might use `$` followed by lowercase characters. Environment variables are conventionally uppercase, so this covers the practical use case without false matches.
@@ -0,0 +1,6 @@
1
+ # Architecture
2
+
3
+ Technical details about PM's internal design.
4
+
5
+ - [Processing Pipeline](processing-pipeline.md) -- The four-stage pipeline that transforms raw prompts
6
+ - [Design Decisions](design-decisions.md) -- Rationale behind key architectural choices
@@ -0,0 +1,112 @@
1
+ # Processing Pipeline
2
+
3
+ PM processes prompts through a four-stage pipeline. The first three stages run at parse time; the fourth runs on demand when `to_s` is called.
4
+
5
+ ## Overview
6
+
7
+ ```mermaid
8
+ graph TD
9
+ A["Raw Input<br/>(file, symbol, word, or string)"] --> B["1. Strip HTML Comments"]
10
+ B --> C["2. Extract YAML Metadata"]
11
+ C --> D["3. Shell Expansion"]
12
+ D --> E["PM::Parsed<br/>(metadata + content)"]
13
+ E -->|"to_s(values)"| F["4. ERB Rendering"]
14
+ F --> G["Rendered String"]
15
+
16
+ style A fill:#00695c,color:#fff
17
+ style E fill:#00695c,color:#fff
18
+ style G fill:#f57f17,color:#000
19
+ ```
20
+
21
+ ## Stage 1: Strip HTML Comments
22
+
23
+ **When:** Parse time
24
+ **Method:** `PM.strip_comments`
25
+
26
+ All `<!-- ... -->` comments are removed, including multiline spans. This runs first so comments don't interfere with metadata extraction.
27
+
28
+ ```
29
+ Input: "<!-- draft -->\n---\ntitle: Test\n---\nContent <!-- note -->"
30
+ Output: "\n---\ntitle: Test\n---\nContent "
31
+ ```
32
+
33
+ ## Stage 2: Extract YAML Metadata
34
+
35
+ **When:** Parse time
36
+ **Regex:** `PM::METADATA_REGEXP`
37
+
38
+ The `---`-delimited front-matter is parsed with `YAML.safe_load` into a Hash, then wrapped in a `PM::Metadata` object. Global defaults for `shell` and `erb` are applied if the file doesn't specify them.
39
+
40
+ For file sources, `directory`, `name`, `created_at`, and `modified_at` are added from file stats.
41
+
42
+ ```
43
+ Input: "---\ntitle: Test\nshell: true\n---\nContent"
44
+ Output: metadata = PM::Metadata(title: "Test", shell: true, erb: true)
45
+ content = "Content"
46
+ ```
47
+
48
+ ## Stage 3: Shell Expansion
49
+
50
+ **When:** Parse time (if `shell: true`)
51
+ **Method:** `PM.expand_shell`
52
+
53
+ Two sub-stages run in order:
54
+
55
+ ### 3a. Command Substitution
56
+
57
+ `$(command)` sequences are found (with proper nesting of parentheses), executed, and replaced with stdout.
58
+
59
+ ### 3b. Environment Variables
60
+
61
+ `$VAR` and `${VAR}` patterns (UPPERCASE only) are replaced with the corresponding environment variable value. Missing variables become empty strings.
62
+
63
+ ```
64
+ Input: "User: $USER\nDate: $(date +%Y-%m-%d)"
65
+ Output: "User: dewayne\nDate: 2025-01-15"
66
+ ```
67
+
68
+ ## Stage 4: ERB Rendering
69
+
70
+ **When:** On demand (`to_s` call, if `erb: true`)
71
+ **Method:** `PM::Parsed#to_s`
72
+
73
+ The content is evaluated as an ERB template. Parameter values (defaults merged with `to_s` arguments) and registered directives are available as local methods in the template.
74
+
75
+ This stage also handles the `include` directive, which recursively parses and renders included files through the full pipeline.
76
+
77
+ ```
78
+ Input: "Hello, <%= name %>!" (with params: {name: "Alice"})
79
+ Output: "Hello, Alice!"
80
+ ```
81
+
82
+ ## Conditional Stages
83
+
84
+ Stages 3 and 4 can be independently disabled:
85
+
86
+ | Setting | Effect |
87
+ |---------|--------|
88
+ | `shell: false` | Stage 3 skipped; `$VAR` and `$(cmd)` preserved |
89
+ | `erb: false` | Stage 4 skipped; `<%= ... %>` preserved |
90
+
91
+ Settings can be per-file (YAML metadata) or global (`PM.configure`). Per-file always wins.
92
+
93
+ ## Data Flow
94
+
95
+ ```mermaid
96
+ graph LR
97
+ subgraph "Parse Time"
98
+ A[Raw Text] --> B[No Comments]
99
+ B --> C[Metadata + Content]
100
+ C --> D[Shell-Expanded Content]
101
+ end
102
+
103
+ subgraph "Render Time"
104
+ D --> E[ERB Template]
105
+ E --> F[Final Output]
106
+ end
107
+
108
+ style A fill:#00695c,color:#fff
109
+ style F fill:#f57f17,color:#000
110
+ ```
111
+
112
+ The `PM::Parsed` object returned by `PM.parse` holds the result of stages 1-3. Calling `to_s` triggers stage 4 and returns the final string.
@@ -0,0 +1 @@
1
+ /* PM (PromptManager) custom styles */
@@ -0,0 +1,173 @@
1
+ # AI Agent Prompts
2
+
3
+ Patterns for building and managing prompt libraries for AI agents.
4
+
5
+ ## Prompt Library Structure
6
+
7
+ Organize prompts by agent role and task:
8
+
9
+ ```
10
+ ~/.prompts/
11
+ agents/
12
+ code_assistant/
13
+ system.md
14
+ review.md
15
+ refactor.md
16
+ explain.md
17
+ writer/
18
+ system.md
19
+ draft.md
20
+ edit.md
21
+ analyst/
22
+ system.md
23
+ summarize.md
24
+ compare.md
25
+ common/
26
+ output_json.md
27
+ output_markdown.md
28
+ safety.md
29
+ ```
30
+
31
+ ## Configuration
32
+
33
+ ```ruby
34
+ PM.configure { |c| c.prompts_dir = '~/.prompts' }
35
+ ```
36
+
37
+ ## System Prompt Pattern
38
+
39
+ `agents/code_assistant/system.md`:
40
+
41
+ ```markdown
42
+ ---
43
+ title: Code Assistant System Prompt
44
+ provider: anthropic
45
+ model: claude-sonnet-4-20250514
46
+ role: system
47
+ ---
48
+ You are an expert software engineer. You help with code review,
49
+ refactoring, and explanation.
50
+
51
+ Current environment:
52
+ - OS: $(uname -s)
53
+ - User: $USER
54
+ - Working directory: $(pwd)
55
+
56
+ <%= include '../common/safety.md' %>
57
+ ```
58
+
59
+ ## Task Prompts with Parameters
60
+
61
+ `agents/code_assistant/review.md`:
62
+
63
+ ```markdown
64
+ ---
65
+ title: Code Review Task
66
+ role: user
67
+ parameters:
68
+ code: null
69
+ language: null
70
+ focus: general
71
+ ---
72
+ <%= include '../common/output_markdown.md' %>
73
+
74
+ Review the following <%= language %> code with a focus on <%= focus %>:
75
+
76
+ ```<%= language %>
77
+ <%= code %>
78
+ ```
79
+ ```
80
+
81
+ ## Custom Directives for Agent Integration
82
+
83
+ Register directives that fetch context dynamically:
84
+
85
+ ```ruby
86
+ PM.register(:git_diff) do |_ctx|
87
+ `git diff --cached`.chomp
88
+ end
89
+
90
+ PM.register(:recent_commits) do |_ctx, count|
91
+ `git log --oneline -#{count}`.chomp
92
+ end
93
+
94
+ PM.register(:file_tree) do |ctx, path|
95
+ dir = path || ctx.directory
96
+ `find #{dir} -type f -name '*.rb' | head -20`.chomp
97
+ end
98
+ ```
99
+
100
+ Use them in prompts:
101
+
102
+ ```markdown
103
+ ---
104
+ title: PR Review
105
+ parameters:
106
+ focus: correctness
107
+ ---
108
+ Review this PR with a focus on <%= focus %>.
109
+
110
+ Changes:
111
+ <%= git_diff %>
112
+
113
+ Recent commit context:
114
+ <%= recent_commits 5 %>
115
+
116
+ Project structure:
117
+ <%= file_tree nil %>
118
+ ```
119
+
120
+ ## Building a Prompt Runner
121
+
122
+ ```ruby
123
+ require 'pm'
124
+
125
+ PM.configure { |c| c.prompts_dir = '~/.prompts' }
126
+
127
+ # Register project-specific directives
128
+ PM.register(:git_diff) { |_ctx| `git diff --cached`.chomp }
129
+
130
+ # Load and render
131
+ system_prompt = PM.parse('agents/code_assistant/system.md')
132
+ task_prompt = PM.parse('agents/code_assistant/review.md')
133
+
134
+ messages = [
135
+ { role: 'system', content: system_prompt.to_s },
136
+ { role: 'user', content: task_prompt.to_s(
137
+ 'code' => File.read(ARGV[0]),
138
+ 'language' => 'ruby',
139
+ 'focus' => 'security'
140
+ )}
141
+ ]
142
+
143
+ # Use metadata for model configuration
144
+ model = task_prompt.metadata.model || system_prompt.metadata.model
145
+ ```
146
+
147
+ ## Prompt Versioning
148
+
149
+ Keep prompts in version control alongside your code. The metadata tracks useful context:
150
+
151
+ ```ruby
152
+ parsed = PM.parse('agents/code_assistant/review.md')
153
+
154
+ puts parsed.metadata.title #=> "Code Review Task"
155
+ puts parsed.metadata.modified_at #=> 2025-01-15 10:30:00 -0500
156
+ ```
157
+
158
+ Use `modified_at` for cache invalidation or audit logging.
159
+
160
+ ## Parameter Validation Pattern
161
+
162
+ Check required parameters before rendering:
163
+
164
+ ```ruby
165
+ parsed = PM.parse('agents/code_assistant/review.md')
166
+
167
+ required = parsed.metadata.parameters
168
+ .select { |_k, v| v.nil? }
169
+ .keys
170
+
171
+ puts "Required parameters: #{required.join(', ')}"
172
+ #=> "Required parameters: code, language"
173
+ ```
@@ -0,0 +1,107 @@
1
+ # Code Review Prompt
2
+
3
+ A complete example of a parameterized code review prompt.
4
+
5
+ ## The Prompt File
6
+
7
+ `prompts/code_review.md`:
8
+
9
+ ```markdown
10
+ ---
11
+ title: Code Review
12
+ provider: openai
13
+ model: gpt-4
14
+ temperature: 0.3
15
+ parameters:
16
+ language: ruby
17
+ code: null
18
+ style_guide: ~/guides/default.md
19
+ ---
20
+ Review the following <%= language %> code for:
21
+ - Correctness
22
+ - Performance
23
+ - Readability
24
+ - Security vulnerabilities
25
+
26
+ Apply the coding standards from this style guide:
27
+ <%= style_guide %>
28
+
29
+ Code to review:
30
+
31
+ <%= code %>
32
+ ```
33
+
34
+ ## Parsing
35
+
36
+ ```ruby
37
+ require 'pm'
38
+
39
+ PM.configure { |c| c.prompts_dir = './prompts' }
40
+
41
+ parsed = PM.parse('code_review.md')
42
+ ```
43
+
44
+ ## Accessing Metadata
45
+
46
+ ```ruby
47
+ parsed.metadata.title #=> "Code Review"
48
+ parsed.metadata.provider #=> "openai"
49
+ parsed.metadata.model #=> "gpt-4"
50
+ parsed.metadata.temperature #=> 0.3
51
+
52
+ parsed.metadata.parameters
53
+ #=> {"language" => "ruby", "code" => nil, "style_guide" => "~/guides/default.md"}
54
+ ```
55
+
56
+ Use metadata to configure your LLM client:
57
+
58
+ ```ruby
59
+ client = OpenAI::Client.new
60
+ response = client.chat(
61
+ parameters: {
62
+ model: parsed.metadata.model,
63
+ temperature: parsed.metadata.temperature,
64
+ messages: [{ role: 'user', content: prompt }]
65
+ }
66
+ )
67
+ ```
68
+
69
+ ## Rendering
70
+
71
+ ```ruby
72
+ # Minimal -- supply only the required parameter
73
+ prompt = parsed.to_s('code' => File.read('app.rb'))
74
+
75
+ # Override defaults
76
+ prompt = parsed.to_s(
77
+ 'code' => File.read('main.py'),
78
+ 'language' => 'python',
79
+ 'style_guide' => File.read('python_style.md')
80
+ )
81
+ ```
82
+
83
+ ## Shell Expansion in Action
84
+
85
+ The `style_guide` parameter defaults to `~/guides/default.md`. If shell expansion is enabled, the `~` is not expanded (it's not a shell variable). But you could use shell references elsewhere in the prompt:
86
+
87
+ ```markdown
88
+ ---
89
+ title: Code Review with Context
90
+ parameters:
91
+ code: null
92
+ ---
93
+ Repository: $(basename $(git rev-parse --toplevel))
94
+ Branch: $(git rev-parse --abbrev-ref HEAD)
95
+ Reviewer: $USER
96
+
97
+ Review this code:
98
+ <%= code %>
99
+ ```
100
+
101
+ After parsing, the shell references are already resolved:
102
+
103
+ ```ruby
104
+ parsed = PM.parse('code_review_context.md')
105
+ parsed.content
106
+ #=> "Repository: my-project\nBranch: main\nReviewer: dewayne\n\nReview this code:\n<%= code %>\n"
107
+ ```
@@ -0,0 +1,7 @@
1
+ # Examples
2
+
3
+ Practical examples showing PM in real-world scenarios.
4
+
5
+ - [Code Review Prompt](code-review-prompt.md) -- A complete code review prompt with parameters and shell context
6
+ - [Multi-File Composition](multi-file-composition.md) -- Composing prompts from shared components with includes
7
+ - [AI Agent Prompts](ai-agent-prompts.md) -- Patterns for building AI agent prompt libraries