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,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
|