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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -0
- data/README.md +206 -516
- data/Rakefile +0 -8
- data/docs/api/configuration.md +31 -327
- data/docs/api/constants.md +60 -0
- data/docs/api/index.md +14 -0
- data/docs/api/metadata.md +99 -0
- data/docs/api/parsed.md +98 -0
- data/docs/api/pm-module.md +131 -0
- data/docs/api/render-context.md +51 -0
- data/docs/architecture/design-decisions.md +70 -0
- data/docs/architecture/index.md +6 -0
- data/docs/architecture/processing-pipeline.md +112 -0
- data/docs/assets/css/custom.css +1 -0
- data/docs/assets/images/prompt_manager.gif +0 -0
- data/docs/assets/images/prompt_manager.mp4 +0 -0
- data/docs/examples/ai-agent-prompts.md +173 -0
- data/docs/examples/code-review-prompt.md +107 -0
- data/docs/examples/index.md +7 -0
- data/docs/examples/multi-file-composition.md +123 -0
- data/docs/getting-started/configuration.md +106 -0
- data/docs/getting-started/index.md +7 -0
- data/docs/getting-started/installation.md +10 -73
- data/docs/getting-started/quick-start.md +50 -225
- data/docs/guides/comment-stripping.md +64 -0
- data/docs/guides/custom-directives.md +115 -0
- data/docs/guides/erb-rendering.md +102 -0
- data/docs/guides/includes.md +146 -0
- data/docs/guides/index.md +11 -0
- data/docs/guides/parameters.md +96 -0
- data/docs/guides/parsing.md +127 -0
- data/docs/guides/shell-expansion.md +108 -0
- data/docs/index.md +54 -214
- data/lib/pm/configuration.rb +17 -0
- data/lib/pm/directives.rb +61 -0
- data/lib/pm/metadata.rb +17 -0
- data/lib/pm/parsed.rb +59 -0
- data/lib/pm/shell.rb +57 -0
- data/lib/pm/version.rb +5 -0
- data/lib/pm.rb +121 -0
- data/lib/prompt_manager.rb +2 -27
- data/mkdocs.yml +101 -66
- metadata +42 -101
- data/docs/.keep +0 -0
- data/docs/advanced/custom-keywords.md +0 -421
- data/docs/advanced/dynamic-directives.md +0 -535
- data/docs/advanced/performance.md +0 -612
- data/docs/advanced/search-integration.md +0 -635
- data/docs/api/directive-processor.md +0 -431
- data/docs/api/prompt-class.md +0 -354
- data/docs/api/storage-adapters.md +0 -462
- data/docs/assets/favicon.ico +0 -1
- data/docs/assets/logo.svg +0 -24
- data/docs/core-features/comments.md +0 -48
- data/docs/core-features/directive-processing.md +0 -38
- data/docs/core-features/erb-integration.md +0 -68
- data/docs/core-features/error-handling.md +0 -197
- data/docs/core-features/parameter-history.md +0 -76
- data/docs/core-features/parameterized-prompts.md +0 -500
- data/docs/core-features/shell-integration.md +0 -79
- data/docs/development/architecture.md +0 -544
- data/docs/development/contributing.md +0 -425
- data/docs/development/roadmap.md +0 -234
- data/docs/development/testing.md +0 -822
- data/docs/examples/advanced.md +0 -523
- data/docs/examples/basic.md +0 -688
- data/docs/examples/real-world.md +0 -776
- data/docs/examples.md +0 -337
- data/docs/getting-started/basic-concepts.md +0 -318
- data/docs/migration/v0.9.0.md +0 -459
- data/docs/migration/v1.0.0.md +0 -591
- data/docs/storage/activerecord-adapter.md +0 -348
- data/docs/storage/custom-adapters.md +0 -176
- data/docs/storage/filesystem-adapter.md +0 -236
- data/docs/storage/overview.md +0 -427
- data/examples/advanced_integrations.rb +0 -52
- data/examples/directives.rb +0 -102
- data/examples/prompts_dir/advanced_demo.txt +0 -79
- data/examples/prompts_dir/directive_example.json +0 -1
- data/examples/prompts_dir/directive_example.txt +0 -8
- data/examples/prompts_dir/todo.json +0 -1
- data/examples/prompts_dir/todo.txt +0 -7
- data/examples/prompts_dir/toy/8-ball.txt +0 -4
- data/examples/rgfzf +0 -44
- data/examples/simple.rb +0 -160
- data/examples/using_search_proc.rb +0 -68
- data/improvement_plan.md +0 -996
- data/lib/prompt_manager/directive_processor.rb +0 -47
- data/lib/prompt_manager/prompt.rb +0 -195
- data/lib/prompt_manager/storage/active_record_adapter.rb +0 -157
- data/lib/prompt_manager/storage/file_system_adapter.rb +0 -339
- data/lib/prompt_manager/storage.rb +0 -34
- data/lib/prompt_manager/version.rb +0 -5
- data/prompt_manager_logo.png +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Comment Stripping
|
|
2
|
+
|
|
3
|
+
HTML comments are stripped before any other processing stage.
|
|
4
|
+
|
|
5
|
+
## Automatic Stripping
|
|
6
|
+
|
|
7
|
+
Comments are removed during `PM.parse`:
|
|
8
|
+
|
|
9
|
+
```markdown
|
|
10
|
+
<!-- This comment will be removed -->
|
|
11
|
+
---
|
|
12
|
+
title: My Prompt
|
|
13
|
+
---
|
|
14
|
+
Content here. <!-- This too -->
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
After parsing:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
parsed = PM.parse(source)
|
|
21
|
+
parsed.content #=> "\nContent here. \n"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Multiline Comments
|
|
25
|
+
|
|
26
|
+
Multiline comments are fully removed:
|
|
27
|
+
|
|
28
|
+
```markdown
|
|
29
|
+
<!--
|
|
30
|
+
This entire block
|
|
31
|
+
is removed
|
|
32
|
+
-->
|
|
33
|
+
---
|
|
34
|
+
title: Example
|
|
35
|
+
---
|
|
36
|
+
Visible content.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Comments Before Metadata
|
|
40
|
+
|
|
41
|
+
Comments before the YAML front-matter are stripped first, so the metadata parser sees clean input:
|
|
42
|
+
|
|
43
|
+
```markdown
|
|
44
|
+
<!-- Editor note: draft version -->
|
|
45
|
+
---
|
|
46
|
+
title: My Prompt
|
|
47
|
+
---
|
|
48
|
+
Content
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Direct Access
|
|
52
|
+
|
|
53
|
+
The comment stripping method is available as a standalone utility:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
PM.strip_comments("Hello <!-- removed --> World")
|
|
57
|
+
#=> "Hello World"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Use Cases
|
|
61
|
+
|
|
62
|
+
- **Editor notes** -- Leave comments for prompt authors that don't appear in the rendered output
|
|
63
|
+
- **Disabled sections** -- Temporarily comment out parts of a prompt
|
|
64
|
+
- **Documentation** -- Annotate prompt files without affecting the AI input
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Custom Directives
|
|
2
|
+
|
|
3
|
+
Register custom methods that become available inside ERB templates.
|
|
4
|
+
|
|
5
|
+
## Registering a Directive
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
PM.register(:read) { |_ctx, path| File.read(path) }
|
|
9
|
+
PM.register(:env) { |_ctx, key| ENV.fetch(key, '') }
|
|
10
|
+
PM.register(:run) { |_ctx, cmd| `#{cmd}`.chomp }
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Use them in any prompt file:
|
|
14
|
+
|
|
15
|
+
```markdown
|
|
16
|
+
---
|
|
17
|
+
title: Deploy Prompt
|
|
18
|
+
---
|
|
19
|
+
Hostname: <%= read '/etc/hostname' %>
|
|
20
|
+
Environment: <%= env 'DEPLOY_ENV' %>
|
|
21
|
+
Recent commits: <%= run 'git log --oneline -5' %>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Aliases
|
|
25
|
+
|
|
26
|
+
Register multiple names for the same directive by passing additional names:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
PM.register(:webpage, :website, :web) { |_ctx, url| fetch_page(url) }
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
All names point to the same block. Use any of them in ERB:
|
|
33
|
+
|
|
34
|
+
```markdown
|
|
35
|
+
<%= webpage 'https://example.com' %>
|
|
36
|
+
<%= web 'https://example.com' %>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Duplicate detection still applies — if any name is already registered, an error is raised.
|
|
40
|
+
|
|
41
|
+
## The RenderContext
|
|
42
|
+
|
|
43
|
+
The first argument to every directive block is a `PM::RenderContext` with access to the current render state:
|
|
44
|
+
|
|
45
|
+
| Field | Type | Description |
|
|
46
|
+
|-------|------|-------------|
|
|
47
|
+
| `directory` | String | Directory of the file being rendered |
|
|
48
|
+
| `params` | Hash | Merged parameter values |
|
|
49
|
+
| `metadata` | PM::Metadata | Current file's metadata |
|
|
50
|
+
| `depth` | Integer | Include nesting depth (0 for top-level) |
|
|
51
|
+
| `included` | Set | File paths already in the include chain |
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
PM.register(:current_file) { |ctx| ctx.metadata.name || 'unknown' }
|
|
55
|
+
PM.register(:nesting) { |ctx| ctx.depth.to_s }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The context is always the first argument. Additional arguments come from the ERB call:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
PM.register(:greet) { |_ctx, name| "Hello, #{name}!" }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```markdown
|
|
65
|
+
<%= greet 'Alice' %>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Duplicate Registration
|
|
69
|
+
|
|
70
|
+
Registering a name that already exists raises an error:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
PM.register(:include) { |_ctx, path| path }
|
|
74
|
+
#=> RuntimeError: Directive already registered: include
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
This protects built-in directives from being overwritten.
|
|
78
|
+
|
|
79
|
+
## Resetting Directives
|
|
80
|
+
|
|
81
|
+
Remove all custom directives and restore only the built-ins:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
PM.reset_directives!
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
After reset, only `include` is registered.
|
|
88
|
+
|
|
89
|
+
## Directives in Included Files
|
|
90
|
+
|
|
91
|
+
Custom directives are available in included files too. They share the same directive registry:
|
|
92
|
+
|
|
93
|
+
```markdown
|
|
94
|
+
<!-- parent.md -->
|
|
95
|
+
<%= include 'child.md' %>
|
|
96
|
+
|
|
97
|
+
<!-- child.md -->
|
|
98
|
+
<%= read '/etc/hostname' %>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Name Format
|
|
102
|
+
|
|
103
|
+
Both symbols and strings are accepted:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
PM.register(:my_helper) { |_ctx| "works" }
|
|
107
|
+
PM.register('other_helper') { |_ctx| "also works" }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Listing Directives
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
PM.directives
|
|
114
|
+
#=> { include: #<Proc>, read: #<Proc>, ... }
|
|
115
|
+
```
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# ERB Rendering
|
|
2
|
+
|
|
3
|
+
ERB templates are evaluated on demand when `to_s` is called.
|
|
4
|
+
|
|
5
|
+
## Basics
|
|
6
|
+
|
|
7
|
+
Any `<%= expression %>` in the content is evaluated as Ruby:
|
|
8
|
+
|
|
9
|
+
```markdown
|
|
10
|
+
---
|
|
11
|
+
title: Math
|
|
12
|
+
---
|
|
13
|
+
Result: <%= 2 + 2 %>
|
|
14
|
+
Today: <%= Time.now.strftime('%Y-%m-%d') %>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
parsed = PM.parse('math.md')
|
|
19
|
+
parsed.to_s #=> "Result: 4\nToday: 2025-01-15\n"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Template Parameters
|
|
23
|
+
|
|
24
|
+
Parameters declared in the YAML front-matter are available as local methods inside ERB:
|
|
25
|
+
|
|
26
|
+
```markdown
|
|
27
|
+
---
|
|
28
|
+
title: Greeting
|
|
29
|
+
parameters:
|
|
30
|
+
name: World
|
|
31
|
+
---
|
|
32
|
+
Hello, <%= name %>!
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
parsed = PM.parse('greeting.md')
|
|
37
|
+
parsed.to_s #=> "Hello, World!"
|
|
38
|
+
parsed.to_s('name' => 'Alice') #=> "Hello, Alice!"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
See [Parameters](parameters.md) for details on required vs default values.
|
|
42
|
+
|
|
43
|
+
## Registered Directives
|
|
44
|
+
|
|
45
|
+
Custom directives registered with `PM.register` are also available in ERB:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
PM.register(:upcase) { |_ctx, str| str.upcase }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```markdown
|
|
52
|
+
---
|
|
53
|
+
title: Example
|
|
54
|
+
---
|
|
55
|
+
<%= upcase 'hello' %>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
See [Custom Directives](custom-directives.md) for the full guide.
|
|
59
|
+
|
|
60
|
+
## Built-in Directives
|
|
61
|
+
|
|
62
|
+
PM includes one built-in directive:
|
|
63
|
+
|
|
64
|
+
- **`include`** -- Include and render another prompt file. See [Including Files](includes.md).
|
|
65
|
+
|
|
66
|
+
## Disabling ERB
|
|
67
|
+
|
|
68
|
+
Set `erb: false` in the file's YAML metadata:
|
|
69
|
+
|
|
70
|
+
```markdown
|
|
71
|
+
---
|
|
72
|
+
title: Raw Template
|
|
73
|
+
erb: false
|
|
74
|
+
---
|
|
75
|
+
This <%= expression %> is preserved as-is.
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
When ERB is disabled:
|
|
79
|
+
|
|
80
|
+
- `to_s` returns the content without template evaluation
|
|
81
|
+
- Parameters are ignored
|
|
82
|
+
- The `include` directive does not execute
|
|
83
|
+
- `metadata.includes` is an empty array after `to_s`
|
|
84
|
+
|
|
85
|
+
### Global Default
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
PM.configure { |c| c.erb = false }
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Per-file metadata always overrides the global setting. A file with `erb: true` gets ERB rendering even when the global default is `false`.
|
|
92
|
+
|
|
93
|
+
## Pipeline Position
|
|
94
|
+
|
|
95
|
+
ERB rendering is the last stage of the pipeline, executed on demand:
|
|
96
|
+
|
|
97
|
+
1. Strip HTML comments
|
|
98
|
+
2. Extract YAML metadata
|
|
99
|
+
3. Shell expansion (at parse time)
|
|
100
|
+
4. **ERB rendering** (at `to_s` time)
|
|
101
|
+
|
|
102
|
+
This means shell variables are already expanded before ERB sees the content. If content contains both `$VAR` and `<%= param %>`, the shell variable is resolved first, then ERB fills in the parameters.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Including Files
|
|
2
|
+
|
|
3
|
+
The `include` directive lets you compose prompts from multiple files.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```markdown
|
|
8
|
+
---
|
|
9
|
+
title: Full Review
|
|
10
|
+
parameters:
|
|
11
|
+
code: null
|
|
12
|
+
---
|
|
13
|
+
<%= include 'common/header.md' %>
|
|
14
|
+
|
|
15
|
+
Review this code:
|
|
16
|
+
<%= code %>
|
|
17
|
+
|
|
18
|
+
<%= include 'common/footer.md' %>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
parsed = PM.parse('full_review.md')
|
|
23
|
+
puts parsed.to_s('code' => File.read('app.rb'))
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## How Includes Work
|
|
27
|
+
|
|
28
|
+
Each included file goes through the full processing pipeline:
|
|
29
|
+
|
|
30
|
+
1. HTML comments stripped
|
|
31
|
+
2. YAML metadata extracted
|
|
32
|
+
3. Shell expansion applied
|
|
33
|
+
4. ERB rendered (with the parent's parameter values)
|
|
34
|
+
|
|
35
|
+
The included file's rendered content replaces the `<%= include ... %>` tag.
|
|
36
|
+
|
|
37
|
+
## Path Resolution
|
|
38
|
+
|
|
39
|
+
Include paths are resolved relative to the **parent file's directory**:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
prompts/
|
|
43
|
+
review.md # contains: <%= include 'common/header.md' %>
|
|
44
|
+
common/
|
|
45
|
+
header.md # resolved from prompts/common/header.md
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Parameter Passing
|
|
49
|
+
|
|
50
|
+
The parent's parameter values are passed to included files. Child files can use the same parameter names:
|
|
51
|
+
|
|
52
|
+
```markdown
|
|
53
|
+
<!-- parent.md -->
|
|
54
|
+
---
|
|
55
|
+
parameters:
|
|
56
|
+
name: null
|
|
57
|
+
---
|
|
58
|
+
<%= include 'greeting.md' %>
|
|
59
|
+
Done.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```markdown
|
|
63
|
+
<!-- greeting.md -->
|
|
64
|
+
---
|
|
65
|
+
title: Greeting
|
|
66
|
+
---
|
|
67
|
+
Hello, <%= name %>!
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
parsed = PM.parse('parent.md')
|
|
72
|
+
parsed.to_s('name' => 'Alice')
|
|
73
|
+
#=> "Hello, Alice!\nDone."
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Nested Includes
|
|
77
|
+
|
|
78
|
+
Includes can be nested. File A can include File B, which includes File C:
|
|
79
|
+
|
|
80
|
+
```markdown
|
|
81
|
+
<!-- a.md -->
|
|
82
|
+
<%= include 'b.md' %>
|
|
83
|
+
|
|
84
|
+
<!-- b.md -->
|
|
85
|
+
<%= include 'c.md' %>
|
|
86
|
+
|
|
87
|
+
<!-- c.md -->
|
|
88
|
+
Content from C
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Circular Include Detection
|
|
92
|
+
|
|
93
|
+
PM detects circular includes and raises an error:
|
|
94
|
+
|
|
95
|
+
```markdown
|
|
96
|
+
<!-- circular_a.md -->
|
|
97
|
+
<%= include 'circular_b.md' %>
|
|
98
|
+
|
|
99
|
+
<!-- circular_b.md -->
|
|
100
|
+
<%= include 'circular_a.md' %>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
PM.parse('circular_a.md').to_s
|
|
105
|
+
#=> RuntimeError: Circular include detected: /path/to/circular_a.md
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Includes Metadata
|
|
109
|
+
|
|
110
|
+
After calling `to_s`, the parent's metadata has an `includes` key with a tree of what was included:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
parsed = PM.parse('full_review.md')
|
|
114
|
+
parsed.to_s('code' => source)
|
|
115
|
+
|
|
116
|
+
parsed.metadata.includes
|
|
117
|
+
#=> [
|
|
118
|
+
# {
|
|
119
|
+
# path: "/prompts/common/header.md",
|
|
120
|
+
# depth: 1,
|
|
121
|
+
# metadata: { title: "Header", ... },
|
|
122
|
+
# includes: []
|
|
123
|
+
# },
|
|
124
|
+
# {
|
|
125
|
+
# path: "/prompts/common/footer.md",
|
|
126
|
+
# depth: 1,
|
|
127
|
+
# metadata: { title: "Footer", ... },
|
|
128
|
+
# includes: []
|
|
129
|
+
# }
|
|
130
|
+
# ]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Nested includes form a tree -- each entry's `includes` array contains its own children.
|
|
134
|
+
|
|
135
|
+
The `includes` array is `nil` before `to_s` is called and is reset on each call.
|
|
136
|
+
|
|
137
|
+
## Requirements
|
|
138
|
+
|
|
139
|
+
- The `include` directive requires file context. Using it with string-parsed prompts raises an error:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
PM.parse("---\n---\n<%= include 'other.md' %>").to_s
|
|
143
|
+
#=> RuntimeError: include requires a file context (use PM.parse with a file path)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- When `erb: false`, the include directive does not execute and `metadata.includes` is an empty array.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Guides
|
|
2
|
+
|
|
3
|
+
In-depth guides for each PM feature.
|
|
4
|
+
|
|
5
|
+
- [Parsing Prompts](parsing.md) -- File paths vs raw strings, metadata extraction
|
|
6
|
+
- [Parameters](parameters.md) -- Required and default parameters, rendering with `to_s`
|
|
7
|
+
- [Shell Expansion](shell-expansion.md) -- Environment variables and command substitution
|
|
8
|
+
- [ERB Rendering](erb-rendering.md) -- Template evaluation and disabling
|
|
9
|
+
- [Including Files](includes.md) -- Composing prompts from multiple files
|
|
10
|
+
- [Custom Directives](custom-directives.md) -- Registering your own ERB helpers
|
|
11
|
+
- [Comment Stripping](comment-stripping.md) -- HTML comment removal
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Parameters
|
|
2
|
+
|
|
3
|
+
Parameters are declared in the YAML `parameters` hash and used as ERB template variables.
|
|
4
|
+
|
|
5
|
+
## Declaring Parameters
|
|
6
|
+
|
|
7
|
+
```markdown
|
|
8
|
+
---
|
|
9
|
+
title: Code Review
|
|
10
|
+
parameters:
|
|
11
|
+
language: ruby
|
|
12
|
+
code: null
|
|
13
|
+
style_guide: ~/guides/default.md
|
|
14
|
+
---
|
|
15
|
+
Review the following <%= language %> code using the style guide
|
|
16
|
+
at <%= style_guide %>:
|
|
17
|
+
|
|
18
|
+
<%= code %>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Required vs Default
|
|
22
|
+
|
|
23
|
+
- **`null` value** -- the parameter is **required**. `to_s` raises `ArgumentError` if not provided.
|
|
24
|
+
- **Any other value** -- used as the **default**. Can be overridden when calling `to_s`.
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
parsed = PM.parse('code_review.md')
|
|
28
|
+
|
|
29
|
+
parsed.metadata.parameters
|
|
30
|
+
#=> {"language" => "ruby", "code" => nil, "style_guide" => "~/guides/default.md"}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Rendering with to_s
|
|
34
|
+
|
|
35
|
+
`to_s` accepts a Hash of parameter values:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
# Supply required params, accept defaults for the rest
|
|
39
|
+
parsed.to_s('code' => File.read('app.rb'))
|
|
40
|
+
|
|
41
|
+
# Override a default
|
|
42
|
+
parsed.to_s('code' => File.read('app.py'), 'language' => 'python')
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Symbol keys work too:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
parsed.to_s(code: File.read('app.rb'))
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### All Defaults
|
|
52
|
+
|
|
53
|
+
When every parameter has a default value, `to_s` needs no arguments:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
parsed.to_s #=> renders with all defaults
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Missing Required Parameters
|
|
60
|
+
|
|
61
|
+
Calling `to_s` without supplying a required parameter raises `ArgumentError`:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
parsed.to_s
|
|
65
|
+
#=> ArgumentError: Missing required parameters: code
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The error message lists all missing parameters.
|
|
69
|
+
|
|
70
|
+
## Parameters in Included Files
|
|
71
|
+
|
|
72
|
+
When a file includes another file via `<%= include 'child.md' %>`, the parent's parameter values are passed to the child. The child can use the same parameter names:
|
|
73
|
+
|
|
74
|
+
```markdown
|
|
75
|
+
<!-- parent.md -->
|
|
76
|
+
---
|
|
77
|
+
title: Parent
|
|
78
|
+
parameters:
|
|
79
|
+
name: null
|
|
80
|
+
---
|
|
81
|
+
<%= include 'greeting.md' %>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```markdown
|
|
85
|
+
<!-- greeting.md -->
|
|
86
|
+
---
|
|
87
|
+
title: Greeting
|
|
88
|
+
---
|
|
89
|
+
Hello, <%= name %>!
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
parsed = PM.parse('parent.md')
|
|
94
|
+
parsed.to_s('name' => 'Alice')
|
|
95
|
+
#=> "Hello, Alice!"
|
|
96
|
+
```
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Parsing Prompts
|
|
2
|
+
|
|
3
|
+
`PM.parse` is the main entry point. It accepts a file path, a Symbol, a single word, or a raw string.
|
|
4
|
+
|
|
5
|
+
## File Paths
|
|
6
|
+
|
|
7
|
+
A source is treated as a file path when it:
|
|
8
|
+
|
|
9
|
+
- Is a `Pathname` object
|
|
10
|
+
- Responds to `to_path`
|
|
11
|
+
- Is a String ending in `.md`
|
|
12
|
+
- Is a Symbol (`.md` is appended, e.g. `:code_review` → `code_review.md`)
|
|
13
|
+
- Is a single word containing only letters, digits, or underscores (`.md` is appended, e.g. `"code_review"` → `code_review.md`)
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
# String file path
|
|
17
|
+
parsed = PM.parse('code_review.md')
|
|
18
|
+
|
|
19
|
+
# Pathname
|
|
20
|
+
parsed = PM.parse(Pathname.new('code_review.md'))
|
|
21
|
+
|
|
22
|
+
# Symbol (basename — .md appended)
|
|
23
|
+
parsed = PM.parse(:code_review)
|
|
24
|
+
|
|
25
|
+
# Single word (basename — .md appended)
|
|
26
|
+
parsed = PM.parse('code_review')
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
When parsing a file, PM adds these keys to the metadata automatically:
|
|
30
|
+
|
|
31
|
+
| Key | Type | Description |
|
|
32
|
+
|-----|------|-------------|
|
|
33
|
+
| `directory` | String | Absolute path to the file's parent directory |
|
|
34
|
+
| `name` | String | Filename (e.g., `code_review.md`) |
|
|
35
|
+
| `created_at` | Time | File creation timestamp |
|
|
36
|
+
| `modified_at` | Time | Last modification timestamp |
|
|
37
|
+
|
|
38
|
+
### prompts_dir
|
|
39
|
+
|
|
40
|
+
Relative file paths are resolved against `PM.config.prompts_dir`:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
PM.configure { |c| c.prompts_dir = '~/.prompts' }
|
|
44
|
+
|
|
45
|
+
PM.parse('agents/summarize.md')
|
|
46
|
+
#=> reads ~/.prompts/agents/summarize.md
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Absolute paths bypass `prompts_dir`:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
PM.parse('/etc/prompts/system.md')
|
|
53
|
+
#=> reads /etc/prompts/system.md regardless of prompts_dir
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Raw Strings
|
|
57
|
+
|
|
58
|
+
Any other string is parsed directly:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
parsed = PM.parse("---\ntitle: Hello\n---\nContent here")
|
|
62
|
+
|
|
63
|
+
parsed.metadata.title #=> "Hello"
|
|
64
|
+
parsed.content #=> "\nContent here\n"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
String-parsed prompts do not have `directory`, `name`, `created_at`, or `modified_at` in their metadata.
|
|
68
|
+
|
|
69
|
+
!!! warning "Include limitations"
|
|
70
|
+
The `include` directive requires file context to resolve relative paths. It raises an error when used with string-parsed prompts.
|
|
71
|
+
|
|
72
|
+
## Metadata Extraction
|
|
73
|
+
|
|
74
|
+
YAML front-matter is delimited by `---` fences:
|
|
75
|
+
|
|
76
|
+
```markdown
|
|
77
|
+
---
|
|
78
|
+
title: My Prompt
|
|
79
|
+
provider: openai
|
|
80
|
+
model: gpt-4
|
|
81
|
+
temperature: 0.3
|
|
82
|
+
---
|
|
83
|
+
The prompt content starts here.
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
All YAML keys become accessible via dot notation on the `PM::Metadata` object:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
parsed.metadata.title #=> "My Prompt"
|
|
90
|
+
parsed.metadata.provider #=> "openai"
|
|
91
|
+
parsed.metadata.temperature #=> 0.3
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Boolean keys also get predicate methods:
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
parsed.metadata.shell #=> true
|
|
98
|
+
parsed.metadata.shell? #=> true
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### No Metadata
|
|
102
|
+
|
|
103
|
+
Files without front-matter are valid. The metadata object is empty and the entire content is preserved:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
parsed = PM.parse("Just plain text")
|
|
107
|
+
parsed.metadata.title #=> nil
|
|
108
|
+
parsed.content #=> "Just plain text"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Bracket Accessor
|
|
112
|
+
|
|
113
|
+
You can also access metadata with bracket notation:
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
parsed[:title] #=> "My Prompt"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Pipeline Stages
|
|
120
|
+
|
|
121
|
+
After parsing, the content has been through these stages:
|
|
122
|
+
|
|
123
|
+
1. HTML comments stripped
|
|
124
|
+
2. YAML metadata extracted
|
|
125
|
+
3. Shell expansion applied (if `shell: true`)
|
|
126
|
+
|
|
127
|
+
ERB rendering happens later, on demand, when `to_s` is called.
|