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,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ace/support/cli"
4
+ require "ace/core"
5
+
6
+ module Ace
7
+ module Docs
8
+ # ace-support-cli based CLI registry for ace-docs
9
+ #
10
+ # This replaces the Thor-based CLI with ace-support-cli while maintaining
11
+ # complete command parity and user-facing behavior.
12
+ module CLI
13
+ extend Ace::Support::Cli::RegistryDsl
14
+
15
+ PROGRAM_NAME = "ace-docs"
16
+
17
+ REGISTERED_COMMANDS = [
18
+ ["status", "Show documentation status"],
19
+ ["discover", "Discover undocumented items"],
20
+ ["update", "Update documentation metadata"],
21
+ ["analyze", "Analyze documentation quality"],
22
+ ["validate", "Validate documentation structure"],
23
+ ["analyze-consistency", "Check documentation consistency"]
24
+ ].freeze
25
+
26
+ HELP_EXAMPLES = [
27
+ "ace-docs status --needs-update # Docs due for refresh",
28
+ "ace-docs update docs/architecture.md # Refresh metadata",
29
+ "ace-docs analyze-consistency # Cross-doc link check"
30
+ ].freeze
31
+
32
+ # Register all commands (Hanami pattern: CLI::Commands::*)
33
+ register "status", Commands::Status.new
34
+ register "discover", Commands::Discover.new
35
+ register "update", Commands::Update.new
36
+ register "analyze", Commands::Analyze.new
37
+ register "validate", Commands::Validate.new
38
+ register "analyze-consistency", Commands::AnalyzeConsistency.new
39
+
40
+ # Register version command
41
+ version_cmd = Ace::Support::Cli::VersionCommand.build(
42
+ gem_name: "ace-docs",
43
+ version: Ace::Docs::VERSION
44
+ )
45
+ register "version", version_cmd
46
+ register "--version", version_cmd
47
+
48
+ # Register help command
49
+ help_cmd = Ace::Support::Cli::HelpCommand.build(
50
+ program_name: PROGRAM_NAME,
51
+ version: Ace::Docs::VERSION,
52
+ commands: REGISTERED_COMMANDS,
53
+ examples: HELP_EXAMPLES
54
+ )
55
+ register "help", help_cmd
56
+ register "--help", help_cmd
57
+ register "-h", help_cmd
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "fileutils"
5
+ require "ace/b36ts"
6
+
7
+ module Ace
8
+ module Docs
9
+ module Models
10
+ # Data model for analysis reports
11
+ class AnalysisReport
12
+ attr_accessor :generated, :since, :documents, :analysis, :statistics
13
+
14
+ def initialize(attributes = {})
15
+ @generated = attributes[:generated] || Time.now.utc.iso8601
16
+ @since = attributes[:since]
17
+ @documents = attributes[:documents] || []
18
+ @analysis = attributes[:analysis]
19
+ @statistics = attributes[:statistics] || {}
20
+ end
21
+
22
+ # Convert report to markdown format
23
+ # @return [String] Markdown-formatted report
24
+ def to_markdown
25
+ frontmatter = build_frontmatter
26
+ body = @analysis || "No analysis available"
27
+
28
+ <<~MARKDOWN
29
+ ---
30
+ #{frontmatter.to_yaml.strip}
31
+ ---
32
+
33
+ #{body}
34
+ MARKDOWN
35
+ end
36
+
37
+ # Save report to cache directory
38
+ # @param cache_dir [String] Cache directory path
39
+ # @return [String] Path to saved file
40
+ def save_to_cache(cache_dir = nil)
41
+ cache_dir ||= Ace::Docs.config["cache_dir"] || ".ace-local/docs"
42
+ FileUtils.mkdir_p(cache_dir)
43
+
44
+ compact_id = Ace::B36ts.encode(Time.now)
45
+ filename = "analysis-#{compact_id}.md"
46
+ filepath = File.join(cache_dir, filename)
47
+
48
+ File.write(filepath, to_markdown)
49
+ filepath
50
+ end
51
+
52
+ # Convert to hash
53
+ # @return [Hash] Report as hash
54
+ def to_h
55
+ {
56
+ generated: @generated,
57
+ since: @since,
58
+ documents: documents_list,
59
+ analysis: @analysis,
60
+ statistics: @statistics
61
+ }
62
+ end
63
+
64
+ # Load report from file
65
+ # @param filepath [String] Path to report file
66
+ # @return [AnalysisReport] Loaded report
67
+ def self.load_from_file(filepath)
68
+ content = File.read(filepath)
69
+
70
+ # Parse YAML frontmatter
71
+ if content =~ /\A---\n(.*?)\n---\n(.*)/m
72
+ frontmatter = YAML.safe_load(Regexp.last_match(1))
73
+ body = Regexp.last_match(2)
74
+
75
+ new(
76
+ generated: frontmatter["generated"],
77
+ since: frontmatter["since"],
78
+ documents: parse_documents(frontmatter["document_list"]),
79
+ analysis: body.strip,
80
+ statistics: frontmatter["statistics"] || {}
81
+ )
82
+ else
83
+ # No frontmatter, treat entire content as analysis
84
+ new(analysis: content)
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ def build_frontmatter
91
+ {
92
+ "generated" => @generated,
93
+ "since" => @since,
94
+ "documents" => @documents.size,
95
+ "document_list" => documents_list,
96
+ "statistics" => @statistics
97
+ }
98
+ end
99
+
100
+ def documents_list
101
+ @documents.map do |doc|
102
+ if doc.respond_to?(:relative_path)
103
+ doc.relative_path || doc.path
104
+ elsif doc.is_a?(Hash)
105
+ doc["path"] || doc[:path]
106
+ else
107
+ doc.to_s
108
+ end
109
+ end
110
+ end
111
+
112
+ def self.parse_documents(doc_list)
113
+ return [] unless doc_list
114
+
115
+ doc_list.map { |path| {path: path} }
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,259 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "time"
5
+
6
+ module Ace
7
+ module Docs
8
+ module Models
9
+ # Model for storing and formatting consistency analysis results
10
+ class ConsistencyReport
11
+ attr_reader :terminology_conflicts, :duplicate_content,
12
+ :version_inconsistencies, :consolidation_opportunities,
13
+ :generated_at, :document_count, :raw_response
14
+
15
+ def initialize(data = {})
16
+ @terminology_conflicts = data[:terminology_conflicts] || []
17
+ @duplicate_content = data[:duplicate_content] || []
18
+ @version_inconsistencies = data[:version_inconsistencies] || []
19
+ @consolidation_opportunities = data[:consolidation_opportunities] || []
20
+ @generated_at = data[:generated_at] || Time.now
21
+ @document_count = data[:document_count] || 0
22
+ @raw_response = data[:raw_response]
23
+ end
24
+
25
+ # Parse LLM response into ConsistencyReport
26
+ # @param response [String] JSON response from LLM
27
+ # @param document_count [Integer] number of documents analyzed
28
+ # @return [ConsistencyReport] parsed report
29
+ def self.parse(response, document_count = 0)
30
+ data = JSON.parse(response, symbolize_names: true)
31
+
32
+ new(
33
+ terminology_conflicts: data[:terminology_conflicts] || [],
34
+ duplicate_content: data[:duplicate_content] || [],
35
+ version_inconsistencies: data[:version_inconsistencies] || [],
36
+ consolidation_opportunities: data[:consolidation_opportunities] || [],
37
+ document_count: document_count,
38
+ generated_at: Time.now,
39
+ raw_response: response
40
+ )
41
+ rescue JSON::ParserError
42
+ # If parsing fails, create a report with the raw response
43
+ new(
44
+ raw_response: response,
45
+ document_count: document_count,
46
+ generated_at: Time.now
47
+ )
48
+ end
49
+
50
+ # Check if the report has any issues
51
+ def has_issues?
52
+ total_issues > 0
53
+ end
54
+
55
+ # Get total number of issues found
56
+ def total_issues
57
+ terminology_conflicts.size +
58
+ duplicate_content.size +
59
+ version_inconsistencies.size +
60
+ consolidation_opportunities.size
61
+ end
62
+
63
+ # Check if parsing was successful
64
+ def parsing_successful?
65
+ !raw_response.nil? && total_issues >= 0
66
+ end
67
+
68
+ # Convert report to markdown format
69
+ def to_markdown
70
+ output = []
71
+
72
+ output << "# Cross-Document Consistency Report"
73
+ output << ""
74
+ output << "Generated: #{generated_at.strftime("%Y-%m-%d %H:%M:%S")}"
75
+ output << "Documents analyzed: #{document_count}"
76
+ output << "Issues found: #{total_issues}"
77
+ output << ""
78
+
79
+ if !parsing_successful? && raw_response
80
+ output << "## Warning: Could not parse LLM response"
81
+ output << ""
82
+ output << "Raw output:"
83
+ output << "```"
84
+ output << raw_response
85
+ output << "```"
86
+ return output.join("\n")
87
+ end
88
+
89
+ if terminology_conflicts.any?
90
+ output << "## Terminology Conflicts (#{terminology_conflicts.size})"
91
+ output << ""
92
+
93
+ terminology_conflicts.each do |conflict|
94
+ output << format_terminology_conflict(conflict)
95
+ output << ""
96
+ end
97
+ end
98
+
99
+ if duplicate_content.any?
100
+ output << "## Duplicate Content (#{duplicate_content.size})"
101
+ output << ""
102
+
103
+ duplicate_content.each do |duplicate|
104
+ output << format_duplicate_content(duplicate)
105
+ output << ""
106
+ end
107
+ end
108
+
109
+ if version_inconsistencies.any?
110
+ output << "## Version Inconsistencies (#{version_inconsistencies.size})"
111
+ output << ""
112
+
113
+ version_inconsistencies.each do |version_issue|
114
+ output << format_version_inconsistency(version_issue)
115
+ output << ""
116
+ end
117
+ end
118
+
119
+ if consolidation_opportunities.any?
120
+ output << "## Consolidation Opportunities (#{consolidation_opportunities.size})"
121
+ output << ""
122
+
123
+ consolidation_opportunities.each do |opportunity|
124
+ output << format_consolidation_opportunity(opportunity)
125
+ output << ""
126
+ end
127
+ end
128
+
129
+ if total_issues == 0
130
+ output << "## No Issues Found"
131
+ output << ""
132
+ output << "✅ All documents appear to be consistent!"
133
+ end
134
+
135
+ output.join("\n")
136
+ end
137
+
138
+ # Convert report to JSON format
139
+ def to_json(*args)
140
+ {
141
+ generated_at: generated_at.iso8601,
142
+ document_count: document_count,
143
+ total_issues: total_issues,
144
+ terminology_conflicts: terminology_conflicts,
145
+ duplicate_content: duplicate_content,
146
+ version_inconsistencies: version_inconsistencies,
147
+ consolidation_opportunities: consolidation_opportunities
148
+ }.to_json(*args)
149
+ end
150
+
151
+ private
152
+
153
+ # Format a terminology conflict for markdown output
154
+ def format_terminology_conflict(conflict)
155
+ lines = []
156
+
157
+ terms = conflict[:terms] || conflict["terms"] || []
158
+ lines << "### \"#{terms[0]}\" vs \"#{terms[1]}\""
159
+
160
+ occurrences = conflict[:occurrences] || conflict["occurrences"] || {}
161
+
162
+ terms.each do |term|
163
+ term_occurrences = occurrences[term.to_sym] || occurrences[term.to_s] || []
164
+ next if term_occurrences.empty?
165
+
166
+ term_occurrences.each do |occurrence|
167
+ file = occurrence[:file] || occurrence["file"]
168
+ count = occurrence[:count] || occurrence["count"]
169
+ examples = occurrence[:examples] || occurrence["examples"] || []
170
+
171
+ lines << "- #{file}: uses \"#{term}\" (#{count} occurrences)"
172
+ if examples.any?
173
+ lines << " Examples: #{examples.first(2).join("; ")}"
174
+ end
175
+ end
176
+ end
177
+
178
+ recommendation = conflict[:recommendation] || conflict["recommendation"]
179
+ lines << "**Recommendation**: #{recommendation}" if recommendation
180
+
181
+ lines.join("\n")
182
+ end
183
+
184
+ # Format duplicate content for markdown output
185
+ def format_duplicate_content(duplicate)
186
+ lines = []
187
+
188
+ description = duplicate[:description] || duplicate["description"] || "Duplicate content"
189
+ similarity = duplicate[:similarity_percentage] || duplicate["similarity_percentage"]
190
+
191
+ lines << "### #{description}"
192
+ lines << "Files with duplicate content (#{similarity}% similarity):" if similarity
193
+
194
+ locations = duplicate[:locations] || duplicate["locations"] || []
195
+ locations.each do |location|
196
+ file = location[:file] || location["file"]
197
+ line_range = location[:lines] || location["lines"]
198
+ excerpt = location[:excerpt] || location["excerpt"]
199
+
200
+ lines << "- #{file} (lines #{line_range})"
201
+ lines << " \"#{excerpt}\"" if excerpt
202
+ end
203
+
204
+ recommendation = duplicate[:recommendation] || duplicate["recommendation"]
205
+ lines << "**Recommendation**: #{recommendation}" if recommendation
206
+
207
+ lines.join("\n")
208
+ end
209
+
210
+ # Format version inconsistency for markdown output
211
+ def format_version_inconsistency(version_issue)
212
+ lines = []
213
+
214
+ item = version_issue[:item] || version_issue["item"] || "Version"
215
+ lines << "### #{item}"
216
+
217
+ versions = version_issue[:versions_found] || version_issue["versions_found"] || []
218
+ versions.each do |version_info|
219
+ version = version_info[:version] || version_info["version"]
220
+ file = version_info[:file] || version_info["file"]
221
+ line = version_info[:line] || version_info["line"]
222
+
223
+ lines << "- #{file}: \"#{version}\""
224
+ lines << " (line #{line})" if line
225
+ end
226
+
227
+ recommendation = version_issue[:recommendation] || version_issue["recommendation"]
228
+ lines << "**Recommendation**: #{recommendation}" if recommendation
229
+
230
+ lines.join("\n")
231
+ end
232
+
233
+ # Format consolidation opportunity for markdown output
234
+ def format_consolidation_opportunity(opportunity)
235
+ lines = []
236
+
237
+ topic = opportunity[:topic] || opportunity["topic"] || "Related content"
238
+ lines << "### #{topic}"
239
+
240
+ lines << "Multiple documents explain similar content:"
241
+
242
+ documents = opportunity[:documents] || opportunity["documents"] || []
243
+ documents.each do |doc|
244
+ file = doc[:file] || doc["file"]
245
+ coverage = doc[:coverage] || doc["coverage"]
246
+
247
+ lines << "- #{file}"
248
+ lines << " Coverage: #{coverage}" if coverage
249
+ end
250
+
251
+ recommendation = opportunity[:recommendation] || opportunity["recommendation"]
252
+ lines << "**Recommendation**: #{recommendation}" if recommendation
253
+
254
+ lines.join("\n")
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end