prompt_manager 1.0.2 → 1.2.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +138 -27
  3. data/README.md +127 -271
  4. data/lib/pm/configuration.rb +34 -4
  5. data/lib/pm/core_directives.rb +21 -20
  6. data/lib/pm/directive.rb +26 -34
  7. data/lib/pm/directives.rb +55 -16
  8. data/lib/pm/errors.rb +71 -0
  9. data/lib/pm/metadata.rb +110 -9
  10. data/lib/pm/parsed.rb +51 -46
  11. data/lib/pm/render_scope.rb +74 -0
  12. data/lib/pm/renderer.rb +59 -0
  13. data/lib/pm/shell.rb +95 -40
  14. data/lib/pm/version.rb +1 -1
  15. data/lib/pm.rb +116 -57
  16. data/lib/prompt_manager.rb +6 -0
  17. metadata +13 -113
  18. data/.envrc +0 -3
  19. data/.irbrc +0 -14
  20. data/COMMITS.md +0 -196
  21. data/Rakefile +0 -12
  22. data/docs/api/configuration.md +0 -59
  23. data/docs/api/constants.md +0 -60
  24. data/docs/api/index.md +0 -14
  25. data/docs/api/metadata.md +0 -99
  26. data/docs/api/parsed.md +0 -98
  27. data/docs/api/pm-module.md +0 -169
  28. data/docs/api/render-context.md +0 -51
  29. data/docs/architecture/design-decisions.md +0 -70
  30. data/docs/architecture/index.md +0 -6
  31. data/docs/architecture/processing-pipeline.md +0 -112
  32. data/docs/assets/css/custom.css +0 -1
  33. data/docs/assets/images/prompt_manager.gif +0 -0
  34. data/docs/assets/images/prompt_manager.mp4 +0 -0
  35. data/docs/examples/ai-agent-prompts.md +0 -173
  36. data/docs/examples/code-review-prompt.md +0 -107
  37. data/docs/examples/index.md +0 -7
  38. data/docs/examples/multi-file-composition.md +0 -123
  39. data/docs/getting-started/configuration.md +0 -106
  40. data/docs/getting-started/index.md +0 -7
  41. data/docs/getting-started/installation.md +0 -34
  42. data/docs/getting-started/quick-start.md +0 -94
  43. data/docs/guides/comment-stripping.md +0 -64
  44. data/docs/guides/custom-directives.md +0 -180
  45. data/docs/guides/erb-rendering.md +0 -102
  46. data/docs/guides/includes.md +0 -184
  47. data/docs/guides/index.md +0 -11
  48. data/docs/guides/parameters.md +0 -96
  49. data/docs/guides/parsing.md +0 -151
  50. data/docs/guides/shell-expansion.md +0 -108
  51. data/docs/index.md +0 -70
  52. data/mkdocs.yml +0 -181
