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,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ace
4
+ module Docs
5
+ module Organisms
6
+ # Validates documents against their declared rules
7
+ class Validator
8
+ def initialize(registry)
9
+ @registry = registry
10
+ end
11
+
12
+ # Validate a document against rules
13
+ # @param document [Document] Document to validate
14
+ # @param syntax [Boolean] Run syntax validation
15
+ # @param semantic [Boolean] Run semantic validation
16
+ # @return [Hash] Validation results
17
+ def validate_document(document, syntax: true, semantic: false)
18
+ errors = []
19
+ warnings = []
20
+
21
+ # Check frontmatter validity
22
+ if !document.managed?
23
+ errors << "Missing required frontmatter fields"
24
+ end
25
+
26
+ # Check max lines rule
27
+ if document.max_lines
28
+ line_count = document.content.lines.count
29
+ if line_count > document.max_lines
30
+ errors << "Exceeds max lines: #{line_count}/#{document.max_lines}"
31
+ end
32
+ end
33
+
34
+ # Check required sections
35
+ if document.required_sections.any?
36
+ missing_sections = check_sections(document)
37
+ if missing_sections.any?
38
+ errors << "Missing required sections: #{missing_sections.join(", ")}"
39
+ end
40
+ end
41
+
42
+ # Syntax validation (would delegate to external linters)
43
+ if syntax
44
+ syntax_results = validate_syntax(document)
45
+ errors.concat(syntax_results[:errors])
46
+ warnings.concat(syntax_results[:warnings])
47
+ end
48
+
49
+ # Semantic validation (would use LLM)
50
+ if semantic
51
+ semantic_results = validate_semantic(document)
52
+ errors.concat(semantic_results[:errors])
53
+ warnings.concat(semantic_results[:warnings])
54
+ end
55
+
56
+ {
57
+ valid: errors.empty?,
58
+ errors: errors,
59
+ warnings: warnings
60
+ }
61
+ end
62
+
63
+ private
64
+
65
+ def check_sections(document)
66
+ required = document.required_sections
67
+ content = document.content.downcase
68
+
69
+ missing = []
70
+ required.each do |section|
71
+ # Check for section as a header
72
+ unless content.include?("# #{section.downcase}") ||
73
+ content.include?("## #{section.downcase}")
74
+ missing << section
75
+ end
76
+ end
77
+
78
+ missing
79
+ end
80
+
81
+ def validate_syntax(document)
82
+ # TODO: Integrate with markdownlint or similar
83
+ {errors: [], warnings: []}
84
+ end
85
+
86
+ def validate_semantic(document)
87
+ # Build semantic validation prompt
88
+ keywords = document.context_keywords.any? ? document.context_keywords.join(", ") : "(none specified)"
89
+
90
+ prompt = <<~PROMPT
91
+ Validate this documentation for semantic accuracy and relevance.
92
+
93
+ Document Type: #{document.doc_type}
94
+ Purpose: #{document.purpose}
95
+ Keywords: #{keywords}
96
+
97
+ Content:
98
+ #{document.content}
99
+
100
+ Check for:
101
+ - Content matches stated purpose
102
+ - Information is accurate and up-to-date
103
+ - No contradictions or inconsistencies
104
+ - Appropriate depth for document type
105
+
106
+ Respond with:
107
+ VALID or INVALID on first line
108
+ Then list any issues as bullet points starting with "-"
109
+ PROMPT
110
+
111
+ # Call LLM using Ruby API
112
+ begin
113
+ response = call_llm_for_validation(prompt)
114
+ stdout = response[:text]
115
+ rescue => e
116
+ # Handle all errors (including when Ace::LLM is not loaded)
117
+ error_msg = if e.message.include?("not found") || e.message.include?("No model specified") || e.message.include?("uninitialized constant")
118
+ "Semantic validation unavailable (ace-llm configuration issue). Check ace-llm setup."
119
+ else
120
+ "Semantic validation error: #{e.message}"
121
+ end
122
+ return {errors: [error_msg], warnings: []}
123
+ end
124
+
125
+ # Parse LLM response
126
+ errors = []
127
+ warnings = []
128
+
129
+ if stdout.match?(/INVALID/i)
130
+ # Extract issues from response
131
+ # Skip the first line (VALID/INVALID) and collect issue lines
132
+ lines = stdout.strip.split("\n")
133
+ lines.each_with_index do |line, idx|
134
+ next if idx == 0 # Skip VALID/INVALID line
135
+ next if line.strip.empty?
136
+
137
+ if line.start_with?("-")
138
+ errors << line.sub(/^-\s*/, "").strip
139
+ end
140
+ end
141
+
142
+ # If no issues were extracted but marked INVALID, add generic error
143
+ if errors.empty?
144
+ errors << "Content validation failed - semantic issues detected"
145
+ end
146
+ end
147
+
148
+ {errors: errors, warnings: warnings}
149
+ end
150
+
151
+ # Call LLM for validation (protected for testing)
152
+ # @param prompt [String] The validation prompt
153
+ # @return [Hash] Response with :text key
154
+ def call_llm_for_validation(prompt)
155
+ Ace::LLM::QueryInterface.query(
156
+ "gflash",
157
+ prompt,
158
+ temperature: 0.3
159
+ )
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ace
4
+ module Docs
5
+ module Prompts
6
+ # Builds prompts for LLM diff compaction
7
+ class CompactDiffPrompt
8
+ # Build a prompt for compacting a diff
9
+ # @param diff [String] The git diff to compact
10
+ # @param documents [Array<Document>] Documents being analyzed
11
+ # @return [String] Complete prompt for LLM
12
+ def self.build(diff, documents = [])
13
+ document_list = format_document_list(documents)
14
+
15
+ <<~PROMPT
16
+ # Task: Compact Git Diff for Documentation Updates
17
+
18
+ You are analyzing code changes to help update documentation. Your task is to compact the following git diff by removing noise while preserving all relevant details that might affect documentation.
19
+
20
+ ## Documents Being Updated
21
+ #{document_list}
22
+
23
+ ## Instructions
24
+
25
+ 1. **REMOVE** (noise that doesn't affect documentation):
26
+ - Test file changes (test/, spec/, *_test.rb, *.test.*)
27
+ - Formatting-only changes (whitespace, indentation)
28
+ - Comment-only changes that don't alter functionality
29
+ - Version bumps in lock files (Gemfile.lock, package-lock.json)
30
+ - Generated files and build artifacts
31
+ - Minor refactors that don't change behavior
32
+
33
+ 2. **KEEP** (relevant changes that affect documentation):
34
+ - New features, classes, methods, or commands
35
+ - Changed interfaces, parameters, or return values
36
+ - New or modified configuration options
37
+ - Architecture or structural changes
38
+ - Behavioral changes
39
+ - New dependencies or tools
40
+ - Error handling changes
41
+ - Performance improvements worth documenting
42
+ - Deprecations or removals
43
+
44
+ 3. **ORGANIZE** the output by impact level:
45
+ - HIGH: New features, removed features, breaking changes
46
+ - MEDIUM: Interface changes, new options, behavioral changes
47
+ - LOW: Performance improvements, minor enhancements
48
+
49
+ ## Output Format
50
+
51
+ Provide a markdown report with:
52
+ - Summary section with key changes
53
+ - Sections organized by impact (HIGH/MEDIUM/LOW)
54
+ - For each change, include:
55
+ * Component/gem name
56
+ * Brief description
57
+ * Relevant files
58
+ * Which documents might need updates
59
+
60
+ ## Git Diff to Analyze
61
+
62
+ ```diff
63
+ #{diff}
64
+ ```
65
+
66
+ ## Response
67
+
68
+ Please provide the compacted analysis in markdown format.
69
+ PROMPT
70
+ end
71
+
72
+ # Build a prompt for analyzing changes without documents
73
+ # @param diff [String] The git diff to analyze
74
+ # @param since [String] Time period for the diff
75
+ # @return [String] Complete prompt for LLM
76
+ def self.build_general(diff, since: "recent changes")
77
+ <<~PROMPT
78
+ # Task: Analyze and Compact Git Diff
79
+
80
+ Analyze the following git diff from #{since} and provide a compacted summary suitable for documentation updates.
81
+
82
+ ## Instructions
83
+
84
+ 1. Remove noise (tests, formatting, lock files)
85
+ 2. Focus on changes that affect documentation
86
+ 3. Organize by impact level (HIGH/MEDIUM/LOW)
87
+ 4. Provide actionable insights for documentation updates
88
+
89
+ ## Git Diff
90
+
91
+ ```diff
92
+ #{diff}
93
+ ```
94
+
95
+ ## Response
96
+
97
+ Provide a structured markdown analysis with:
98
+ - Executive summary
99
+ - Significant changes by impact level
100
+ - Files and components affected
101
+ - Suggested documentation updates
102
+ PROMPT
103
+ end
104
+
105
+ private_class_method def self.format_document_list(documents)
106
+ return "No specific documents targeted" if documents.empty?
107
+
108
+ list = documents.map do |doc|
109
+ path = doc.respond_to?(:relative_path) ? doc.relative_path : doc.path
110
+ type = doc.respond_to?(:doc_type) ? " (#{doc.doc_type})" : ""
111
+ "- #{path}#{type}"
112
+ end
113
+
114
+ list.join("\n")
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,286 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "fileutils"
5
+
6
+ # Try to load ace-bundle for better document embedding
7
+ begin
8
+ require "ace/bundle"
9
+ rescue LoadError
10
+ # Will work without ace-bundle but with less optimal formatting
11
+ end
12
+
13
+ module Ace
14
+ module Docs
15
+ module Prompts
16
+ # Builds prompts for cross-document consistency analysis
17
+ class ConsistencyPrompt
18
+ # Build the complete prompt for consistency analysis
19
+ # @param documents [Hash] hash of { path => content } for documents to analyze
20
+ # @param options [Hash] analysis options
21
+ # @param session_dir [String, nil] session directory for saving context.md
22
+ # @return [Hash] { system: String, user: String } prompts
23
+ def build(documents, options = {}, session_dir: nil)
24
+ # Use ace-bundle if available for better document separation
25
+ user_content = if defined?(Ace::Bundle) && session_dir
26
+ build_with_context(documents, options, session_dir)
27
+ else
28
+ user_prompt(documents, options)
29
+ end
30
+
31
+ {
32
+ system: system_prompt,
33
+ user: user_content
34
+ }
35
+ end
36
+
37
+ private
38
+
39
+ # Generate the system prompt with analysis instructions
40
+ def system_prompt
41
+ <<~PROMPT
42
+ You are a documentation consistency analyzer. Analyze the provided documents for consistency issues.
43
+
44
+ ## Analysis Types
45
+
46
+ 1. **TERMINOLOGY CONFLICTS**: Inconsistent word usage
47
+ - Different words for the same concept (e.g., "gem" vs "package" in Ruby context)
48
+ - Spelling variations (US vs UK: "analyze" vs "analyse", "color" vs "colour")
49
+ - Case inconsistencies (e.g., "GitHub" vs "github" vs "Github")
50
+ - Technical term variations (e.g., "LLM" vs "large language model")
51
+
52
+ 2. **DUPLICATE CONTENT**: Similar or identical content blocks
53
+ - Identify content that appears in multiple documents with >70% similarity
54
+ - Flag redundant explanations of the same concept
55
+ - Note which document should be the authoritative source
56
+
57
+ 3. **VERSION INCONSISTENCIES**: Mismatched version numbers
58
+ - Different version numbers for the same tool/library
59
+ - Outdated references in some documents
60
+ - Inconsistent release information
61
+
62
+ 4. **CONSOLIDATION OPPORTUNITIES**: Content that could be merged
63
+ - Multiple documents explaining the same workflow
64
+ - Scattered information that belongs together
65
+ - Overlapping guides that could be combined
66
+
67
+ ## Response Format
68
+
69
+ Return ONLY valid JSON in this exact structure:
70
+
71
+ ```json
72
+ {
73
+ "terminology_conflicts": [
74
+ {
75
+ "terms": ["term1", "term2"],
76
+ "occurrences": {
77
+ "term1": [
78
+ {"file": "path/to/file1.md", "count": 5, "examples": ["line excerpt 1", "line excerpt 2"]},
79
+ {"file": "path/to/file2.md", "count": 3, "examples": ["line excerpt"]}
80
+ ],
81
+ "term2": [
82
+ {"file": "path/to/file3.md", "count": 8, "examples": ["line excerpt"]}
83
+ ]
84
+ },
85
+ "recommendation": "Standardize to 'term1' (Ruby ecosystem convention)"
86
+ }
87
+ ],
88
+ "duplicate_content": [
89
+ {
90
+ "description": "Installation instructions",
91
+ "similarity_percentage": 85,
92
+ "locations": [
93
+ {"file": "README.md", "lines": "45-67", "excerpt": "first 50 chars..."},
94
+ {"file": "docs/getting-started.md", "lines": "12-34", "excerpt": "first 50 chars..."}
95
+ ],
96
+ "recommendation": "Keep in getting-started.md, reference from README"
97
+ }
98
+ ],
99
+ "version_inconsistencies": [
100
+ {
101
+ "item": "ace-docs version",
102
+ "versions_found": [
103
+ {"version": "0.4.5", "file": "README.md", "line": 12},
104
+ {"version": "0.4.6", "file": "CHANGELOG.md", "line": 5},
105
+ {"version": "0.4.4", "file": "docs/api.md", "line": 23}
106
+ ],
107
+ "recommendation": "Update all to 0.4.6 (latest in CHANGELOG)"
108
+ }
109
+ ],
110
+ "consolidation_opportunities": [
111
+ {
112
+ "topic": "Workflow instructions for updating documents",
113
+ "documents": [
114
+ {"file": "docs/update-workflow.md", "coverage": "comprehensive"},
115
+ {"file": "docs/quick-update.md", "coverage": "basic steps"},
116
+ {"file": "README.md", "coverage": "brief mention in section 'Updating Documents'"}
117
+ ],
118
+ "recommendation": "Consolidate into single workflow document with quick-start section"
119
+ }
120
+ ]
121
+ }
122
+ ```
123
+
124
+ ## Analysis Guidelines
125
+
126
+ - For terminology: Consider context (Ruby gems vs npm packages)
127
+ - For duplicates: Use the similarity threshold provided (default 70%)
128
+ - For versions: Check semantic versioning and identify the most recent
129
+ - For consolidation: Consider user journey and information architecture
130
+ - Always provide actionable, specific recommendations
131
+ - Include file paths and line numbers when possible
132
+ - Provide brief excerpts to illustrate the issues
133
+ PROMPT
134
+ end
135
+
136
+ # Generate the user prompt with document content and parameters
137
+ def user_prompt(documents, options)
138
+ threshold = options[:threshold] || 70
139
+ focus_areas = build_focus_areas(options)
140
+
141
+ <<~PROMPT
142
+ Analyze these #{documents.count} documents for consistency issues:
143
+
144
+ ## Documents to Analyze
145
+
146
+ #{format_documents(documents)}
147
+
148
+ ## Analysis Parameters
149
+
150
+ - Similarity threshold for duplicates: #{threshold}%
151
+ - Focus areas: #{focus_areas.join(", ")}
152
+ #{"- Pattern filter: #{options[:pattern]}" if options[:pattern]}
153
+
154
+ ## Instructions
155
+
156
+ 1. Scan all documents for the four types of consistency issues
157
+ 2. Apply the similarity threshold of #{threshold}% for duplicate detection
158
+ 3. Provide specific, actionable recommendations for each issue
159
+ 4. Include file paths and line references where applicable
160
+ 5. Return results in the exact JSON format specified
161
+
162
+ Analyze the documents now and return the JSON results.
163
+ PROMPT
164
+ end
165
+
166
+ # Format documents for inclusion in the prompt
167
+ def format_documents(documents)
168
+ documents.map do |path, content|
169
+ # Extract frontmatter if present
170
+ frontmatter = extract_frontmatter(content)
171
+
172
+ # Truncate very long documents
173
+ truncated_content = truncate_content(content)
174
+
175
+ <<~DOC
176
+ ### File: #{path}
177
+ #{"Frontmatter: #{frontmatter.to_json}\n" if frontmatter}
178
+ ```
179
+ #{truncated_content}
180
+ ```
181
+ DOC
182
+ end.join("\n")
183
+ end
184
+
185
+ # Extract YAML frontmatter from content
186
+ def extract_frontmatter(content)
187
+ return nil unless content.start_with?("---\n")
188
+
189
+ parts = content.split(/^---\s*$/, 3)
190
+ return nil unless parts.size >= 2
191
+
192
+ begin
193
+ require "yaml"
194
+ YAML.safe_load(parts[1])
195
+ rescue
196
+ nil
197
+ end
198
+ end
199
+
200
+ # Truncate content if it's too long
201
+ def truncate_content(content, max_lines = 500)
202
+ lines = content.lines
203
+ return content if lines.size <= max_lines
204
+
205
+ truncated = lines.first(max_lines).join
206
+ truncated + "\n... (truncated, #{lines.size - max_lines} lines omitted) ..."
207
+ end
208
+
209
+ # Build focus areas based on options
210
+ def build_focus_areas(options)
211
+ areas = []
212
+
213
+ if options[:all] || (!options[:terminology] && !options[:duplicates] && !options[:versions])
214
+ areas = ["terminology conflicts", "duplicate content", "version inconsistencies", "consolidation opportunities"]
215
+ else
216
+ areas << "terminology conflicts" if options[:terminology]
217
+ areas << "duplicate content" if options[:duplicates]
218
+ areas << "version inconsistencies" if options[:versions]
219
+ end
220
+
221
+ areas.empty? ? ["all issue types"] : areas
222
+ end
223
+
224
+ # Build user prompt with ace-bundle for better document separation
225
+ def build_with_context(documents, options, session_dir)
226
+ # Use the actual document paths directly (no copying needed)
227
+ doc_files = documents.keys.map { |path| File.expand_path(path) }
228
+
229
+ # Create context.md with frontmatter and instructions
230
+ threshold = options[:threshold] || 70
231
+ focus_areas = build_focus_areas(options)
232
+
233
+ # Build context configuration
234
+ context_config = {
235
+ "params" => {"format" => "markdown-xml"},
236
+ "embed_document_source" => true,
237
+ "files" => doc_files
238
+ }
239
+
240
+ # Create frontmatter
241
+ frontmatter = {"context" => context_config}
242
+
243
+ # Build instructions
244
+ instructions = <<~INSTRUCTIONS
245
+ # Cross-Document Consistency Analysis
246
+
247
+ Analyze these #{documents.count} documents for consistency issues.
248
+
249
+ ## Analysis Parameters
250
+
251
+ - Similarity threshold for duplicates: #{threshold}%
252
+ - Focus areas: #{focus_areas.join(", ")}
253
+ #{"- Pattern filter: #{options[:pattern]}" if options[:pattern]}
254
+
255
+ ## Instructions
256
+
257
+ 1. Scan all documents for the four types of consistency issues
258
+ 2. Apply the similarity threshold of #{threshold}% for duplicate detection
259
+ 3. Provide specific, actionable recommendations for each issue
260
+ 4. Include file paths and line references where applicable
261
+ 5. Return results in the exact JSON format specified
262
+
263
+ ## Documents
264
+
265
+ The following documents are embedded below using XML tags:
266
+ INSTRUCTIONS
267
+
268
+ # Create context.md
269
+ context_md_content = "#{YAML.dump(frontmatter).strip}\n---\n\n#{instructions}"
270
+ context_md_path = File.join(session_dir, "context.md")
271
+ File.write(context_md_path, context_md_content)
272
+
273
+ # Load via ace-bundle to get XML-embedded documents
274
+ begin
275
+ result = Ace::Bundle.load_file(context_md_path)
276
+ result.content
277
+ rescue => e
278
+ warn "ace-bundle embedding failed: #{e.message}, falling back to direct format" if Ace::Docs.debug?
279
+ # Fallback to regular prompt if ace-bundle fails
280
+ user_prompt(documents, options)
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end