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
|
@@ -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
|