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
@@ -1,47 +0,0 @@
1
- # lib/prompt_manager/directive_processor.rb
2
-
3
- # This is an example of a directive processor class.
4
- # It only supports the //include directive which is also
5
- # aliased as //import.
6
-
7
- module PromptManager
8
- class DirectiveProcessor
9
- EXCLUDED_METHODS = %w[ run initialize ]
10
-
11
- def initialize
12
- @prefix_size = PromptManager::Prompt::DIRECTIVE_SIGNAL.size
13
- @included_files = []
14
- end
15
-
16
- def run(directives)
17
- return {} if directives.nil? || directives.empty?
18
- directives.each do |key, _|
19
- sans_prefix = key[@prefix_size..]
20
- args = sans_prefix.split(' ')
21
- method_name = args.shift
22
-
23
- if EXCLUDED_METHODS.include?(method_name)
24
- directives[key] = "Error: #{method_name} is not a valid directive: #{key}"
25
- elsif respond_to?(method_name)
26
- directives[key] = send(method_name, *args)
27
- else
28
- directives[key] = "Error: Unknown directive '#{key}'"
29
- end
30
- end
31
- directives
32
- end
33
-
34
- def include(file_path)
35
- if File.exist?(file_path) &&
36
- File.readable?(file_path) &&
37
- !@included_files.include?(file_path)
38
- content = File.read(file_path)
39
- @included_files << file_path
40
- content
41
- else
42
- "Error: File '#{file_path}' not accessible"
43
- end
44
- end
45
- alias_method :import, :include
46
- end
47
- end
@@ -1,195 +0,0 @@
1
- # prompt_manager/lib/prompt_manager/prompt.rb
2
-
3
- require_relative "directive_processor"
4
-
5
- class PromptManager::Prompt
6
- COMMENT_SIGNAL = '#' # lines beginning with this are a comment
7
- DIRECTIVE_SIGNAL = '//' # Like the old IBM JCL
8
- DEFAULT_PARAMETER_REGEX = /(\[[A-Z _|]+\])/
9
- @parameter_regex = DEFAULT_PARAMETER_REGEX
10
-
11
- ##############################################
12
- ## Public class methods
13
-
14
- class << self
15
- attr_accessor :storage_adapter, :parameter_regex
16
-
17
- def get(id:)
18
- storage_adapter.get(id: id) # Return the hash directly from storage
19
- end
20
-
21
- def create(id:, text: "", parameters: {})
22
- storage_adapter.save(
23
- id: id,
24
- text: text,
25
- parameters: parameters
26
- )
27
-
28
- ::PromptManager::Prompt.new(id: id, context: [], directives_processor: PromptManager::DirectiveProcessor.new)
29
- end
30
-
31
- def find(id:)
32
- ::PromptManager::Prompt.new(id: id, context: [], directives_processor: PromptManager::DirectiveProcessor.new)
33
- end
34
-
35
- def destroy(id:)
36
- prompt = find(id: id)
37
- prompt.delete
38
- end
39
-
40
- def search(for_what)
41
- storage_adapter.search(for_what)
42
- end
43
-
44
- def method_missing(method_name, *args, &block)
45
- if storage_adapter.respond_to?(method_name)
46
- storage_adapter.send(method_name, *args, &block)
47
- else
48
- super
49
- end
50
- end
51
-
52
- def respond_to_missing?(method_name, include_private = false)
53
- storage_adapter.respond_to?(method_name, include_private) || super
54
- end
55
- end
56
-
57
- ##############################################
58
- ## Public Instance Methods
59
-
60
- attr_accessor :id, # String name for the prompt
61
- :text, # String, full text of the prompt
62
- :parameters # Hash, Key and Value are Strings
63
-
64
-
65
- def initialize(
66
- id: nil, # A String name for the prompt
67
- context: [], # TODO: Array of Strings or Pathname?
68
- directives_processor: PromptManager::DirectiveProcessor.new,
69
- external_binding: binding,
70
- erb_flag: false, # replace $ENVAR and ${ENVAR} when true
71
- envar_flag: false # process ERB against the external_binding when true
72
- )
73
-
74
- @id = id
75
- @directives_processor = directives_processor
76
-
77
- validate_arguments(@id)
78
-
79
- @record = db.get(id: id)
80
- @text = @record[:text] || ""
81
- @parameters = @record[:parameters] || {}
82
- @directives = {}
83
- @external_binding = external_binding
84
- @erb_flag = erb_flag
85
- @envar_flag = envar_flag
86
- end
87
-
88
- def validate_arguments(prompt_id, prompts_db=db)
89
- raise ArgumentError, 'id cannot be blank' if prompt_id.nil? || prompt_id.strip.empty?
90
- raise(ArgumentError, 'storage_adapter is not set') if prompts_db.nil?
91
- end
92
-
93
- def to_s
94
- processed_text = remove_comments
95
- processed_text = substitute_values(processed_text, @parameters)
96
- processed_text = substitute_env_vars(processed_text)
97
- processed_text = process_directives(processed_text)
98
- process_erb(processed_text)
99
- end
100
-
101
- def save
102
- db.save(
103
- id: id,
104
- text: text, # Save the original text
105
- parameters: parameters
106
- )
107
- end
108
-
109
- def delete = db.delete(id: id)
110
-
111
-
112
- ######################################
113
- private
114
-
115
- def db = self.class.storage_adapter
116
-
117
-
118
- def remove_comments
119
- lines = @text.gsub(/<!--.*?-->/m, '').lines(chomp: true)
120
- markdown_block_depth = 0
121
- filtered_lines = []
122
- end_index = lines.index("__END__") || lines.size
123
-
124
- lines[0...end_index].each do |line|
125
- trimmed_line = line.strip
126
-
127
- if trimmed_line.start_with?('```')
128
- if trimmed_line == '```markdown'
129
- markdown_block_depth += 1
130
- elsif markdown_block_depth > 0
131
- markdown_block_depth -= 1
132
- end
133
- end
134
-
135
- if markdown_block_depth > 0 || !trimmed_line.start_with?(COMMENT_SIGNAL)
136
- filtered_lines << line
137
- end
138
- end
139
-
140
- filtered_lines.join("\n")
141
- end
142
-
143
-
144
-
145
-
146
- def substitute_values(input_text, values_hash)
147
- if values_hash.is_a?(Hash) && !values_hash.empty?
148
- values_hash.each do |key, value|
149
- value = value.last if value.is_a?(Array)
150
- input_text = input_text.gsub(key, value.nil? ? '' : value)
151
- end
152
- end
153
- input_text
154
- end
155
-
156
- def erb? = @erb_flag
157
- def envar? = @envar_flag
158
-
159
- def substitute_env_vars(input_text)
160
- return input_text unless envar?
161
-
162
- # First, handle shell command substitution $(command)
163
- input_text = input_text.gsub(/\$\(([^\)]+)\)/) do |match|
164
- cmd = $1.strip
165
- begin
166
- # Execute the shell command and capture its output
167
- result = `#{cmd}`.chomp
168
- result.empty? ? match : result
169
- rescue => e
170
- # If command execution fails, log the error and keep the original text
171
- warn "Shell command execution failed: #{e.message}"
172
- match
173
- end
174
- end
175
-
176
- # Then handle environment variables as before
177
- input_text.gsub(/\$(\w+)|\$\{(\w+)\}/) do |match|
178
- env_var = $1 || $2
179
- ENV[env_var] || match
180
- end
181
- end
182
-
183
- def process_directives(input_text)
184
- directive_lines = input_text.split("\n").select { |line| line.strip.start_with?(DIRECTIVE_SIGNAL) }
185
- @directives = directive_lines.each_with_object({}) { |line, hash| hash[line.strip] = "" }
186
- @directives = @directives_processor.run(@directives)
187
- substitute_values(input_text, @directives)
188
- end
189
-
190
- def process_erb(input_text)
191
- return input_text unless erb?
192
-
193
- ERB.new(input_text).result(@external_binding)
194
- end
195
- end
@@ -1,157 +0,0 @@
1
- # prompt_manager/lib/prompt_manager/storage/active_record_adapter.rb
2
-
3
- # This class acts as an adapter for interacting with an ActiveRecord model
4
- require 'active_record'
5
- # to manage storage operations for PromptManager::Prompt instances. It defines
6
- # methods that allow for saving, searching, retrieving by ID, and deleting
7
- # prompts.
8
- #
9
- # To use this adapter, you must configure it with an ActiveRecord model and
10
- # the column names for ID, text content, and parameters. The adapter will
11
- # handle serialization and deserialization of parameters.
12
- #
13
- # This adapter is used by PromptManager::Prompt as its storage backend, enabling CRUD operations on persistent prompt data.
14
-
15
- class PromptManager::Storage::ActiveRecordAdapter
16
-
17
- class << self
18
- # Configure the ActiveRecord model and column mappings
19
- attr_accessor :model,
20
- :id_column,
21
- :text_column,
22
- :parameters_column
23
-
24
- # Configure the adapter with the required settings
25
- # Must be called with a block before using the adapter
26
- def config
27
- if block_given?
28
- yield self
29
- validate_configuration
30
- else
31
- raise ArgumentError, "No block given to config"
32
- end
33
-
34
- self
35
- end
36
-
37
- # Validate that all required configuration is present and valid
38
- def validate_configuration
39
- validate_model
40
- validate_columns
41
- end
42
-
43
- # Ensure the provided model is a valid ActiveRecord model
44
- def validate_model
45
- raise ArgumentError, "AR Model not set" unless model
46
- raise ArgumentError, "AR Model is not an ActiveRecord model" unless model < ActiveRecord::Base
47
- end
48
-
49
- # Verify that all required columns exist in the model
50
- def validate_columns
51
- columns = model.column_names # Array of Strings
52
- [id_column, text_column, parameters_column].each do |column|
53
- raise ArgumentError, "#{column} is not a valid column for model #{model}" unless columns.include?(column.to_s)
54
- end
55
- end
56
-
57
- # Delegate unknown methods to the ActiveRecord model
58
- def method_missing(method_name, *args, &block)
59
- if model.respond_to?(method_name)
60
- model.send(method_name, *args, &block)
61
- else
62
- super
63
- end
64
- end
65
-
66
- # Support respond_to? for delegated methods
67
- def respond_to_missing?(method_name, include_private = false)
68
- model.respond_to?(method_name, include_private) || super
69
- end
70
- end
71
-
72
-
73
- ##############################################
74
- # The ActiveRecord object representing the current prompt
75
- attr_accessor :record
76
-
77
- # Accessor methods to avoid repeated self.class prefixes
78
- def model = self.class.model
79
- def id_column = self.class.id_column
80
- def text_column = self.class.text_column
81
- def parameters_column = self.class.parameters_column
82
-
83
- # Initialize the adapter and validate configuration
84
- def initialize
85
- self.class.send(:validate_configuration) # send gets around private designations of a method
86
- @record = model.first
87
- end
88
-
89
- # Retrieve a prompt by its ID
90
- # Returns a hash with id, text, and parameters
91
- def get(id:)
92
- @record = model.find_by(id_column => id)
93
- raise ArgumentError, "Prompt not found with id: #{id}" unless @record
94
-
95
- # Handle case where parameters might be stored as a JSON string
96
- # instead of a native Hash
97
- parameters = @record[parameters_column]
98
-
99
- if parameters.is_a? String
100
- parameters = JSON.parse parameters
101
- end
102
-
103
- {
104
- id: id,
105
- text: @record[text_column],
106
- parameters: parameters
107
- }
108
- end
109
-
110
- # Save a prompt with the given ID, text, and parameters
111
- # Creates a new record if one doesn't exist, otherwise updates existing record
112
- def save(id:, text: "", parameters: {})
113
- @record = model.find_or_initialize_by(id_column => id)
114
-
115
- @record[text_column] = text
116
- @record[parameters_column] = parameters
117
- @record.save!
118
- end
119
-
120
- # Delete a prompt with the given ID
121
- def delete(id:)
122
- @record = model.find_by(id_column => id)
123
- @record&.destroy
124
- end
125
-
126
- # Return an array of all prompt IDs
127
- def list(*)
128
- model.all.pluck(id_column)
129
- end
130
-
131
- # Search for prompts containing the given text
132
- # Returns an array of matching prompt IDs
133
- def search(for_what)
134
- model.where("#{text_column} LIKE ?", "%#{for_what}%").pluck(id_column)
135
- end
136
-
137
- ##############################################
138
- private
139
-
140
- # Delegate unknown methods to the current record
141
- def method_missing(method_name, *args, &block)
142
- if @record && @record.respond_to?(method_name)
143
- @record.send(method_name, *args, &block)
144
- elsif model.respond_to?(method_name)
145
- model.send(method_name, *args, &block)
146
- else
147
- super
148
- end
149
- end
150
-
151
- # Support respond_to? for delegated methods
152
- def respond_to_missing?(method_name, include_private = false)
153
- (model.respond_to?(method_name, include_private) ||
154
- (@record && @record.respond_to?(method_name, include_private)) ||
155
- super)
156
- end
157
- end