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.
- checksums.yaml +7 -0
- data/.ace-defaults/docs/config.yml +169 -0
- data/.ace-defaults/docs/multi-subject-example.md +130 -0
- data/.ace-defaults/docs/single-subject-example.md +150 -0
- data/.ace-defaults/nav/protocols/guide-sources/ace-docs.yml +10 -0
- data/.ace-defaults/nav/protocols/prompt-sources/ace-docs.yml +34 -0
- data/.ace-defaults/nav/protocols/tmpl-sources/ace-docs.yml +10 -0
- data/.ace-defaults/nav/protocols/wfi-sources/ace-docs.yml +19 -0
- data/CHANGELOG.md +1082 -0
- data/LICENSE +21 -0
- data/README.md +40 -0
- data/Rakefile +14 -0
- data/exe/ace-docs +14 -0
- data/handbook/guides/documentation/ruby.md +16 -0
- data/handbook/guides/documentation/rust.md +35 -0
- data/handbook/guides/documentation/typescript.md +18 -0
- data/handbook/guides/documentation.g.md +437 -0
- data/handbook/guides/documents-embedded-sync.g.md +473 -0
- data/handbook/guides/documents-embedding.g.md +276 -0
- data/handbook/guides/markdown-style.g.md +290 -0
- data/handbook/prompts/ace-change-analyzer.system.md +113 -0
- data/handbook/prompts/ace-change-analyzer.user.md +95 -0
- data/handbook/prompts/document-analysis.md +74 -0
- data/handbook/prompts/document-analysis.system.md +129 -0
- data/handbook/prompts/markdown-style.system.md +113 -0
- data/handbook/skills/as-docs-create-adr/SKILL.md +35 -0
- data/handbook/skills/as-docs-create-api/SKILL.md +35 -0
- data/handbook/skills/as-docs-create-user/SKILL.md +35 -0
- data/handbook/skills/as-docs-maintain-adrs/SKILL.md +35 -0
- data/handbook/skills/as-docs-squash-changelog/SKILL.md +42 -0
- data/handbook/skills/as-docs-update/SKILL.md +36 -0
- data/handbook/skills/as-docs-update-blueprint/SKILL.md +28 -0
- data/handbook/skills/as-docs-update-roadmap/SKILL.md +24 -0
- data/handbook/skills/as-docs-update-tools/SKILL.md +36 -0
- data/handbook/skills/as-docs-update-usage/SKILL.md +26 -0
- data/handbook/templates/code-docs/javascript-jsdoc.template.md +102 -0
- data/handbook/templates/code-docs/ruby-yard.template.md +85 -0
- data/handbook/templates/project-docs/README.template.md +73 -0
- data/handbook/templates/project-docs/architecture.template.md +300 -0
- data/handbook/templates/project-docs/blueprint.template.md +165 -0
- data/handbook/templates/project-docs/context/ownership.yml +160 -0
- data/handbook/templates/project-docs/decisions/adr.template.md +60 -0
- data/handbook/templates/project-docs/prd.template.md +144 -0
- data/handbook/templates/project-docs/roadmap/roadmap.template.md +47 -0
- data/handbook/templates/project-docs/vision.template.md +233 -0
- data/handbook/templates/user-docs/user-guide.template.md +107 -0
- data/handbook/workflow-instructions/docs/create-adr.wf.md +334 -0
- data/handbook/workflow-instructions/docs/create-api.wf.md +448 -0
- data/handbook/workflow-instructions/docs/create-cookbook.wf.md +434 -0
- data/handbook/workflow-instructions/docs/create-user.wf.md +399 -0
- data/handbook/workflow-instructions/docs/maintain-adrs.wf.md +589 -0
- data/handbook/workflow-instructions/docs/squash-changelog.wf.md +246 -0
- data/handbook/workflow-instructions/docs/update-blueprint.wf.md +361 -0
- data/handbook/workflow-instructions/docs/update-context.wf.md +336 -0
- data/handbook/workflow-instructions/docs/update-roadmap.wf.md +421 -0
- data/handbook/workflow-instructions/docs/update-tools.wf.md +307 -0
- data/handbook/workflow-instructions/docs/update-usage.wf.md +710 -0
- data/handbook/workflow-instructions/docs/update.wf.md +418 -0
- data/lib/ace/docs/atoms/diff_filterer.rb +131 -0
- data/lib/ace/docs/atoms/frontmatter_free_matcher.rb +20 -0
- data/lib/ace/docs/atoms/git_date_resolver.rb +16 -0
- data/lib/ace/docs/atoms/readme_metadata_inferrer.rb +60 -0
- data/lib/ace/docs/atoms/terminology_extractor.rb +308 -0
- data/lib/ace/docs/atoms/time_range_calculator.rb +96 -0
- data/lib/ace/docs/atoms/timestamp_parser.rb +106 -0
- data/lib/ace/docs/atoms/type_inferrer.rb +70 -0
- data/lib/ace/docs/cli/commands/analyze.rb +351 -0
- data/lib/ace/docs/cli/commands/analyze_consistency.rb +185 -0
- data/lib/ace/docs/cli/commands/discover.rb +75 -0
- data/lib/ace/docs/cli/commands/scope_options.rb +71 -0
- data/lib/ace/docs/cli/commands/status.rb +241 -0
- data/lib/ace/docs/cli/commands/update.rb +198 -0
- data/lib/ace/docs/cli/commands/validate.rb +225 -0
- data/lib/ace/docs/cli.rb +60 -0
- data/lib/ace/docs/models/analysis_report.rb +120 -0
- data/lib/ace/docs/models/consistency_report.rb +259 -0
- data/lib/ace/docs/models/document.rb +354 -0
- data/lib/ace/docs/molecules/change_detector.rb +389 -0
- data/lib/ace/docs/molecules/document_loader.rb +133 -0
- data/lib/ace/docs/molecules/frontmatter_manager.rb +85 -0
- data/lib/ace/docs/molecules/git_date_resolver.rb +30 -0
- data/lib/ace/docs/organisms/cross_document_analyzer.rb +274 -0
- data/lib/ace/docs/organisms/document_registry.rb +318 -0
- data/lib/ace/docs/organisms/validator.rb +164 -0
- data/lib/ace/docs/prompts/compact_diff_prompt.rb +119 -0
- data/lib/ace/docs/prompts/consistency_prompt.rb +286 -0
- data/lib/ace/docs/prompts/document_analysis_prompt.rb +389 -0
- data/lib/ace/docs/version.rb +7 -0
- data/lib/ace/docs.rb +82 -0
- data/lib/test.rb +4 -0
- 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
|
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