aidp 0.17.1 → 0.18.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +69 -0
  3. data/lib/aidp/cli.rb +43 -2
  4. data/lib/aidp/config.rb +9 -14
  5. data/lib/aidp/execute/prompt_manager.rb +128 -1
  6. data/lib/aidp/execute/repl_macros.rb +555 -0
  7. data/lib/aidp/execute/work_loop_runner.rb +108 -1
  8. data/lib/aidp/harness/ai_decision_engine.rb +376 -0
  9. data/lib/aidp/harness/capability_registry.rb +273 -0
  10. data/lib/aidp/harness/config_schema.rb +305 -1
  11. data/lib/aidp/harness/configuration.rb +452 -0
  12. data/lib/aidp/harness/enhanced_runner.rb +7 -1
  13. data/lib/aidp/harness/provider_factory.rb +0 -2
  14. data/lib/aidp/harness/runner.rb +7 -1
  15. data/lib/aidp/harness/thinking_depth_manager.rb +335 -0
  16. data/lib/aidp/harness/zfc_condition_detector.rb +395 -0
  17. data/lib/aidp/init/devcontainer_generator.rb +274 -0
  18. data/lib/aidp/init/runner.rb +37 -10
  19. data/lib/aidp/init.rb +1 -0
  20. data/lib/aidp/prompt_optimization/context_composer.rb +286 -0
  21. data/lib/aidp/prompt_optimization/optimizer.rb +335 -0
  22. data/lib/aidp/prompt_optimization/prompt_builder.rb +309 -0
  23. data/lib/aidp/prompt_optimization/relevance_scorer.rb +256 -0
  24. data/lib/aidp/prompt_optimization/source_code_fragmenter.rb +308 -0
  25. data/lib/aidp/prompt_optimization/style_guide_indexer.rb +240 -0
  26. data/lib/aidp/prompt_optimization/template_indexer.rb +250 -0
  27. data/lib/aidp/provider_manager.rb +0 -2
  28. data/lib/aidp/providers/anthropic.rb +19 -0
  29. data/lib/aidp/setup/wizard.rb +299 -4
  30. data/lib/aidp/utils/devcontainer_detector.rb +166 -0
  31. data/lib/aidp/version.rb +1 -1
  32. data/lib/aidp/watch/build_processor.rb +72 -6
  33. data/lib/aidp/watch/repository_client.rb +2 -1
  34. data/lib/aidp.rb +0 -1
  35. data/templates/aidp.yml.example +128 -0
  36. metadata +14 -2
  37. data/lib/aidp/providers/macos_ui.rb +0 -102