@@ -1,123 +0,0 @@
1
- # Multi-File Composition
2
-
3
- Compose prompts from shared components using the `include` directive.
4
-
5
- ## Directory Structure
6
-
7
- ```
8
- prompts/
9
- review.md
10
- common/
11
- system_context.md
12
- output_format.md
13
- ruby_standards.md
14
- ```
15
-
16
- ## Shared Components
17
-
18
- `prompts/common/system_context.md`:
19
-
20
- ```markdown
21
- ---
22
- title: System Context
23
- ---
24
- You are a senior software engineer with expertise in <%= language %>.
25
- Current date: $(date +%Y-%m-%d)
26
- User: $USER
27
- ```
28
-
29
- `prompts/common/output_format.md`:
30
-
31
- ```markdown
32
- ---
33
- title: Output Format
34
- ---
35
- Format your response as:
36
- 1. Summary (2-3 sentences)
37
- 2. Issues found (bulleted list)
38
- 3. Suggestions (numbered list)
39
- 4. Revised code (if applicable)
40
- ```
41
-
42
- `prompts/common/ruby_standards.md`:
43
-
44
- ```markdown
45
- ---
46
- title: Ruby Standards
47
- ---
48
- Follow these Ruby conventions:
49
- - Use snake_case for methods and variables
50
- - Prefer single-line blocks with braces for short expressions
51
- - Use guard clauses over nested conditionals
52
- - Avoid monkey-patching standard library classes
53
- ```
54
-
55
- ## The Composed Prompt
56
-
57
- `prompts/review.md`:
58
-
59
- ```markdown
60
- ---
61
- title: Full Code Review
62
- parameters:
63
- language: ruby
64
- code: null
65
- ---
66
- <%= include 'common/system_context.md' %>
67
-
68
- <%= include 'common/ruby_standards.md' %>
69
-
70
- Review the following code:
71
-
72
- <%= code %>
73
-
74
- <%= include 'common/output_format.md' %>
75
- ```
76
-
77
- ## Usage
78
-
79
- ```ruby
80
- PM.configure { |c| c.prompts_dir = './prompts' }
81
-
82
- parsed = PM.parse('review.md')
83
- prompt = parsed.to_s('code' => File.read('app.rb'))
84
- ```
85
-
86
- The rendered prompt combines all four files into a single coherent prompt.
87
-
88
- ## Inspecting the Include Tree
89
-
90
- ```ruby
91
- parsed.metadata.includes
92
- #=> [
93
- # { path: ".../common/system_context.md", depth: 1, metadata: {...}, includes: [] },
94
- # { path: ".../common/ruby_standards.md", depth: 1, metadata: {...}, includes: [] },
95
- # { path: ".../common/output_format.md", depth: 1, metadata: {...}, includes: [] }
96
- # ]
97
- ```
98
-
99
- ## Nested Includes
100
-
101
- Components can include other components. For example, `system_context.md` could include a `team_info.md`:
102
-
103
- ```markdown
104
- ---
105
- title: System Context
106
- ---
107
- You are a senior software engineer.
108
- <%= include 'common/team_info.md' %>
109
- ```
110
-
111
- The include tree reflects the nesting:
112
-
113
- ```ruby
114
- parsed.metadata.includes[0][:includes]
115
- #=> [{ path: ".../common/team_info.md", depth: 2, metadata: {...}, includes: [] }]
116
- ```
117
-
118
- ## Benefits
119
-
120
- - **Reuse** -- Shared components (system context, output format) written once
121
- - **Consistency** -- Changes to a component propagate to all prompts that include it
122
- - **Modularity** -- Swap components per use case (different standards for different languages)
123
- - **Traceability** -- The includes tree shows exactly what went into each prompt
@@ -1,106 +0,0 @@
1
- # Configuration
2
-
3
- PM has three global settings controlled through `PM.configure`.
4
-
5
- ## Setting Defaults
6
-
7
- ```ruby
8
- PM.configure do |config|
9
- config.prompts_dir = '~/.prompts' # default: ''
10
- config.shell = true # default: true
11
- config.erb = true # default: true
12
- end
13
- ```
14
-
15
- ## prompts_dir
16
-
17
- Prepended to relative file paths passed to `PM.parse`. Absolute paths bypass it. Symbols and single words are converted to `.md` basenames first.
18
-
19
- ```ruby
20
- PM.configure { |c| c.prompts_dir = '/usr/share/prompts' }
21
-
22
- PM.parse('code_review.md')
23
- #=> reads /usr/share/prompts/code_review.md
24
-
25
- PM.parse(:code_review)
26
- #=> reads /usr/share/prompts/code_review.md
27
-
28
- PM.parse('code_review')
29
- #=> reads /usr/share/prompts/code_review.md
30
-
31
- PM.parse('/absolute/path/review.md')
32
- #=> reads /absolute/path/review.md (prompts_dir ignored)
33
- ```
34
-
35
- Subdirectories work naturally:
36
-
37
- ```ruby
38
- PM.configure { |c| c.prompts_dir = '~/.prompts' }
39
-
40
- PM.parse('agents/summarize.md')
41
- #=> reads ~/.prompts/agents/summarize.md
42
- ```
43
-
44
- ## shell
45
-
46
- Controls whether shell expansion (`$VAR`, `$(command)`) runs during parsing. Defaults to `true`.
47
-
48
- ```ruby
49
- PM.configure { |c| c.shell = false }
50
-
51
- # All files now skip shell expansion by default
52
- ```
53
-
54
- Per-file YAML metadata overrides the global setting:
55
-
56
- ```markdown
57
- ---
58
- title: Override Example
59
- shell: true
60
- ---
61
- This file gets shell expansion even when the global default is false.
62
- User: $USER
63
- ```
64
-
65
- ## erb
66
-
67
- Controls whether ERB rendering runs during `to_s`. Defaults to `true`.
68
-
69
- ```ruby
70
- PM.configure { |c| c.erb = false }
71
-
72
- # All files now skip ERB rendering by default
73
- ```
74
-
75
- Per-file metadata overrides this too:
76
-
77
- ```markdown
78
- ---
79
- title: Override Example
80
- erb: true
81
- ---
82
- This file renders ERB even when the global default is false.
83
- Result: <%= 2 + 2 %>
84
- ```
85
-
86
- ## Resetting
87
-
88
- Restore all settings to their defaults:
89
-
90
- ```ruby
91
- PM.config.reset!
92
-
93
- PM.config.prompts_dir #=> ''
94
- PM.config.shell #=> true
95
- PM.config.erb #=> true
96
- ```
97
-
98
- ## Accessing Current Settings
99
-
100
- ```ruby
101
- PM.config.prompts_dir
102
- PM.config.shell
103
- PM.config.erb
104
- ```
105
-
106
- `PM.config` returns the singleton `PM::Configuration` instance. `PM.configure` yields the same instance.
@@ -1,7 +0,0 @@
1
- # Getting Started
2
-
3
- This section covers everything you need to start using PM in your Ruby projects.
4
-
5
- - [Installation](installation.md) -- Add PM to your project
6
- - [Quick Start](quick-start.md) -- Parse your first prompt in a few lines
7
- - [Configuration](configuration.md) -- Set global defaults for prompt directories, shell expansion, and ERB rendering
@@ -1,34 +0,0 @@
1
- # Installation
2
-
3
- ## Gemfile
4
-
5
- Add PM to your Gemfile:
6
-
7
- ```ruby
8
- gem 'prompt_manager'
9
- ```
10
-
11
- Then run:
12
-
13
- ```bash
14
- bundle install
15
- ```
16
-
17
- ## Manual Install
18
-
19
- ```bash
20
- gem install prompt_manager
21
- ```
22
-
23
- ## Require
24
-
25
- ```ruby
26
- require 'pm'
27
- ```
28
-
29
- The `PM` module is the primary interface. All public methods are accessed through it.
30
-
31
- ## Requirements
32
-
33
- - Ruby >= 3.2.0
34
- - No runtime dependencies beyond the standard library (plus `ostruct` on Ruby 4.0+)
@@ -1,94 +0,0 @@
1
- # Quick Start
2
-
3
- ## Parse a String
4
-
5
- ```ruby
6
- require 'pm'
7
-
8
- parsed = PM.parse("---\ntitle: Hello\n---\nContent here")
9
-
10
- parsed.metadata.title #=> "Hello"
11
- parsed.content #=> "\nContent here\n"
12
- ```
13
-
14
- ## Parse a File
15
-
16
- Given `greeting.md`:
17
-
18
- ```markdown
19
- ---
20
- title: Greeting
21
- parameters:
22
- name: null
23
- ---
24
- Hello, <%= name %>! Welcome.
25
- ```
26
-
27
- ```ruby
28
- # Any of these forms work:
29
- parsed = PM.parse('greeting.md') # explicit .md extension
30
- parsed = PM.parse(:greeting) # Symbol — .md appended
31
- parsed = PM.parse('greeting') # single word — .md appended
32
-
33
- parsed.metadata.title #=> "Greeting"
34
- parsed.metadata.parameters #=> {"name" => nil}
35
-
36
- # Render with parameters
37
- puts parsed.to_s('name' => 'Alice')
38
- #=> "Hello, Alice! Welcome."
39
- ```
40
-
41
- When parsing a file, PM also adds `directory`, `name`, `created_at`, and `modified_at` to the metadata.
42
-
43
- ## Parse a String Directly
44
-
45
- `PM.parse` treats single words as prompt IDs and looks them up as files. To always parse a string as content, use `PM.parse_string`:
46
-
47
- ```ruby
48
- PM.parse('hello') #=> looks for hello.md in prompts_dir
49
- PM.parse_string('hello') #=> parses "hello" as string content
50
-
51
- parsed = PM.parse_string("---\ntitle: Inline\n---\nSome content")
52
- parsed.metadata.title #=> "Inline"
53
- parsed.content #=> "Some content\n"
54
- ```
55
-
56
- ## Parameters
57
-
58
- Parameters declared in the YAML front-matter define template variables:
59
-
60
- - **`null` value** -- parameter is required; `to_s` raises `ArgumentError` if missing
61
- - **Any other value** -- used as the default; can be overridden in `to_s`
62
-
63
- ```ruby
64
- parsed = PM.parse("---\nparameters:\n lang: ruby\n code: null\n---\n<%= lang %>: <%= code %>")
65
-
66
- # Use defaults where available, supply required params
67
- parsed.to_s('code' => 'puts "hi"') #=> "ruby: puts \"hi\""
68
-
69
- # Override defaults
70
- parsed.to_s('code' => 'print("hi")', 'lang' => 'python')
71
- ```
72
-
73
- ## Shell Expansion
74
-
75
- Environment variables and commands are expanded at parse time:
76
-
77
- ```markdown
78
- ---
79
- title: Info
80
- ---
81
- User: $USER
82
- Date: $(date +%Y-%m-%d)
83
- ```
84
-
85
- ```ruby
86
- parsed = PM.parse('info.md')
87
- parsed.content #=> "User: dewayne\nDate: 2025-01-15\n"
88
- ```
89
-
90
- ## Next Steps
91
-
92
- - [Configuration](configuration.md) -- Set a prompts directory and global defaults
93
- - [Parsing Guide](../guides/parsing.md) -- File vs string parsing in depth
94
- - [Parameters Guide](../guides/parameters.md) -- Required and default parameters
@@ -1,64 +0,0 @@
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
@@ -1,180 +0,0 @@
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 the built-in directives are registered: `include`, `insert`, and `read`.
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
- ## Class-Based Directives
111
-
112
- For organized groups of directives, subclass `PM::Directive`:
113
-
114
- ```ruby
115
- class MyDirectives < PM::Directive
116
- desc "Fetch environment variable"
117
- def env(ctx, key)
118
- ENV.fetch(key, '')
119
- end
120
-
121
- desc "Run a shell command"
122
- def run(ctx, cmd)
123
- `#{cmd}`.chomp
124
- end
125
- alias_method :exec, :run
126
-
127
- # No desc — not registered as a directive
128
- def helper
129
- "internal"
130
- end
131
- end
132
-
133
- PM::Directive.register_all
134
- ```
135
-
136
- `desc` marks the next method as a directive. Methods without `desc` are ordinary helpers and won't be registered. `alias_method` aliases are detected automatically via `UnboundMethod#original_name`.
137
-
138
- ### Category name
139
-
140
- The class name determines a category heading (useful for help output):
141
-
142
- ```ruby
143
- MyDirectives.category_name #=> "My"
144
- PM::CoreDirectives.category_name #=> "Core"
145
- ```
146
-
147
- The last segment of the class name is used, with `Directives` stripped and camelCase split.
148
-
149
- ### Dispatch customization
150
-
151
- Override `build_dispatch_block` to customize how methods are called when dispatched through `PM.directives`:
152
-
153
- ```ruby
154
- class MyDirectives < PM::Directive
155
- class << self
156
- # Default: proc { |ctx, *args| inst.send(method_name, ctx, *args) }
157
- def build_dispatch_block(inst, method_name)
158
- proc { |_ctx, *args| inst.send(method_name, args.flatten) }
159
- end
160
- end
161
- end
162
- ```
163
-
164
- ### Subclass tracking
165
-
166
- All `PM::Directive` subclasses (direct and indirect) are tracked centrally:
167
-
168
- ```ruby
169
- PM::Directive.directive_subclasses
170
- #=> [PM::CoreDirectives, MyDirectives, ...]
171
- ```
172
-
173
- `register_all` iterates this list, creates a singleton instance per subclass, and registers each described method with `PM.register`.
174
-
175
- ## Listing Directives
176
-
177
- ```ruby
178
- PM.directives
179
- #=> { include: #<Proc>, insert: #<Proc>, read: #<Proc>, ... }
180
- ```
@@ -1,102 +0,0 @@
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.