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,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,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 */
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|