ace-docs 0.31.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 (91) hide show
  1. checksums.yaml +7 -0
  2. data/.ace-defaults/docs/config.yml +169 -0
  3. data/.ace-defaults/docs/multi-subject-example.md +130 -0
  4. data/.ace-defaults/docs/single-subject-example.md +150 -0
  5. data/.ace-defaults/nav/protocols/guide-sources/ace-docs.yml +10 -0
  6. data/.ace-defaults/nav/protocols/prompt-sources/ace-docs.yml +34 -0
  7. data/.ace-defaults/nav/protocols/tmpl-sources/ace-docs.yml +10 -0
  8. data/.ace-defaults/nav/protocols/wfi-sources/ace-docs.yml +19 -0
  9. data/CHANGELOG.md +1082 -0
  10. data/LICENSE +21 -0
  11. data/README.md +40 -0
  12. data/Rakefile +14 -0
  13. data/exe/ace-docs +14 -0
  14. data/handbook/guides/documentation/ruby.md +16 -0
  15. data/handbook/guides/documentation/rust.md +35 -0
  16. data/handbook/guides/documentation/typescript.md +18 -0
  17. data/handbook/guides/documentation.g.md +437 -0
  18. data/handbook/guides/documents-embedded-sync.g.md +473 -0
  19. data/handbook/guides/documents-embedding.g.md +276 -0
  20. data/handbook/guides/markdown-style.g.md +290 -0
  21. data/handbook/prompts/ace-change-analyzer.system.md +113 -0
  22. data/handbook/prompts/ace-change-analyzer.user.md +95 -0
  23. data/handbook/prompts/document-analysis.md +74 -0
  24. data/handbook/prompts/document-analysis.system.md +129 -0
  25. data/handbook/prompts/markdown-style.system.md +113 -0
  26. data/handbook/skills/as-docs-create-adr/SKILL.md +35 -0
  27. data/handbook/skills/as-docs-create-api/SKILL.md +35 -0
  28. data/handbook/skills/as-docs-create-user/SKILL.md +35 -0
  29. data/handbook/skills/as-docs-maintain-adrs/SKILL.md +35 -0
  30. data/handbook/skills/as-docs-squash-changelog/SKILL.md +42 -0
  31. data/handbook/skills/as-docs-update/SKILL.md +36 -0
  32. data/handbook/skills/as-docs-update-blueprint/SKILL.md +28 -0
  33. data/handbook/skills/as-docs-update-roadmap/SKILL.md +24 -0
  34. data/handbook/skills/as-docs-update-tools/SKILL.md +36 -0
  35. data/handbook/skills/as-docs-update-usage/SKILL.md +26 -0
  36. data/handbook/templates/code-docs/javascript-jsdoc.template.md +102 -0
  37. data/handbook/templates/code-docs/ruby-yard.template.md +85 -0
  38. data/handbook/templates/project-docs/README.template.md +73 -0
  39. data/handbook/templates/project-docs/architecture.template.md +300 -0
  40. data/handbook/templates/project-docs/blueprint.template.md +165 -0
  41. data/handbook/templates/project-docs/context/ownership.yml +160 -0
  42. data/handbook/templates/project-docs/decisions/adr.template.md +60 -0
  43. data/handbook/templates/project-docs/prd.template.md +144 -0
  44. data/handbook/templates/project-docs/roadmap/roadmap.template.md +47 -0
  45. data/handbook/templates/project-docs/vision.template.md +233 -0
  46. data/handbook/templates/user-docs/user-guide.template.md +107 -0
  47. data/handbook/workflow-instructions/docs/create-adr.wf.md +334 -0
  48. data/handbook/workflow-instructions/docs/create-api.wf.md +448 -0
  49. data/handbook/workflow-instructions/docs/create-cookbook.wf.md +434 -0
  50. data/handbook/workflow-instructions/docs/create-user.wf.md +399 -0
  51. data/handbook/workflow-instructions/docs/maintain-adrs.wf.md +589 -0
  52. data/handbook/workflow-instructions/docs/squash-changelog.wf.md +246 -0
  53. data/handbook/workflow-instructions/docs/update-blueprint.wf.md +361 -0
  54. data/handbook/workflow-instructions/docs/update-context.wf.md +336 -0
  55. data/handbook/workflow-instructions/docs/update-roadmap.wf.md +421 -0
  56. data/handbook/workflow-instructions/docs/update-tools.wf.md +307 -0
  57. data/handbook/workflow-instructions/docs/update-usage.wf.md +710 -0
  58. data/handbook/workflow-instructions/docs/update.wf.md +418 -0
  59. data/lib/ace/docs/atoms/diff_filterer.rb +131 -0
  60. data/lib/ace/docs/atoms/frontmatter_free_matcher.rb +20 -0
  61. data/lib/ace/docs/atoms/git_date_resolver.rb +16 -0
  62. data/lib/ace/docs/atoms/readme_metadata_inferrer.rb +60 -0
  63. data/lib/ace/docs/atoms/terminology_extractor.rb +308 -0
  64. data/lib/ace/docs/atoms/time_range_calculator.rb +96 -0
  65. data/lib/ace/docs/atoms/timestamp_parser.rb +106 -0
  66. data/lib/ace/docs/atoms/type_inferrer.rb +70 -0
  67. data/lib/ace/docs/cli/commands/analyze.rb +351 -0
  68. data/lib/ace/docs/cli/commands/analyze_consistency.rb +185 -0
  69. data/lib/ace/docs/cli/commands/discover.rb +75 -0
  70. data/lib/ace/docs/cli/commands/scope_options.rb +71 -0
  71. data/lib/ace/docs/cli/commands/status.rb +241 -0
  72. data/lib/ace/docs/cli/commands/update.rb +198 -0
  73. data/lib/ace/docs/cli/commands/validate.rb +225 -0
  74. data/lib/ace/docs/cli.rb +60 -0
  75. data/lib/ace/docs/models/analysis_report.rb +120 -0
  76. data/lib/ace/docs/models/consistency_report.rb +259 -0
  77. data/lib/ace/docs/models/document.rb +354 -0
  78. data/lib/ace/docs/molecules/change_detector.rb +389 -0
  79. data/lib/ace/docs/molecules/document_loader.rb +133 -0
  80. data/lib/ace/docs/molecules/frontmatter_manager.rb +85 -0
  81. data/lib/ace/docs/molecules/git_date_resolver.rb +30 -0
  82. data/lib/ace/docs/organisms/cross_document_analyzer.rb +274 -0
  83. data/lib/ace/docs/organisms/document_registry.rb +318 -0
  84. data/lib/ace/docs/organisms/validator.rb +164 -0
  85. data/lib/ace/docs/prompts/compact_diff_prompt.rb +119 -0
  86. data/lib/ace/docs/prompts/consistency_prompt.rb +286 -0
  87. data/lib/ace/docs/prompts/document_analysis_prompt.rb +389 -0
  88. data/lib/ace/docs/version.rb +7 -0
  89. data/lib/ace/docs.rb +82 -0
  90. data/lib/test.rb +4 -0
  91. metadata +347 -0
