Class: Ace::Docs::Models::Document
- Inherits:
-
Object
- Object
- Ace::Docs::Models::Document
- Defined in:
- lib/ace/docs/models/document.rb
Overview
Model representing a managed document with frontmatter and content
Instance Attribute Summary collapse
-
#ace_docs_config ⇒ Object
readonly
Get the ace-docs configuration namespace.
-
#content ⇒ Object
Returns the value of attribute content.
-
#context_config ⇒ Object
Returns the value of attribute context_config.
-
#doc_type ⇒ Object
Returns the value of attribute doc_type.
-
#frontmatter ⇒ Object
Returns the value of attribute frontmatter.
-
#metadata ⇒ Object
Returns the value of attribute metadata.
-
#path ⇒ Object
Returns the value of attribute path.
-
#purpose ⇒ Object
Returns the value of attribute purpose.
-
#rules ⇒ Object
Returns the value of attribute rules.
-
#update_config ⇒ Object
Returns the value of attribute update_config.
Instance Method Summary collapse
-
#<=>(other) ⇒ Object
Make documents comparable by path.
-
#==(other) ⇒ Object
Check equality.
-
#auto_generate ⇒ Object
Get auto-generation rules.
-
#context_includes ⇒ Object
Get additional context includes.
-
#context_keywords ⇒ Object
Get context keywords for LLM analysis.
-
#context_preset ⇒ Object
Get the context preset.
-
#display_name ⇒ Object
Get display name for document.
-
#focus_hints ⇒ Object
Get the focus hints for LLM analysis.
-
#freshness_status ⇒ Object
Get the freshness status Uses configurable thresholds from .ace-defaults/docs/config.yml Follows ADR-022 pattern for configuration loading.
-
#freshness_thresholds ⇒ Hash
Get freshness thresholds from configuration Falls back to historical defaults if config not available Supports frequency-specific thresholds from .ace-defaults/docs/config.yml.
-
#hash ⇒ Object
Generate hash code.
-
#initialize(path: nil, frontmatter: {}, content: "") ⇒ Document
constructor
A new instance of Document.
-
#last_checked ⇒ Date, ...
Get the last checked date or datetime.
-
#last_updated ⇒ Date, ...
Get the last updated date or datetime.
-
#managed? ⇒ Boolean
Check if document is managed by ace-docs.
-
#max_lines ⇒ Object
Get the maximum line count rule.
-
#multi_subject? ⇒ Boolean
Check if document has multi-subject configuration.
-
#needs_update? ⇒ Boolean
Check if document needs updating based on frequency and last updated date.
-
#no_duplicate_from ⇒ Object
Get documents to avoid duplication from.
-
#relative_path ⇒ Object
Get relative path from current directory.
-
#required_sections ⇒ Object
Get required sections.
-
#subject_configurations ⇒ Array<Hash>
Get structured subject configurations for multi-subject support.
-
#subject_diff_filters ⇒ Array<String>
Get subject diff filters.
-
#title ⇒ Object
Get the document title from content.
-
#to_h ⇒ Object
Convert to hash for serialization.
-
#to_s ⇒ Object
Format for display.
-
#update_frequency ⇒ Object
Get the update frequency configuration.
Constructor Details
#initialize(path: nil, frontmatter: {}, content: "") ⇒ Document
Returns a new instance of Document.
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/ace/docs/models/document.rb', line 15 def initialize(path: nil, frontmatter: {}, content: "") @path = path @frontmatter = frontmatter || {} @content = content || "" # Extract key fields from frontmatter @doc_type = @frontmatter["doc-type"] @purpose = @frontmatter["purpose"] @update_config = @frontmatter["update"] || {} @context_config = @frontmatter["context"] || {} @rules = @frontmatter["rules"] || {} @metadata = @frontmatter["metadata"] || {} # Extract ace-docs namespace configuration @ace_docs_config = @frontmatter["ace-docs"] || {} end |
Instance Attribute Details
#ace_docs_config ⇒ Object (readonly)
Get the ace-docs configuration namespace
192 193 194 |
# File 'lib/ace/docs/models/document.rb', line 192 def ace_docs_config @ace_docs_config end |
#content ⇒ Object
Returns the value of attribute content.
12 13 14 |
# File 'lib/ace/docs/models/document.rb', line 12 def content @content end |
#context_config ⇒ Object
Returns the value of attribute context_config.
12 13 14 |
# File 'lib/ace/docs/models/document.rb', line 12 def context_config @context_config end |
#doc_type ⇒ Object
Returns the value of attribute doc_type.
12 13 14 |
# File 'lib/ace/docs/models/document.rb', line 12 def doc_type @doc_type end |
#frontmatter ⇒ Object
Returns the value of attribute frontmatter.
12 13 14 |
# File 'lib/ace/docs/models/document.rb', line 12 def frontmatter @frontmatter end |
#metadata ⇒ Object
Returns the value of attribute metadata.
12 13 14 |
# File 'lib/ace/docs/models/document.rb', line 12 def @metadata end |
#path ⇒ Object
Returns the value of attribute path.
12 13 14 |
# File 'lib/ace/docs/models/document.rb', line 12 def path @path end |
#purpose ⇒ Object
Returns the value of attribute purpose.
12 13 14 |
# File 'lib/ace/docs/models/document.rb', line 12 def purpose @purpose end |
#rules ⇒ Object
Returns the value of attribute rules.
12 13 14 |
# File 'lib/ace/docs/models/document.rb', line 12 def rules @rules end |
#update_config ⇒ Object
Returns the value of attribute update_config.
12 13 14 |
# File 'lib/ace/docs/models/document.rb', line 12 def update_config @update_config end |
Instance Method Details
#<=>(other) ⇒ Object
Make documents comparable by path
347 348 349 350 |
# File 'lib/ace/docs/models/document.rb', line 347 def <=>(other) return nil unless other.is_a?(Document) (@path || "") <=> (other.path || "") end |
#==(other) ⇒ Object
Check equality
336 337 338 339 |
# File 'lib/ace/docs/models/document.rb', line 336 def ==(other) return false unless other.is_a?(Document) @path == other.path end |
#auto_generate ⇒ Object
Get auto-generation rules
272 273 274 |
# File 'lib/ace/docs/models/document.rb', line 272 def auto_generate @rules["auto-generate"] || [] end |
#context_includes ⇒ Object
Get additional context includes
252 253 254 |
# File 'lib/ace/docs/models/document.rb', line 252 def context_includes @context_config["includes"] || [] end |
#context_keywords ⇒ Object
Get context keywords for LLM analysis
237 238 239 |
# File 'lib/ace/docs/models/document.rb', line 237 def context_keywords @ace_docs_config.dig("context", "keywords") || [] end |
#context_preset ⇒ Object
Get the context preset
242 243 244 245 246 247 248 249 |
# File 'lib/ace/docs/models/document.rb', line 242 def context_preset # Try new ace-docs namespace first preset = @ace_docs_config.dig("context", "preset") return preset if preset # Fall back to legacy format @context_config["preset"] end |
#display_name ⇒ Object
Get display name for document
284 285 286 |
# File 'lib/ace/docs/models/document.rb', line 284 def display_name @path ? File.basename(@path) : "untitled.md" end |
#focus_hints ⇒ Object
Get the focus hints for LLM analysis
187 188 189 |
# File 'lib/ace/docs/models/document.rb', line 187 def focus_hints @update_config["focus"] || {} end |
#freshness_status ⇒ Object
Get the freshness status Uses configurable thresholds from .ace-defaults/docs/config.yml Follows ADR-022 pattern for configuration loading
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/ace/docs/models/document.rb', line 121 def freshness_status return :unknown unless last_updated # Convert Time to Date for comparison last_updated_date = last_updated.is_a?(Time) ? last_updated.to_date : last_updated days_since_update = (Date.today - last_updated_date).to_i thresholds = freshness_thresholds case update_frequency when "daily" if days_since_update == 0 :current elsif days_since_update <= thresholds[:daily_stale] :stale else :outdated end when "weekly" if days_since_update <= thresholds[:weekly_current] :current elsif days_since_update <= thresholds[:weekly_stale] :stale else :outdated end when "monthly" if days_since_update <= thresholds[:monthly_current] :current elsif days_since_update <= thresholds[:monthly_stale] :stale else :outdated end when "on-change" :current # Always current for on-change documents else :unknown end end |
#freshness_thresholds ⇒ Hash
Get freshness thresholds from configuration Falls back to historical defaults if config not available Supports frequency-specific thresholds from .ace-defaults/docs/config.yml
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/ace/docs/models/document.rb', line 166 def freshness_thresholds config_thresholds = Ace::Docs.config["default_freshness_days"] || {} # Extract frequency-specific thresholds with historical defaults daily_config = config_thresholds["daily"] || {} weekly_config = config_thresholds["weekly"] || {} monthly_config = config_thresholds["monthly"] || {} { # Daily frequency: current=today (0 days), stale within 2 days daily_stale: daily_config["stale"] || 2, # Weekly frequency: historical defaults 7/14 days weekly_current: weekly_config["current"] || 7, weekly_stale: weekly_config["stale"] || 14, # Monthly frequency: historical defaults 30/45 days monthly_current: monthly_config["current"] || 30, monthly_stale: monthly_config["stale"] || 45 } end |
#hash ⇒ Object
Generate hash code
342 343 344 |
# File 'lib/ace/docs/models/document.rb', line 342 def hash @path.hash end |
#last_checked ⇒ Date, ...
Get the last checked date or datetime
Polymorphic Return Type:
- Date object for date-only timestamps (YYYY-MM-DD)
- Time object (UTC) for ISO 8601 timestamps (YYYY-MM-DDTHH:MM:SSZ)
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/ace/docs/models/document.rb', line 79 def last_checked date_str = @ace_docs_config["last-checked"] || @update_config["last-checked"] return nil unless date_str result = case date_str when Date, Time date_str # Return as-is when String Atoms::TimestampParser.(date_str) end # Ensure Time objects are in UTC result.is_a?(Time) ? result.utc : result rescue ArgumentError nil end |
#last_updated ⇒ Date, ...
Get the last updated date or datetime
Polymorphic Return Type:
- Date object for date-only timestamps (YYYY-MM-DD)
- Time object (UTC) for ISO 8601 timestamps (YYYY-MM-DDTHH:MM:SSZ)
This preserves the precision of the original timestamp format. When comparing dates for freshness calculations, Time objects are converted to Date objects.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/ace/docs/models/document.rb', line 52 def last_updated # Try ace-docs namespace first date_str = @ace_docs_config["last-updated"] return nil unless date_str result = case date_str when Date, Time date_str # Return as-is when String Atoms::TimestampParser.(date_str) end # Ensure Time objects are in UTC result.is_a?(Time) ? result.utc : result rescue ArgumentError nil end |
#managed? ⇒ Boolean
Check if document is managed by ace-docs
33 34 35 |
# File 'lib/ace/docs/models/document.rb', line 33 def managed? !@doc_type.nil? && !@purpose.nil? end |
#max_lines ⇒ Object
Get the maximum line count rule
257 258 259 |
# File 'lib/ace/docs/models/document.rb', line 257 def max_lines @rules["max-lines"] end |
#multi_subject? ⇒ Boolean
Check if document has multi-subject configuration
206 207 208 209 |
# File 'lib/ace/docs/models/document.rb', line 206 def multi_subject? subject_config = @ace_docs_config["subject"] subject_config.is_a?(Array) end |
#needs_update? ⇒ Boolean
Check if document needs updating based on frequency and last updated date
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/ace/docs/models/document.rb', line 97 def needs_update? return true unless last_updated # Convert Time to Date for comparison last_updated_date = last_updated.is_a?(Time) ? last_updated.to_date : last_updated days_since_update = (Date.today - last_updated_date).to_i case update_frequency when "daily" days_since_update >= 1 when "weekly" days_since_update >= 7 when "monthly" days_since_update >= 30 when "on-change" false # Only update when changes detected else false end end |
#no_duplicate_from ⇒ Object
Get documents to avoid duplication from
267 268 269 |
# File 'lib/ace/docs/models/document.rb', line 267 def no_duplicate_from @rules["no-duplicate-from"] || [] end |
#relative_path ⇒ Object
Get relative path from current directory
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 |
# File 'lib/ace/docs/models/document.rb', line 289 def relative_path return nil unless @path begin require "pathname" # Try to get a nice relative path from current directory pwd_path = Pathname.new(Dir.pwd) file_path = Pathname.new(@path) # If file is under current directory or we can compute relative path relative = file_path.relative_path_from(pwd_path).to_s # If relative path goes up too many levels, just use absolute if relative.start_with?("../../../") @path else relative end rescue # If we can't compute relative path, use absolute @path end end |
#required_sections ⇒ Object
Get required sections
262 263 264 |
# File 'lib/ace/docs/models/document.rb', line 262 def required_sections @rules["sections"] || [] end |
#subject_configurations ⇒ Array<Hash>
Get structured subject configurations for multi-subject support
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/ace/docs/models/document.rb', line 213 def subject_configurations subject_config = @ace_docs_config["subject"] if subject_config.is_a?(Array) # Multi-subject format: array of single-key hashes # [ { "code" => { "diff" => { "filters" => [...] } } }, { "docs" => {...} } ] subject_config.map do |subject_hash| # Each item should be a hash with one key (the subject name) name = subject_hash.keys.first config = subject_hash[name] || {} filters = config.dig("diff", "filters") || [] { name: name, filters: filters } end.reject { |s| s[:filters].empty? } else # No valid subject configuration [] end end |
#subject_diff_filters ⇒ Array<String>
Get subject diff filters
196 197 198 199 200 201 202 |
# File 'lib/ace/docs/models/document.rb', line 196 def subject_diff_filters # Try new format first filters = @ace_docs_config.dig("subject", "diff", "filters") return filters if filters && !filters.empty? [] end |
#title ⇒ Object
Get the document title from content
277 278 279 280 281 |
# File 'lib/ace/docs/models/document.rb', line 277 def title # Extract first heading from content match = @content.match(/^#\s+(.+)$/m) match ? match[1].strip : File.basename(@path || "untitled", ".md") end |
#to_h ⇒ Object
Convert to hash for serialization
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/ace/docs/models/document.rb', line 314 def to_h { path: @path, doc_type: @doc_type, purpose: @purpose, title: title, last_updated: last_updated&.to_s, update_frequency: update_frequency, needs_update: needs_update?, freshness: freshness_status, rules: @rules, context: @context_config, metadata: @metadata } end |
#to_s ⇒ Object
Format for display
331 332 333 |
# File 'lib/ace/docs/models/document.rb', line 331 def to_s "Document: #{display_name} (#{@doc_type || "untyped"})" end |
#update_frequency ⇒ Object
Get the update frequency configuration
38 39 40 |
# File 'lib/ace/docs/models/document.rb', line 38 def update_frequency @update_config["frequency"] || "on-change" end |