aidp 0.32.0 → 0.33.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/lib/aidp/analyze/feature_analyzer.rb +322 -320
- data/lib/aidp/auto_update/coordinator.rb +97 -7
- data/lib/aidp/auto_update.rb +0 -12
- data/lib/aidp/cli/devcontainer_commands.rb +0 -5
- data/lib/aidp/cli.rb +2 -1
- data/lib/aidp/comment_consolidator.rb +78 -0
- data/lib/aidp/concurrency.rb +0 -3
- data/lib/aidp/config.rb +0 -1
- data/lib/aidp/config_paths.rb +71 -0
- data/lib/aidp/execute/work_loop_runner.rb +324 -15
- data/lib/aidp/harness/ai_filter_factory.rb +285 -0
- data/lib/aidp/harness/config_schema.rb +97 -1
- data/lib/aidp/harness/config_validator.rb +1 -1
- data/lib/aidp/harness/configuration.rb +61 -5
- data/lib/aidp/harness/filter_definition.rb +212 -0
- data/lib/aidp/harness/generated_filter_strategy.rb +197 -0
- data/lib/aidp/harness/output_filter.rb +50 -25
- data/lib/aidp/harness/output_filter_config.rb +129 -0
- data/lib/aidp/harness/provider_manager.rb +90 -2
- data/lib/aidp/harness/runner.rb +0 -11
- data/lib/aidp/harness/test_runner.rb +179 -41
- data/lib/aidp/harness/thinking_depth_manager.rb +16 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +0 -2
- data/lib/aidp/loader.rb +195 -0
- data/lib/aidp/metadata/compiler.rb +29 -17
- data/lib/aidp/metadata/query.rb +1 -1
- data/lib/aidp/metadata/scanner.rb +8 -1
- data/lib/aidp/metadata/tool_metadata.rb +13 -13
- data/lib/aidp/metadata/validator.rb +10 -0
- data/lib/aidp/metadata.rb +16 -0
- data/lib/aidp/pr_worktree_manager.rb +2 -2
- data/lib/aidp/provider_manager.rb +1 -7
- data/lib/aidp/setup/wizard.rb +279 -9
- data/lib/aidp/skills.rb +0 -5
- data/lib/aidp/storage/csv_storage.rb +3 -0
- data/lib/aidp/style_guide/selector.rb +360 -0
- data/lib/aidp/tooling_detector.rb +283 -16
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/change_request_processor.rb +152 -14
- data/lib/aidp/watch/repository_client.rb +41 -0
- data/lib/aidp/watch/runner.rb +29 -18
- data/lib/aidp/watch.rb +5 -7
- data/lib/aidp/workstream_cleanup.rb +0 -2
- data/lib/aidp/workstream_executor.rb +0 -4
- data/lib/aidp/worktree.rb +0 -1
- data/lib/aidp.rb +21 -106
- metadata +72 -36
- data/lib/aidp/config/paths.rb +0 -131
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aidp
|
|
4
|
+
module StyleGuide
|
|
5
|
+
# Deterministic selector for STYLE_GUIDE sections based on task context
|
|
6
|
+
#
|
|
7
|
+
# This class provides intelligent section selection from STYLE_GUIDE.md
|
|
8
|
+
# based on keywords in the task context. For providers with their own
|
|
9
|
+
# instruction files (Claude, GitHub Copilot), it skips style guide injection.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic usage
|
|
12
|
+
# selector = Selector.new(project_dir: "/path/to/project")
|
|
13
|
+
# content = selector.select_sections(keywords: ["testing", "error"])
|
|
14
|
+
#
|
|
15
|
+
# @example Check if provider needs style guide
|
|
16
|
+
# selector.provider_needs_style_guide?("cursor") # => true
|
|
17
|
+
# selector.provider_needs_style_guide?("claude") # => false
|
|
18
|
+
class Selector
|
|
19
|
+
# Providers that have their own instruction files and don't need
|
|
20
|
+
# the style guide injected into prompts
|
|
21
|
+
PROVIDERS_WITH_INSTRUCTION_FILES = %w[
|
|
22
|
+
claude
|
|
23
|
+
anthropic
|
|
24
|
+
github_copilot
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
# Mapping of keywords/topics to STYLE_GUIDE.md line ranges
|
|
28
|
+
# Each entry is [start_line, end_line, description]
|
|
29
|
+
SECTION_MAPPING = {
|
|
30
|
+
# Core Engineering
|
|
31
|
+
"code_organization" => [[25, 117, "Code Organization"]],
|
|
32
|
+
"class" => [[25, 117, "Code Organization"], [217, 236, "Sandi Metz Rules"]],
|
|
33
|
+
"method" => [[224, 229, "Method Design"], [217, 236, "Sandi Metz Rules"]],
|
|
34
|
+
"composition" => [[29, 35, "Composition over Inheritance"]],
|
|
35
|
+
"inheritance" => [[29, 35, "Composition over Inheritance"]],
|
|
36
|
+
"small_objects" => [[25, 117, "Code Organization"]],
|
|
37
|
+
"single_responsibility" => [[25, 117, "Code Organization"]],
|
|
38
|
+
|
|
39
|
+
# Sandi Metz
|
|
40
|
+
"sandi_metz" => [[217, 236, "Sandi Metz Rules"]],
|
|
41
|
+
"parameter" => [[231, 236, "Parameter Limits"]],
|
|
42
|
+
|
|
43
|
+
# Ruby Conventions
|
|
44
|
+
"ruby" => [[259, 278, "Ruby Conventions"], [446, 498, "Ruby Version Management"]],
|
|
45
|
+
"naming" => [[51, 56, "Naming Conventions"]],
|
|
46
|
+
"convention" => [[259, 278, "Ruby Conventions"]],
|
|
47
|
+
"style" => [[259, 278, "Ruby Conventions"]],
|
|
48
|
+
"require" => [[263, 266, "Require Practices"]],
|
|
49
|
+
"mise" => [[446, 498, "Ruby Version Management"]],
|
|
50
|
+
"version" => [[446, 498, "Ruby Version Management"]],
|
|
51
|
+
|
|
52
|
+
# Feature Organization
|
|
53
|
+
"feature" => [[58, 116, "Feature Organization by Purpose"]],
|
|
54
|
+
"workflow" => [[58, 116, "Feature Organization by Purpose"]],
|
|
55
|
+
"template" => [[118, 210, "Template/Skill Separation"]],
|
|
56
|
+
"skill" => [[118, 210, "Template/Skill Separation"]],
|
|
57
|
+
|
|
58
|
+
# Logging
|
|
59
|
+
"logging" => [[287, 430, "Logging Practices"]],
|
|
60
|
+
"log" => [[287, 430, "Logging Practices"]],
|
|
61
|
+
"debug" => [[287, 430, "Logging Practices"]],
|
|
62
|
+
|
|
63
|
+
# ZFC - Zero Framework Cognition
|
|
64
|
+
"zfc" => [[500, 797, "Zero Framework Cognition (ZFC)"]],
|
|
65
|
+
"zero_framework" => [[500, 797, "Zero Framework Cognition (ZFC)"]],
|
|
66
|
+
"ai_decision" => [[500, 797, "Zero Framework Cognition (ZFC)"]],
|
|
67
|
+
"decision_engine" => [[500, 797, "Zero Framework Cognition (ZFC)"]],
|
|
68
|
+
|
|
69
|
+
# AGD - AI-Generated Determinism
|
|
70
|
+
"agd" => [[798, 855, "AI-Generated Determinism (AGD)"]],
|
|
71
|
+
"determinism" => [[798, 855, "AI-Generated Determinism (AGD)"]],
|
|
72
|
+
"config_time" => [[798, 855, "AI-Generated Determinism (AGD)"]],
|
|
73
|
+
|
|
74
|
+
# TTY / TUI
|
|
75
|
+
"tty" => [[856, 1105, "TTY Toolkit Guidelines"]],
|
|
76
|
+
"tui" => [[856, 1105, "TTY Toolkit Guidelines"]],
|
|
77
|
+
"ui" => [[856, 1105, "TTY Toolkit Guidelines"]],
|
|
78
|
+
"prompt" => [[856, 1105, "TTY Toolkit Guidelines"], [936, 972, "TTY Output Practices"]],
|
|
79
|
+
"progress" => [[856, 1105, "TTY Toolkit Guidelines"]],
|
|
80
|
+
"spinner" => [[856, 1105, "TTY Toolkit Guidelines"]],
|
|
81
|
+
"table" => [[856, 1105, "TTY Toolkit Guidelines"]],
|
|
82
|
+
"select" => [[856, 1105, "TTY Toolkit Guidelines"]],
|
|
83
|
+
|
|
84
|
+
# Testing
|
|
85
|
+
"testing" => [[1873, 2112, "Testing Guidelines"]],
|
|
86
|
+
"test" => [[1873, 2112, "Testing Guidelines"], [1550, 1800, "Test Coverage Philosophy"]],
|
|
87
|
+
"spec" => [[1873, 2112, "Testing Guidelines"]],
|
|
88
|
+
"rspec" => [[1873, 2112, "Testing Guidelines"]],
|
|
89
|
+
"mock" => [[1873, 2112, "Testing Guidelines"], [1930, 2012, "Dependency Injection"]],
|
|
90
|
+
"stub" => [[1873, 2112, "Testing Guidelines"]],
|
|
91
|
+
"coverage" => [[1550, 1800, "Test Coverage Philosophy"]],
|
|
92
|
+
"pending" => [[1816, 1870, "Pending Specs Policy"]],
|
|
93
|
+
"expect_script" => [[1173, 1234, "expect Scripts for TUI"]],
|
|
94
|
+
"tmux" => [[1203, 1295, "tmux Testing"]],
|
|
95
|
+
"time_test" => [[1656, 1690, "Time-based Testing"]],
|
|
96
|
+
"fork" => [[1718, 1778, "Forked Process Testing"]],
|
|
97
|
+
"encoding" => [[1780, 1813, "String Encoding"]],
|
|
98
|
+
"dependency_injection" => [[1930, 2012, "Dependency Injection"]],
|
|
99
|
+
|
|
100
|
+
# Error Handling
|
|
101
|
+
"error" => [[2113, 2168, "Error Handling"], [280, 286, "Error Handling Basics"]],
|
|
102
|
+
"exception" => [[2113, 2168, "Error Handling"], [2130, 2140, "Error Class Pattern"]],
|
|
103
|
+
"rescue" => [[280, 286, "Error Handling Basics"], [2113, 2168, "Error Handling"]],
|
|
104
|
+
|
|
105
|
+
# Concurrency
|
|
106
|
+
"concurrency" => [[2170, 2185, "Concurrency & Threads"]],
|
|
107
|
+
"thread" => [[2170, 2185, "Concurrency & Threads"]],
|
|
108
|
+
"async" => [[2170, 2185, "Concurrency & Threads"]],
|
|
109
|
+
|
|
110
|
+
# Performance
|
|
111
|
+
"performance" => [[2206, 2232, "Performance"]],
|
|
112
|
+
"optimization" => [[2206, 2232, "Performance"]],
|
|
113
|
+
"cache" => [[2206, 2232, "Performance"]],
|
|
114
|
+
|
|
115
|
+
# Security
|
|
116
|
+
"security" => [[2233, 2272, "Security & Safety"]],
|
|
117
|
+
"safety" => [[2233, 2272, "Security & Safety"]],
|
|
118
|
+
"validation" => [[2233, 2272, "Security & Safety"]],
|
|
119
|
+
|
|
120
|
+
# Backward Compatibility / Pre-release
|
|
121
|
+
"backward_compatibility" => [[2273, 2472, "Backward Compatibility"]],
|
|
122
|
+
"deprecation" => [[2273, 2472, "Backward Compatibility"]],
|
|
123
|
+
"legacy" => [[2273, 2472, "Backward Compatibility"]],
|
|
124
|
+
|
|
125
|
+
# Commit Hygiene
|
|
126
|
+
"commit" => [[2476, 2482, "Commit Hygiene"]],
|
|
127
|
+
"git" => [[2476, 2482, "Commit Hygiene"]],
|
|
128
|
+
|
|
129
|
+
# Prompt Optimization
|
|
130
|
+
"prompt_optimization" => [[2523, 2854, "Prompt Optimization"]],
|
|
131
|
+
"fragment" => [[2523, 2854, "Prompt Optimization"]],
|
|
132
|
+
|
|
133
|
+
# Task Filing
|
|
134
|
+
"task" => [[2856, 2890, "Task Filing"]],
|
|
135
|
+
"tasklist" => [[2856, 2890, "Task Filing"]]
|
|
136
|
+
}.freeze
|
|
137
|
+
|
|
138
|
+
# Default sections to always include (core rules)
|
|
139
|
+
CORE_SECTIONS = [
|
|
140
|
+
[25, 117, "Code Organization"],
|
|
141
|
+
[217, 236, "Sandi Metz Rules"],
|
|
142
|
+
[287, 430, "Logging Practices"]
|
|
143
|
+
].freeze
|
|
144
|
+
|
|
145
|
+
attr_reader :project_dir
|
|
146
|
+
|
|
147
|
+
def initialize(project_dir:)
|
|
148
|
+
@project_dir = project_dir
|
|
149
|
+
@style_guide_content = nil
|
|
150
|
+
@style_guide_lines = nil
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Check if a provider needs style guide injection
|
|
154
|
+
#
|
|
155
|
+
# @param provider_name [String] Name of the provider
|
|
156
|
+
# @return [Boolean] true if style guide should be injected
|
|
157
|
+
def provider_needs_style_guide?(provider_name)
|
|
158
|
+
return true if provider_name.nil?
|
|
159
|
+
|
|
160
|
+
normalized = provider_name.to_s.downcase.strip
|
|
161
|
+
!PROVIDERS_WITH_INSTRUCTION_FILES.include?(normalized)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Select relevant sections from STYLE_GUIDE.md based on keywords
|
|
165
|
+
#
|
|
166
|
+
# @param keywords [Array<String>] Keywords to match against
|
|
167
|
+
# @param include_core [Boolean] Whether to include core sections
|
|
168
|
+
# @param max_lines [Integer, nil] Maximum lines to return (nil for unlimited)
|
|
169
|
+
# @return [String] Combined content of selected sections
|
|
170
|
+
def select_sections(keywords: [], include_core: true, max_lines: nil)
|
|
171
|
+
Aidp.log_debug("style_guide_selector", "selecting_sections",
|
|
172
|
+
keywords: keywords, include_core: include_core, max_lines: max_lines)
|
|
173
|
+
|
|
174
|
+
return "" unless style_guide_exists?
|
|
175
|
+
|
|
176
|
+
# Gather all matching sections
|
|
177
|
+
sections = gather_sections(keywords, include_core)
|
|
178
|
+
|
|
179
|
+
# Merge overlapping ranges and sort
|
|
180
|
+
merged_ranges = merge_and_sort_ranges(sections)
|
|
181
|
+
|
|
182
|
+
# Extract content from ranges
|
|
183
|
+
content = extract_content(merged_ranges, max_lines)
|
|
184
|
+
|
|
185
|
+
Aidp.log_debug("style_guide_selector", "sections_selected",
|
|
186
|
+
section_count: merged_ranges.size, content_lines: content.lines.count)
|
|
187
|
+
|
|
188
|
+
content
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Extract keywords from task context
|
|
192
|
+
#
|
|
193
|
+
# @param context [Hash, String] Task context (description, affected files, etc.)
|
|
194
|
+
# @return [Array<String>] Extracted keywords
|
|
195
|
+
def extract_keywords(context)
|
|
196
|
+
text = context.is_a?(Hash) ? context.values.join(" ") : context.to_s
|
|
197
|
+
text_lower = text.downcase
|
|
198
|
+
|
|
199
|
+
keywords = []
|
|
200
|
+
|
|
201
|
+
SECTION_MAPPING.keys.each do |keyword|
|
|
202
|
+
# Convert keyword format (snake_case to spaces/variations)
|
|
203
|
+
patterns = build_patterns(keyword)
|
|
204
|
+
keywords << keyword if patterns.any? { |p| text_lower.include?(p) }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
Aidp.log_debug("style_guide_selector", "keywords_extracted",
|
|
208
|
+
input_length: text.length, keywords_found: keywords.size)
|
|
209
|
+
|
|
210
|
+
keywords.uniq
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Get all available section names
|
|
214
|
+
#
|
|
215
|
+
# @return [Array<String>] List of all section mapping keys
|
|
216
|
+
def available_keywords
|
|
217
|
+
SECTION_MAPPING.keys.sort
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Check if style guide file exists
|
|
221
|
+
#
|
|
222
|
+
# @return [Boolean]
|
|
223
|
+
def style_guide_exists?
|
|
224
|
+
File.exist?(style_guide_path)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Get information about what sections would be selected for given keywords
|
|
228
|
+
#
|
|
229
|
+
# @param keywords [Array<String>] Keywords to check
|
|
230
|
+
# @return [Array<Hash>] Section info with line ranges and descriptions
|
|
231
|
+
def preview_selection(keywords)
|
|
232
|
+
sections = gather_sections(keywords, false)
|
|
233
|
+
merged = merge_and_sort_ranges(sections)
|
|
234
|
+
|
|
235
|
+
merged.map do |start_line, end_line, description|
|
|
236
|
+
{
|
|
237
|
+
start_line: start_line,
|
|
238
|
+
end_line: end_line,
|
|
239
|
+
description: description,
|
|
240
|
+
estimated_lines: end_line - start_line + 1
|
|
241
|
+
}
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
private
|
|
246
|
+
|
|
247
|
+
def style_guide_path
|
|
248
|
+
File.join(@project_dir, "docs", "STYLE_GUIDE.md")
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def style_guide_lines
|
|
252
|
+
@style_guide_lines ||= begin
|
|
253
|
+
return [] unless style_guide_exists?
|
|
254
|
+
|
|
255
|
+
content = File.read(style_guide_path, encoding: "UTF-8")
|
|
256
|
+
content = content.encode("UTF-8", invalid: :replace, undef: :replace)
|
|
257
|
+
content.lines
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def gather_sections(keywords, include_core)
|
|
262
|
+
sections = []
|
|
263
|
+
|
|
264
|
+
# Add core sections if requested
|
|
265
|
+
sections.concat(CORE_SECTIONS.dup) if include_core
|
|
266
|
+
|
|
267
|
+
# Add sections matching keywords
|
|
268
|
+
keywords.each do |keyword|
|
|
269
|
+
normalized = keyword.to_s.downcase.gsub(/[^a-z0-9]/, "_")
|
|
270
|
+
if SECTION_MAPPING.key?(normalized)
|
|
271
|
+
sections.concat(SECTION_MAPPING[normalized])
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
sections
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def merge_and_sort_ranges(sections)
|
|
279
|
+
return [] if sections.empty?
|
|
280
|
+
|
|
281
|
+
# Convert to sortable format and sort by start line
|
|
282
|
+
ranges = sections.map { |start_l, end_l, desc| [start_l.to_i, end_l.to_i, desc] }
|
|
283
|
+
ranges.sort_by!(&:first)
|
|
284
|
+
|
|
285
|
+
# Merge overlapping/adjacent ranges
|
|
286
|
+
merged = []
|
|
287
|
+
current_start, current_end, current_desc = ranges.first
|
|
288
|
+
|
|
289
|
+
ranges[1..].each do |start_l, end_l, desc|
|
|
290
|
+
if start_l <= current_end + 5 # Allow small gaps (5 lines)
|
|
291
|
+
# Extend current range
|
|
292
|
+
current_end = [current_end, end_l].max
|
|
293
|
+
current_desc = "#{current_desc}, #{desc}" unless current_desc.include?(desc)
|
|
294
|
+
else
|
|
295
|
+
# Save current range and start new one
|
|
296
|
+
merged << [current_start, current_end, current_desc]
|
|
297
|
+
current_start = start_l
|
|
298
|
+
current_end = end_l
|
|
299
|
+
current_desc = desc
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Don't forget the last range
|
|
304
|
+
merged << [current_start, current_end, current_desc]
|
|
305
|
+
|
|
306
|
+
merged
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def extract_content(ranges, max_lines)
|
|
310
|
+
return "" if ranges.empty?
|
|
311
|
+
|
|
312
|
+
lines = style_guide_lines
|
|
313
|
+
return "" if lines.empty?
|
|
314
|
+
|
|
315
|
+
parts = []
|
|
316
|
+
total_lines = 0
|
|
317
|
+
|
|
318
|
+
ranges.each do |start_line, end_line, description|
|
|
319
|
+
break if max_lines && total_lines >= max_lines
|
|
320
|
+
|
|
321
|
+
# Adjust for 0-based indexing
|
|
322
|
+
start_idx = [start_line - 1, 0].max
|
|
323
|
+
end_idx = [end_line - 1, lines.length - 1].min
|
|
324
|
+
|
|
325
|
+
section_lines = lines[start_idx..end_idx]
|
|
326
|
+
next if section_lines.nil? || section_lines.empty?
|
|
327
|
+
|
|
328
|
+
# Add section header comment
|
|
329
|
+
parts << "<!-- Section: #{description} (lines #{start_line}-#{end_line}) -->"
|
|
330
|
+
parts << section_lines.join
|
|
331
|
+
|
|
332
|
+
total_lines += section_lines.length
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
content = parts.join("\n")
|
|
336
|
+
|
|
337
|
+
# Trim to max_lines if specified
|
|
338
|
+
if max_lines && content.lines.count > max_lines
|
|
339
|
+
content = content.lines.first(max_lines).join
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
content
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def build_patterns(keyword)
|
|
346
|
+
patterns = [keyword]
|
|
347
|
+
|
|
348
|
+
# Add variations
|
|
349
|
+
patterns << keyword.tr("_", " ")
|
|
350
|
+
patterns << keyword.tr("_", "-")
|
|
351
|
+
|
|
352
|
+
# Add singular/plural variations for common terms
|
|
353
|
+
patterns << "#{keyword}s" unless keyword.end_with?("s")
|
|
354
|
+
patterns << keyword.chomp("s") if keyword.end_with?("s")
|
|
355
|
+
|
|
356
|
+
patterns.uniq
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
end
|