@@ -0,0 +1,389 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "timeout"
5
+ require "yaml"
6
+
7
+ module Ace
8
+ module Docs
9
+ module Prompts
10
+ # Builds prompts for analyzing changes relevant to a specific document
11
+ class DocumentAnalysisPrompt
12
+ NAV_RESOLVE_TIMEOUT_SECONDS = 10
13
+
14
+ # Build prompts for analyzing changes for a specific document
15
+ # @param document [Document] The document to analyze changes for
16
+ # @param diff [String, Hash] Either single diff string or hash of {subject_name => diff_string}
17
+ # @param since [String] Time period for the diff
18
+ # @param cache_dir [String, nil] Optional cache directory for context.md
19
+ # @return [Hash] Hash with :system, :user prompts, :context_md, :diff_stats
20
+ def self.build(document, diff, since: nil, cache_dir: nil)
21
+ # Load base instructions
22
+ base_instructions = load_user_prompt_template
23
+
24
+ # Handle both single diff and multi-subject diffs
25
+ diff_files = []
26
+ if diff.is_a?(Hash)
27
+ # Multi-subject: save each diff with subject name
28
+ if cache_dir && Dir.exist?(cache_dir)
29
+ diff.each do |subject_name, diff_content|
30
+ next if diff_content.strip.empty? # Skip empty diffs
31
+
32
+ diff_file_path = File.join(cache_dir, "#{subject_name}.diff")
33
+ File.write(diff_file_path, diff_content)
34
+ # Store absolute path for ace-bundle (it resolves relative to context.md location)
35
+ diff_files << diff_file_path
36
+ end
37
+ end
38
+ elsif cache_dir && Dir.exist?(cache_dir)
39
+ # Single diff: backward compatible behavior
40
+ diff_file_path = File.join(cache_dir, "repo-diff.diff")
41
+ File.write(diff_file_path, diff)
42
+ # Store absolute path for ace-bundle
43
+ diff_files << diff_file_path
44
+ end
45
+
46
+ # Create context.md = frontmatter + instructions + scope section
47
+ # Use relative paths for diff files
48
+ context_md = create_context_markdown(base_instructions, document, since,
49
+ diff_files: diff_files)
50
+
51
+ # Save context.md to cache
52
+ if cache_dir && Dir.exist?(cache_dir)
53
+ File.write(File.join(cache_dir, "context.md"), context_md)
54
+ end
55
+
56
+ # Load via ace-bundle (returns instructions + embedded context + diffs)
57
+ embedded_content = load_context_md(context_md, document: document, cache_dir: cache_dir) || base_instructions
58
+
59
+ # Calculate diff stats for metadata
60
+ all_diffs = diff.is_a?(Hash) ? diff.values.join("\n") : diff
61
+ diff_stats = calculate_diff_stats(all_diffs)
62
+
63
+ {
64
+ system: load_system_prompt,
65
+ user: embedded_content,
66
+ context_md: context_md,
67
+ diff_stats: diff_stats
68
+ }
69
+ end
70
+
71
+ # Load user prompt template via ace-nav protocol
72
+ # @return [String] User prompt template content
73
+ def self.load_user_prompt_template
74
+ stdout, _, status = Open3.capture3("ace-nav", "prompt://document-analysis", "--content")
75
+
76
+ if status.success?
77
+ stdout.strip
78
+ else
79
+ # Fallback to reading file directly
80
+ template_path = File.join(Ace::Docs.root, "handbook/prompts/document-analysis.md")
81
+ File.exist?(template_path) ? File.read(template_path) : fallback_user_template
82
+ end
83
+ rescue
84
+ fallback_user_template
85
+ end
86
+
87
+ # Fallback user template if ace-nav unavailable
88
+ # @return [String] Minimal user template
89
+ def self.fallback_user_template
90
+ <<~TEMPLATE
91
+ # Document Analysis
92
+
93
+ ## Document Information
94
+ **Path**: {document_path}
95
+ **Type**: {document_type}
96
+ **Purpose**: {document_purpose}
97
+
98
+ ## Changes to Analyze
99
+ {diff_content}
100
+ TEMPLATE
101
+ end
102
+
103
+ # Load system prompt via ace-nav protocol
104
+ # @return [String] System prompt content
105
+ def self.load_system_prompt
106
+ stdout, _, status = Open3.capture3("ace-nav", "prompt://document-analysis.system", "--content")
107
+
108
+ if status.success?
109
+ stdout.strip
110
+ else
111
+ # Fallback to embedded prompt if ace-nav fails
112
+ fallback_system_prompt
113
+ end
114
+ rescue
115
+ fallback_system_prompt
116
+ end
117
+
118
+ # Build user prompt with document context and diff
119
+ # @param document [Document] The document to analyze
120
+ # @param diff [String] The git diff content
121
+ # @param since [String] Time period
122
+ # @param context [String, nil] Optional embedded context from ace-bundle
123
+ # @return [String] User prompt
124
+ def self.build_user_prompt(document, diff, since, context: nil)
125
+ # Extract document metadata
126
+ doc_type = document.doc_type || "document"
127
+ purpose = document.purpose || "(not specified)"
128
+ doc_path = document.respond_to?(:relative_path) ? document.relative_path : document.path
129
+
130
+ # Extract context information
131
+ context_keywords = document.context_keywords
132
+ context_preset = document.context_preset
133
+
134
+ # Extract subject filters (to show what was filtered)
135
+ subject_filters = document.subject_diff_filters
136
+
137
+ # Build context description
138
+ context_desc = build_context_description(context_keywords, context_preset)
139
+
140
+ # Build filters description
141
+ filters_desc = build_filters_description(subject_filters)
142
+
143
+ # Build time description
144
+ time_desc = since ? "since #{since}" : "recent changes"
145
+
146
+ # Build context section if provided
147
+ context_section = if context && !context.strip.empty?
148
+ "\n## Context\n\n#{context}\n"
149
+ else
150
+ ""
151
+ end
152
+
153
+ <<~PROMPT
154
+ ## Document Information
155
+
156
+ **Path**: #{doc_path}
157
+ **Type**: #{doc_type}
158
+ **Purpose**: #{purpose}
159
+
160
+ #{context_desc}
161
+ #{context_section}
162
+ ## Changes to Analyze
163
+
164
+ The following git diff shows changes #{time_desc}.
165
+
166
+ #{filters_desc}
167
+
168
+ ```diff
169
+ #{diff}
170
+ ```
171
+ PROMPT
172
+ end
173
+
174
+ # Fallback system prompt if ace-nav unavailable
175
+ # @return [String] Embedded system prompt
176
+ def self.fallback_system_prompt
177
+ <<~PROMPT
178
+ You are analyzing code changes to determine what needs to be updated in documentation.
179
+
180
+ Provide a markdown report with:
181
+ - Summary (2-3 sentences)
182
+ - Changes Detected (organized by HIGH/MEDIUM/LOW priority)
183
+ - Recommended Updates (specific sections with reasoning)
184
+ - Additional Notes
185
+
186
+ Focus on relevance to the document's purpose and be specific about what needs updating and why.
187
+ PROMPT
188
+ end
189
+
190
+ # Calculate diff statistics
191
+ # @param diff [String] The git diff content
192
+ # @return [Hash] Statistics with hunks_total, files_changed, insertions, deletions
193
+ def self.calculate_diff_stats(diff)
194
+ {
195
+ hunks_total: diff.scan(/^@@ .+ @@/).size,
196
+ files_changed: diff.scan(/^diff --git/).size,
197
+ insertions: diff.scan(/^\+[^+]/).size,
198
+ deletions: diff.scan(/^-[^-]/).size
199
+ }
200
+ end
201
+
202
+ # Create context.md with frontmatter, instructions, and scope section
203
+ # @param base_instructions [String] The base prompt instructions
204
+ # @param document [Document] The document configuration
205
+ # @param since [String] Time period for analysis
206
+ # @param diff_files [Array<String>] Array of relative paths to diff files
207
+ # @return [String] Complete context.md content
208
+ def self.create_context_markdown(base_instructions, document, since, diff_files: [])
209
+ # Start with base context config
210
+ context_config = {
211
+ "params" => {"format" => "markdown-xml"},
212
+ "embed_document_source" => true
213
+ }
214
+
215
+ # Merge document's context configuration (supports new ace-docs namespace and legacy format)
216
+ doc_context = document.ace_docs_config&.dig("context") || document.context_config || {}
217
+ context_config = context_config.merge(doc_context)
218
+
219
+ # Add template/guide files from document type config
220
+ type_ref_files = resolve_type_references(document)
221
+ if type_ref_files && !type_ref_files.empty?
222
+ context_config["files"] ||= []
223
+ context_config["files"].concat(type_ref_files)
224
+ end
225
+
226
+ # Add diff files to files array (append to existing files if any)
227
+ if diff_files && !diff_files.empty?
228
+ context_config["files"] ||= []
229
+ context_config["files"].concat(diff_files)
230
+ end
231
+
232
+ frontmatter = {"context" => context_config}
233
+
234
+ # Build analysis scope section
235
+ scope_section = build_analysis_scope_section(document, since)
236
+
237
+ # context.md = frontmatter + base instructions + scope section
238
+ # YAML.dump adds opening --- but not closing, we add closing ---
239
+ "#{YAML.dump(frontmatter).strip}\n---\n\n#{base_instructions}\n\n#{scope_section}"
240
+ end
241
+
242
+ # Build analysis scope section explaining context vs subject
243
+ # @param document [Document] The document configuration
244
+ # @param since [String] Time period for analysis
245
+ # @return [String] Analysis scope section
246
+ def self.build_analysis_scope_section(document, since)
247
+ preset = document.context_preset
248
+ keywords = document.context_keywords
249
+
250
+ context_desc = if preset && !preset.empty?
251
+ "- Loaded from preset: `#{preset}`"
252
+ elsif keywords && !keywords.empty?
253
+ "- Keywords: #{keywords.map { |k| "`#{k}`" }.join(", ")}"
254
+ else
255
+ "- No context files specified"
256
+ end
257
+
258
+ # Handle both multi-subject and single-subject configurations
259
+ if document.multi_subject?
260
+ subject_configs = document.subject_configurations
261
+ filters_desc = if subject_configs.empty?
262
+ "- All repository changes (no filters)"
263
+ else
264
+ subject_configs.map do |subj|
265
+ "- `#{subj[:name]}`: #{subj[:filters].join(", ")}"
266
+ end.join("\n")
267
+ end
268
+ else
269
+ filters = document.subject_diff_filters || []
270
+ filters_desc = if filters.empty?
271
+ "- All repository changes (no filters)"
272
+ else
273
+ filters.map { |f| "- `#{f}`" }.join("\n")
274
+ end
275
+ end
276
+
277
+ # Check for template/guide references from document type config
278
+ refs_section = build_type_references_section(document)
279
+
280
+ <<~SECTION
281
+ ## Analysis Scope
282
+
283
+ **Context files** (for understanding the codebase):
284
+ #{context_desc}
285
+
286
+ **Subject of analysis** (git diff filtered to):
287
+ #{filters_desc}
288
+
289
+ **Time range**: Changes since #{since || "recent"}
290
+ #{refs_section}
291
+ SECTION
292
+ end
293
+
294
+ # Resolve template and guide references from document type config
295
+ # Looks up the document's type in Ace::Docs.config to find template/guide protocol URLs,
296
+ # then resolves them to file paths via ace-nav
297
+ # @param document [Document] The document configuration
298
+ # @return [Array<String>] Array of resolved file paths
299
+ def self.resolve_type_references(document)
300
+ doc_type = document.doc_type
301
+ return [] unless doc_type
302
+
303
+ type_config = Ace::Docs.config.dig("document_types", doc_type)
304
+ return [] unless type_config
305
+
306
+ files = []
307
+ %w[template guide].each do |ref_key|
308
+ protocol_url = type_config[ref_key]
309
+ next unless protocol_url
310
+
311
+ stdout, _stderr, status = with_nav_resolve_timeout do
312
+ Open3.capture3("ace-nav", "resolve", protocol_url)
313
+ end
314
+ if status.success?
315
+ path = stdout.strip
316
+ files << path if path && !path.empty? && File.exist?(path)
317
+ end
318
+ end
319
+
320
+ files
321
+ rescue
322
+ []
323
+ end
324
+
325
+ def self.with_nav_resolve_timeout
326
+ Timeout.timeout(NAV_RESOLVE_TIMEOUT_SECONDS) { yield }
327
+ rescue Timeout::Error
328
+ ["", "ace-nav resolve timed out after #{NAV_RESOLVE_TIMEOUT_SECONDS}s", Struct.new(:success?).new(false)]
329
+ end
330
+
331
+ # Build a reference section describing template/guide for this doc type
332
+ # @param document [Document] The document configuration
333
+ # @return [String] Reference section text or empty string
334
+ def self.build_type_references_section(document)
335
+ doc_type = document.doc_type
336
+ return "" unless doc_type
337
+
338
+ type_config = Ace::Docs.config.dig("document_types", doc_type)
339
+ return "" unless type_config
340
+
341
+ refs = []
342
+ refs << "- Template: `#{type_config["template"]}`" if type_config["template"]
343
+ refs << "- Guide: `#{type_config["guide"]}`" if type_config["guide"]
344
+ return "" if refs.empty?
345
+
346
+ "\n**Reference documents** (template/guide for `#{doc_type}` doc type):\n#{refs.join("\n")}\n"
347
+ rescue
348
+ ""
349
+ end
350
+
351
+ # Load context.md via ace-bundle (embeds files as XML)
352
+ # @param context_md [String] The context.md content
353
+ # @param document [Document] The document configuration (unused, kept for compatibility)
354
+ # @param cache_dir [String, nil] Directory containing context.md and referenced files
355
+ # @return [String, nil] Final prompt with embedded files or nil if unavailable
356
+ def self.load_context_md(context_md, document:, cache_dir: nil)
357
+ require "ace/bundle"
358
+
359
+ # Load context.md - ace-bundle processes presets and files from frontmatter
360
+ result = if cache_dir
361
+ context_file = File.join(cache_dir, "context.md")
362
+ Ace::Bundle.load_file(context_file)
363
+ else
364
+ Ace::Bundle.load_auto(context_md)
365
+ end
366
+
367
+ result.content
368
+ rescue LoadError
369
+ warn "ace-bundle not available - context embedding disabled" if Ace::Docs.debug?
370
+ nil
371
+ rescue => e
372
+ warn "Context loading failed: #{e.message}" if Ace::Docs.debug?
373
+ nil
374
+ end
375
+
376
+ # Make helper methods accessible
377
+ private_class_method :load_user_prompt_template
378
+ private_class_method :fallback_user_template
379
+ private_class_method :calculate_diff_stats
380
+ private_class_method :create_context_markdown
381
+ private_class_method :build_analysis_scope_section
382
+ private_class_method :resolve_type_references
383
+ private_class_method :with_nav_resolve_timeout
384
+ private_class_method :build_type_references_section
385
+ private_class_method :load_context_md
386
+ end
387
+ end
388
+ end
389
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ace
4
+ module Docs
5
+ VERSION = "0.31.0"
6
+ end
7
+ end
data/lib/ace/docs.rb ADDED
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require_relative "docs/version"
5
+ require "ace/support/config" # For config cascade
6
+
7
+ # CLI Commands (Hanami pattern)
8
+ require_relative "docs/cli/commands/status"
9
+ require_relative "docs/cli/commands/discover"
10
+ require_relative "docs/cli/commands/update"
11
+ require_relative "docs/cli/commands/analyze"
12
+ require_relative "docs/cli/commands/validate"
13
+ require_relative "docs/cli/commands/analyze_consistency"
14
+
15
+ # CLI
16
+ require_relative "docs/cli"
17
+
18
+ module Ace
19
+ module Docs
20
+ class Error < StandardError; end
21
+
22
+ # Main entry point for ace-docs gem
23
+ # Provides documentation management with frontmatter,
24
+ # change analysis, and intelligent updates
25
+ def self.root
26
+ File.expand_path("../..", __dir__)
27
+ end
28
+
29
+ # Get configuration using ace-config cascade
30
+ # Follows ADR-022: Configuration Default and Override Pattern
31
+ # Uses Ace::Support::Config.create() for configuration cascade resolution
32
+ # @return [Hash] Configuration hash with defaults merged
33
+ def self.config
34
+ @config ||= begin
35
+ gem_root = Gem.loaded_specs["ace-docs"]&.gem_dir ||
36
+ File.expand_path("../..", __dir__)
37
+
38
+ resolver = Ace::Support::Config.create(
39
+ config_dir: ".ace",
40
+ defaults_dir: ".ace-defaults",
41
+ gem_path: gem_root
42
+ )
43
+
44
+ # Resolve config for docs namespace
45
+ config = resolver.resolve_namespace("docs")
46
+ config.data
47
+ rescue => e
48
+ warn "ace-docs: Could not load config: #{e.class} - #{e.message}" if debug?
49
+ # Fall back to gem defaults instead of empty hash to prevent silent config erasure
50
+ load_gem_defaults_fallback
51
+ end
52
+ end
53
+
54
+ # Reset configuration (primarily for testing)
55
+ def self.reset_config!
56
+ @config = nil
57
+ end
58
+
59
+ # Load gem defaults directly as fallback when cascade resolution fails
60
+ # This ensures configuration is never silently erased due to YAML errors
61
+ # or user config issues
62
+ # @return [Hash] Defaults hash or empty hash if defaults also fail
63
+ def self.load_gem_defaults_fallback
64
+ gem_root = Gem.loaded_specs["ace-docs"]&.gem_dir ||
65
+ File.expand_path("../..", __dir__)
66
+ defaults_path = File.join(gem_root, ".ace-defaults", "docs", "config.yml")
67
+
68
+ return {} unless File.exist?(defaults_path)
69
+
70
+ YAML.safe_load_file(defaults_path, permitted_classes: [Date], aliases: true) || {}
71
+ rescue
72
+ {} # Only return empty hash if even defaults fail to load
73
+ end
74
+ private_class_method :load_gem_defaults_fallback
75
+
76
+ # Check if debug mode is enabled
77
+ # @return [Boolean] True if debug mode is enabled
78
+ def self.debug?
79
+ ENV["ACE_DEBUG"] == "1" || ENV["DEBUG"] == "1"
80
+ end
81
+ end
82
+ end
data/lib/test.rb ADDED
@@ -0,0 +1,4 @@
1
+ class Test
2
+ def hello
3
+ end
4
+ end