@@ -0,0 +1,395 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "condition_detector"
4
+ require_relative "ai_decision_engine"
5
+
6
+ module Aidp
7
+ module Harness
8
+ # ZFC-enabled wrapper for ConditionDetector
9
+ #
10
+ # Delegates semantic analysis to AI when ZFC is enabled, falls back to
11
+ # legacy pattern matching when disabled or on AI failure.
12
+ #
13
+ # @example Basic usage
14
+ # detector = ZfcConditionDetector.new(config, provider_factory)
15
+ # if detector.is_rate_limited?(result)
16
+ # # Handle rate limit
17
+ # end
18
+ #
19
+ # @see docs/ZFC_COMPLIANCE_ASSESSMENT.md
20
+ # @see docs/ZFC_IMPLEMENTATION_PLAN.md
21
+ class ZfcConditionDetector
22
+ attr_reader :config, :legacy_detector, :ai_engine, :stats
23
+
24
+ # Initialize ZFC condition detector
25
+ #
26
+ # @param config [Configuration, ConfigManager] AIDP configuration
27
+ # @param provider_factory [ProviderFactory, nil] Optional factory for creating providers
28
+ def initialize(config, provider_factory: nil)
29
+ @config = config
30
+ @legacy_detector = ConditionDetector.new
31
+
32
+ # Create ProviderFactory if not provided and ZFC is enabled
33
+ # Note: ConfigManager doesn't have zfc_enabled?, so we check respond_to? first
34
+ if provider_factory.nil? && config.respond_to?(:zfc_enabled?) && config.zfc_enabled?
35
+ require_relative "provider_factory"
36
+ provider_factory = ProviderFactory.new
37
+ end
38
+
39
+ @ai_engine = AIDecisionEngine.new(config, provider_factory: provider_factory)
40
+
41
+ # Statistics for A/B testing
42
+ @stats = {
43
+ zfc_calls: 0,
44
+ legacy_calls: 0,
45
+ zfc_fallbacks: 0,
46
+ agreements: 0,
47
+ disagreements: 0,
48
+ zfc_total_cost: 0.0
49
+ }
50
+ end
51
+
52
+ # Check if result indicates rate limiting
53
+ #
54
+ # @param result [Hash] AI response or error
55
+ # @param provider [String, nil] Provider name for context
56
+ # @return [Boolean] true if rate limited
57
+ def is_rate_limited?(result, provider = nil)
58
+ detect_condition(:is_rate_limited?, result, provider: provider) do |ai_result|
59
+ ai_result[:condition] == "rate_limit" &&
60
+ ai_result[:confidence] >= confidence_threshold(:condition_detection)
61
+ end
62
+ end
63
+
64
+ # Check if result needs user feedback
65
+ #
66
+ # @param result [Hash] AI response
67
+ # @return [Boolean] true if user feedback needed
68
+ def needs_user_feedback?(result)
69
+ detect_condition(:needs_user_feedback?, result, provider: nil) do |ai_result|
70
+ ai_result[:condition] == "user_feedback_needed" &&
71
+ ai_result[:confidence] >= confidence_threshold(:condition_detection)
72
+ end
73
+ end
74
+
75
+ # Check if work is complete
76
+ #
77
+ # @param result [Hash] AI response
78
+ # @param progress [Hash, nil] Progress context
79
+ # @return [Boolean] true if work complete
80
+ def is_work_complete?(result, progress = nil)
81
+ return false unless result
82
+
83
+ if zfc_enabled?(:completion_detection)
84
+ begin
85
+ # Build context for AI decision
86
+ context = {
87
+ response: result_to_text(result),
88
+ task_description: progress&.dig(:task) || "general task"
89
+ }
90
+
91
+ # Ask AI if work is complete
92
+ ai_result = @ai_engine.decide(:completion_detection,
93
+ context: context,
94
+ tier: zfc_tier(:completion_detection),
95
+ cache_ttl: zfc_cache_ttl(:completion_detection))
96
+
97
+ record_zfc_call(:completion_detection, ai_result)
98
+
99
+ # A/B test if enabled
100
+ if ab_testing_enabled?
101
+ legacy_result = @legacy_detector.is_work_complete?(result, progress)
102
+ compare_results(:is_work_complete, ai_result[:complete], legacy_result)
103
+ end
104
+
105
+ ai_result[:complete] &&
106
+ ai_result[:confidence] >= confidence_threshold(:completion_detection)
107
+ rescue => e
108
+ Aidp.log_error("zfc_condition_detector", "ZFC completion detection failed, falling back to legacy", {
109
+ error: e.message,
110
+ error_class: e.class.name
111
+ })
112
+ record_fallback(:completion_detection)
113
+ @legacy_detector.is_work_complete?(result, progress)
114
+ end
115
+ else
116
+ @stats[:legacy_calls] += 1
117
+ @legacy_detector.is_work_complete?(result, progress)
118
+ end
119
+ end
120
+
121
+ # Extract questions from result (delegates to legacy for now)
122
+ #
123
+ # @param result [Hash] AI response
124
+ # @return [Array<Hash>] List of questions
125
+ def extract_questions(result)
126
+ @legacy_detector.extract_questions(result)
127
+ end
128
+
129
+ # Extract rate limit info (delegates to legacy for now)
130
+ #
131
+ # @param result [Hash] AI response or error
132
+ # @param provider [String, nil] Provider name
133
+ # @return [Hash] Rate limit information
134
+ def extract_rate_limit_info(result, provider = nil)
135
+ @legacy_detector.extract_rate_limit_info(result, provider)
136
+ end
137
+
138
+ # Classify error using AI or legacy pattern matching
139
+ #
140
+ # @param error [Exception, StandardError] Error to classify
141
+ # @param context [Hash] Additional context (provider, model, etc.)
142
+ # @return [Hash] Classification with error_type, retryable, recommended_action
143
+ def classify_error(error, context = {})
144
+ return @legacy_detector.classify_error(error) unless zfc_enabled?(:error_classification)
145
+
146
+ begin
147
+ # Build context for AI decision
148
+ error_context = {
149
+ error_message: error_to_text(error),
150
+ context: context.to_s
151
+ }
152
+
153
+ # Ask AI to classify error
154
+ ai_result = @ai_engine.decide(:error_classification,
155
+ context: error_context,
156
+ tier: zfc_tier(:error_classification),
157
+ cache_ttl: zfc_cache_ttl(:error_classification))
158
+
159
+ record_zfc_call(:error_classification, ai_result)
160
+
161
+ # A/B test if enabled
162
+ if ab_testing_enabled?
163
+ legacy_result = @legacy_detector.classify_error(error)
164
+ compare_error_results(ai_result, legacy_result)
165
+ end
166
+
167
+ # Only use AI result if confidence is high enough
168
+ if ai_result[:confidence] >= confidence_threshold(:error_classification)
169
+ # Convert AI result to legacy format
170
+ {
171
+ error: error,
172
+ error_type: ai_result[:error_type].to_sym,
173
+ retryable: ai_result[:retryable],
174
+ recommended_action: ai_result[:recommended_action].to_sym,
175
+ confidence: ai_result[:confidence],
176
+ reasoning: ai_result[:reasoning],
177
+ timestamp: Time.now,
178
+ context: context,
179
+ message: error&.message || "Unknown error"
180
+ }
181
+ else
182
+ @legacy_detector.classify_error(error)
183
+ end
184
+ rescue => e
185
+ Aidp.log_error("zfc_condition_detector", "ZFC error classification failed, falling back to legacy", {
186
+ error: e.message,
187
+ error_class: e.class.name,
188
+ original_error: error&.class&.name
189
+ })
190
+ record_fallback(:error_classification)
191
+ @legacy_detector.classify_error(error)
192
+ end
193
+ end
194
+
195
+ # Get statistics summary
196
+ #
197
+ # @return [Hash] Statistics including accuracy, cost, performance
198
+ def statistics
199
+ total_calls = @stats[:zfc_calls] + @stats[:legacy_calls]
200
+ return @stats.merge(total_calls: 0, accuracy: nil) if total_calls.zero?
201
+
202
+ comparisons = @stats[:agreements] + @stats[:disagreements]
203
+ accuracy = comparisons.zero? ? nil : (@stats[:agreements].to_f / comparisons * 100).round(2)
204
+
205
+ @stats.merge(
206
+ total_calls: total_calls,
207
+ zfc_percentage: (@stats[:zfc_calls].to_f / total_calls * 100).round(2),
208
+ accuracy: accuracy,
209
+ fallback_rate: (@stats[:zfc_fallbacks].to_f / [@stats[:zfc_calls], 1].max * 100).round(2)
210
+ )
211
+ end
212
+
213
+ private
214
+
215
+ # Generic condition detection with ZFC/legacy fallback
216
+ def detect_condition(method_name, result, provider: nil)
217
+ return false unless result
218
+
219
+ if zfc_enabled?(:condition_detection)
220
+ begin
221
+ # Build context for AI decision
222
+ context = {
223
+ response: result_to_text(result)
224
+ }
225
+ context[:provider] = provider if provider
226
+
227
+ # Ask AI to classify condition
228
+ ai_result = @ai_engine.decide(:condition_detection,
229
+ context: context,
230
+ tier: zfc_tier(:condition_detection),
231
+ cache_ttl: zfc_cache_ttl(:condition_detection))
232
+
233
+ record_zfc_call(:condition_detection, ai_result)
234
+
235
+ # Convert AI result to boolean using provided block
236
+ zfc_decision = yield(ai_result)
237
+
238
+ # A/B test if enabled
239
+ if ab_testing_enabled?
240
+ # Call legacy method with appropriate arguments
241
+ legacy_decision = call_legacy_method(method_name, result, provider)
242
+ compare_results(method_name, zfc_decision, legacy_decision)
243
+ end
244
+
245
+ zfc_decision
246
+ rescue => e
247
+ Aidp.log_error("zfc_condition_detector", "ZFC condition detection failed, falling back to legacy", {
248
+ error: e.message,
249
+ error_class: e.class.name,
250
+ method: method_name
251
+ })
252
+ record_fallback(:condition_detection)
253
+ call_legacy_method(method_name, result, provider)
254
+ end
255
+ else
256
+ @stats[:legacy_calls] += 1
257
+ call_legacy_method(method_name, result, provider)
258
+ end
259
+ end
260
+
261
+ # Call legacy method with appropriate arguments based on method signature
262
+ def call_legacy_method(method_name, result, provider)
263
+ case method_name
264
+ when :is_rate_limited?
265
+ @legacy_detector.send(method_name, result, provider)
266
+ when :needs_user_feedback?
267
+ @legacy_detector.send(method_name, result)
268
+ else
269
+ @legacy_detector.send(method_name, result, provider)
270
+ end
271
+ end
272
+
273
+ # Convert result to text for AI analysis
274
+ def result_to_text(result)
275
+ case result
276
+ when String
277
+ result
278
+ when Hash
279
+ # Try common keys - match what ConditionDetector uses
280
+ result[:output] || result[:content] || result[:message] || result[:error] || result[:response] || result.to_s
281
+ else
282
+ result.to_s
283
+ end
284
+ end
285
+
286
+ # Convert error to text for AI analysis
287
+ def error_to_text(error)
288
+ return "Unknown error" unless error
289
+
290
+ message = error.message || error.to_s
291
+ error_class = error.class.name
292
+
293
+ # Include error class and message
294
+ text = "#{error_class}: #{message}"
295
+
296
+ # Add backtrace context if available
297
+ if error.backtrace && !error.backtrace.empty?
298
+ text += "\nLocation: #{error.backtrace.first}"
299
+ end
300
+
301
+ text
302
+ end
303
+
304
+ # Check if ZFC is enabled for decision type
305
+ def zfc_enabled?(decision_type)
306
+ return false unless @config.respond_to?(:zfc_decision_enabled?)
307
+ @config.zfc_decision_enabled?(decision_type)
308
+ end
309
+
310
+ # Get tier for ZFC decision type
311
+ def zfc_tier(decision_type)
312
+ return "mini" unless @config.respond_to?(:zfc_decision_tier)
313
+ @config.zfc_decision_tier(decision_type)
314
+ end
315
+
316
+ # Get cache TTL for decision type
317
+ def zfc_cache_ttl(decision_type)
318
+ return nil unless @config.respond_to?(:zfc_decision_cache_ttl)
319
+ @config.zfc_decision_cache_ttl(decision_type)
320
+ end
321
+
322
+ # Get confidence threshold for decision type
323
+ def confidence_threshold(decision_type)
324
+ return 0.7 unless @config.respond_to?(:zfc_decision_confidence_threshold)
325
+ @config.zfc_decision_confidence_threshold(decision_type)
326
+ end
327
+
328
+ # Check if A/B testing is enabled
329
+ def ab_testing_enabled?
330
+ return false unless @config.respond_to?(:zfc_ab_testing_enabled?)
331
+ @config.zfc_ab_testing_enabled?
332
+ end
333
+
334
+ # Record ZFC call for statistics
335
+ def record_zfc_call(decision_type, ai_result)
336
+ @stats[:zfc_calls] += 1
337
+
338
+ # Estimate cost (very rough)
339
+ # Mini tier: ~$0.15/MTok input, $0.75/MTok output
340
+ # Assume ~500 tokens input, ~100 tokens output per decision
341
+ estimated_cost = (500 * 0.15 / 1_000_000) + (100 * 0.75 / 1_000_000)
342
+ @stats[:zfc_total_cost] += estimated_cost
343
+ end
344
+
345
+ # Record fallback to legacy
346
+ def record_fallback(decision_type)
347
+ @stats[:zfc_fallbacks] += 1
348
+ end
349
+
350
+ # Compare ZFC vs legacy results for A/B testing
351
+ def compare_results(method_name, zfc_result, legacy_result)
352
+ if zfc_result == legacy_result
353
+ @stats[:agreements] += 1
354
+ else
355
+ @stats[:disagreements] += 1
356
+
357
+ # Log comparisons if configured (only available in Configuration, not ConfigManager)
358
+ if @config.respond_to?(:zfc_ab_testing_config) &&
359
+ @config.zfc_ab_testing_config[:log_comparisons]
360
+ Aidp.log_debug("zfc_ab_testing", "ZFC vs Legacy disagreement", {
361
+ method: method_name,
362
+ zfc_result: zfc_result,
363
+ legacy_result: legacy_result
364
+ })
365
+ end
366
+ end
367
+ end
368
+
369
+ # Compare ZFC vs legacy error classification results
370
+ def compare_error_results(ai_result, legacy_result)
371
+ # Extract comparable fields
372
+ ai_error_type = ai_result[:error_type].to_sym
373
+ legacy_error_type = legacy_result[:error_type]
374
+
375
+ if ai_error_type == legacy_error_type
376
+ @stats[:agreements] += 1
377
+ else
378
+ @stats[:disagreements] += 1
379
+
380
+ # Log comparisons if configured
381
+ if @config.respond_to?(:zfc_ab_testing_config) &&
382
+ @config.zfc_ab_testing_config[:log_comparisons]
383
+ Aidp.log_debug("zfc_ab_testing", "Error classification disagreement", {
384
+ ai_error_type: ai_error_type,
385
+ legacy_error_type: legacy_error_type,
386
+ ai_retryable: ai_result[:retryable],
387
+ ai_action: ai_result[:recommended_action],
388
+ ai_confidence: ai_result[:confidence]
389
+ })
390
+ end
391
+ end
392
+ end
393
+ end
394
+ end
395
+ end
@@ -0,0 +1,274 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+ require_relative "../message_display"
6
+
7
+ module Aidp
8
+ module Init
9
+ # Generates .devcontainer configuration for projects
10
+ # Provides sandboxed development environment with network security
11
+ #
12
+ # Design Philosophy:
13
+ # - Use project analysis data (already collected by ProjectAnalyzer)
14
+ # - Avoid hardcoded framework/tool assumptions
15
+ # - Prefer templates over code generation
16
+ # - Let the data drive decisions, not hardcoded logic
17
+ class DevcontainerGenerator
18
+ include Aidp::MessageDisplay
19
+
20
+ def initialize(project_dir = Dir.pwd)
21
+ @project_dir = project_dir
22
+ @devcontainer_dir = File.join(@project_dir, ".devcontainer")
23
+ end
24
+
25
+ # Generate devcontainer configuration based on project analysis
26
+ #
27
+ # @param analysis [Hash] Project analysis from ProjectAnalyzer
28
+ # @param preferences [Hash] User preferences for devcontainer setup
29
+ # @return [Array<String>] List of generated files
30
+ def generate(analysis:, preferences: {})
31
+ ensure_directory_exists
32
+
33
+ files = []
34
+ files << generate_dockerfile(analysis, preferences)
35
+ files << generate_devcontainer_json(analysis, preferences)
36
+ files << generate_firewall_script(analysis, preferences)
37
+ files << generate_readme(analysis, preferences)
38
+
39
+ files.compact
40
+ end
41
+
42
+ # Check if devcontainer already exists
43
+ #
44
+ # @return [Boolean] true if .devcontainer directory exists
45
+ def exists?
46
+ Dir.exist?(@devcontainer_dir)
47
+ end
48
+
49
+ private
50
+
51
+ def ensure_directory_exists
52
+ FileUtils.mkdir_p(@devcontainer_dir) unless Dir.exist?(@devcontainer_dir)
53
+ end
54
+
55
+ def generate_dockerfile(analysis, _preferences)
56
+ # Use AIDP's template as the default (Ruby-based)
57
+ # Projects can customize after generation
58
+ template_path = File.join(File.dirname(__FILE__), "..", "..", "..", ".devcontainer", "Dockerfile")
59
+
60
+ content = if File.exist?(template_path)
61
+ File.read(template_path)
62
+ else
63
+ # Fallback to basic Dockerfile if template not found
64
+ generate_basic_dockerfile
65
+ end
66
+
67
+ file_path = File.join(@devcontainer_dir, "Dockerfile")
68
+ File.write(file_path, content)
69
+ file_path
70
+ end
71
+
72
+ def generate_devcontainer_json(_analysis, preferences)
73
+ # Use minimal, universal configuration
74
+ # Users can customize extensions/settings based on their project needs
75
+ config = {
76
+ name: "#{File.basename(@project_dir)} Development Container",
77
+ build: {
78
+ dockerfile: "Dockerfile",
79
+ args: {
80
+ TZ: preferences[:timezone] || "UTC"
81
+ }
82
+ },
83
+ capAdd: ["NET_ADMIN", "NET_RAW"],
84
+ mounts: [
85
+ "source=#{File.basename(@project_dir)}-bashhistory,target=/home/aidp/.bash_history,type=volume",
86
+ "source=#{File.basename(@project_dir)}-aidp,target=/home/aidp/.aidp,type=volume"
87
+ ],
88
+ postStartCommand: "sudo /usr/local/bin/init-firewall.sh",
89
+ remoteUser: "aidp",
90
+ customizations: {
91
+ vscode: {
92
+ # Include only universal, language-agnostic extensions
93
+ # Project-specific extensions should be added by the user
94
+ extensions: [
95
+ "editorconfig.editorconfig", # Respect .editorconfig
96
+ "eamodio.gitlens" # Git integration
97
+ ]
98
+ }
99
+ }
100
+ }
101
+
102
+ file_path = File.join(@devcontainer_dir, "devcontainer.json")
103
+ File.write(file_path, JSON.pretty_generate(config))
104
+ file_path
105
+ end
106
+
107
+ def generate_firewall_script(_analysis, _preferences)
108
+ # Copy template from AIDP repo
109
+ template_path = File.join(File.dirname(__FILE__), "..", "..", "..", ".devcontainer", "init-firewall.sh")
110
+ file_path = File.join(@devcontainer_dir, "init-firewall.sh")
111
+
112
+ if File.exist?(template_path)
113
+ FileUtils.cp(template_path, file_path)
114
+ else
115
+ # Generate basic firewall script
116
+ content = generate_basic_firewall_script
117
+ File.write(file_path, content)
118
+ end
119
+
120
+ # Make executable
121
+ FileUtils.chmod(0o755, file_path)
122
+ file_path
123
+ end
124
+
125
+ def generate_readme(_analysis, _preferences)
126
+ # Copy template from AIDP repo
127
+ template_path = File.join(File.dirname(__FILE__), "..", "..", "..", ".devcontainer", "README.md")
128
+ file_path = File.join(@devcontainer_dir, "README.md")
129
+
130
+ if File.exist?(template_path)
131
+ content = File.read(template_path)
132
+ # Customize project name
133
+ content.gsub!("AIDP Development Container", "#{File.basename(@project_dir)} Development Container")
134
+ content.gsub!("AIDP", File.basename(@project_dir))
135
+ else
136
+ # Generate basic README
137
+ content = generate_basic_readme
138
+ end
139
+ File.write(file_path, content)
140
+
141
+ file_path
142
+ end
143
+
144
+ # Generate basic Dockerfile as fallback when template not found
145
+ # Uses Ubuntu base with minimal tooling - users customize for their needs
146
+ def generate_basic_dockerfile
147
+ <<~DOCKERFILE
148
+ FROM ubuntu:22.04
149
+
150
+ # Install essential system dependencies
151
+ RUN apt-get update && apt-get install -y \\
152
+ git \\
153
+ curl \\
154
+ wget \\
155
+ build-essential \\
156
+ iptables \\
157
+ ipset \\
158
+ && rm -rf /var/lib/apt/lists/*
159
+
160
+ # Create non-root user
161
+ ARG USERNAME=aidp
162
+ ARG USER_UID=1000
163
+ ARG USER_GID=$USER_UID
164
+
165
+ RUN groupadd --gid $USER_GID $USERNAME \\
166
+ && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \\
167
+ && echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/$USERNAME \\
168
+ && chmod 0440 /etc/sudoers.d/$USERNAME
169
+
170
+ # Set up workspace
171
+ RUN mkdir -p /workspace && chown -R $USERNAME:$USERNAME /workspace
172
+ RUN mkdir -p /home/$USERNAME/.aidp && chown -R $USERNAME:$USERNAME /home/$USERNAME/.aidp
173
+
174
+ WORKDIR /workspace
175
+
176
+ USER $USERNAME
177
+
178
+ # Users should customize this Dockerfile for their language/framework needs
179
+ # See https://containers.dev for examples
180
+ DOCKERFILE
181
+ end
182
+
183
+ def generate_basic_firewall_script
184
+ <<~BASH
185
+ #!/bin/bash
186
+ # Basic firewall configuration for devcontainer
187
+ # Allows only essential services
188
+
189
+ set -e
190
+
191
+ echo "Initializing firewall..."
192
+
193
+ # Create ipset for allowed domains
194
+ ipset create allowed-domains hash:net -exist
195
+
196
+ # DNS lookupand add common domains
197
+ add_domain() {
198
+ local domain=$1
199
+ local ips=$(dig +short "$domain" A)
200
+ for ip in $ips; do
201
+ if [[ $ip =~ ^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$ ]]; then
202
+ ipset add allowed-domains "$ip" -exist
203
+ fi
204
+ done
205
+ }
206
+
207
+ # Allow essential services
208
+ add_domain "github.com"
209
+ add_domain "api.github.com"
210
+
211
+ # Set default policies
212
+ iptables -P INPUT DROP
213
+ iptables -P FORWARD DROP
214
+ iptables -P OUTPUT DROP
215
+
216
+ # Allow loopback
217
+ iptables -A INPUT -i lo -j ACCEPT
218
+ iptables -A OUTPUT -o lo -j ACCEPT
219
+
220
+ # Allow established connections
221
+ iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
222
+ iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
223
+
224
+ # Allow DNS
225
+ iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
226
+ iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
227
+
228
+ # Allow SSH
229
+ iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT
230
+
231
+ # Allow HTTPS to allowed domains
232
+ iptables -A OUTPUT -p tcp --dport 443 -m set --match-set allowed-domains dst -j ACCEPT
233
+
234
+ echo "Firewall initialized successfully"
235
+ BASH
236
+ end
237
+
238
+ def generate_basic_readme
239
+ <<~README
240
+ # #{File.basename(@project_dir)} Development Container
241
+
242
+ This directory contains the development container configuration for this project.
243
+
244
+ ## Prerequisites
245
+
246
+ - [VS Code](https://code.visualstudio.com/)
247
+ - [Docker Desktop](https://www.docker.com/products/docker-desktop/)
248
+ - [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
249
+
250
+ ## Usage
251
+
252
+ 1. Open this project in VS Code
253
+ 2. Press `F1` and select "Dev Containers: Reopen in Container"
254
+ 3. Wait for the container to build
255
+
256
+ ## Features
257
+
258
+ - Sandboxed development environment
259
+ - Network security with firewall
260
+ - All development tools pre-installed
261
+ - Persistent volumes for history and configuration
262
+
263
+ ## Customization
264
+
265
+ Edit the files in `.devcontainer/` to customize the environment:
266
+
267
+ - `Dockerfile` - Base image and system packages
268
+ - `devcontainer.json` - VS Code settings and extensions
269
+ - `init-firewall.sh` - Network security rules
270
+ README
271
+ end
272
+ end
273
+ end
274
+ end