htm 0.0.20 → 0.0.30

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 (154) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/Rakefile +104 -18
  4. data/db/migrate/00001_enable_extensions.rb +9 -5
  5. data/db/migrate/00002_create_robots.rb +18 -6
  6. data/db/migrate/00003_create_file_sources.rb +30 -17
  7. data/db/migrate/00004_create_nodes.rb +60 -48
  8. data/db/migrate/00005_create_tags.rb +24 -12
  9. data/db/migrate/00006_create_node_tags.rb +28 -13
  10. data/db/migrate/00007_create_robot_nodes.rb +40 -26
  11. data/db/schema.sql +17 -1
  12. data/db/seeds.rb +33 -33
  13. data/docs/database/naming-convention.md +244 -0
  14. data/docs/database_rake_tasks.md +31 -0
  15. data/docs/development/rake-tasks.md +80 -35
  16. data/docs/guides/mcp-server.md +70 -1
  17. data/examples/.envrc +6 -0
  18. data/examples/.gitignore +2 -0
  19. data/examples/00_create_examples_db.rb +94 -0
  20. data/examples/{basic_usage.rb → 01_basic_usage.rb} +12 -16
  21. data/examples/{custom_llm_configuration.rb → 03_custom_llm_configuration.rb} +13 -3
  22. data/examples/{file_loader_usage.rb → 04_file_loader_usage.rb} +11 -14
  23. data/examples/{timeframe_demo.rb → 05_timeframe_demo.rb} +10 -3
  24. data/examples/{example_app → 06_example_app}/app.rb +15 -15
  25. data/examples/{cli_app → 07_cli_app}/htm_cli.rb +15 -22
  26. data/examples/08_sinatra_app/Gemfile.lock +241 -0
  27. data/examples/{sinatra_app → 08_sinatra_app}/app.rb +19 -18
  28. data/examples/{mcp_client.rb → 09_mcp_client.rb} +5 -8
  29. data/examples/{telemetry → 10_telemetry}/SETUP_README.md +1 -1
  30. data/examples/{telemetry → 10_telemetry}/demo.rb +14 -10
  31. data/examples/11_robot_groups/README.md +335 -0
  32. data/examples/{robot_groups → 11_robot_groups/lib}/robot_worker.rb +17 -3
  33. data/examples/{robot_groups → 11_robot_groups}/multi_process.rb +9 -9
  34. data/examples/{robot_groups → 11_robot_groups}/same_process.rb +9 -12
  35. data/examples/{rails_app → 12_rails_app}/Gemfile +3 -0
  36. data/examples/{rails_app → 12_rails_app}/Gemfile.lock +87 -58
  37. data/examples/{rails_app → 12_rails_app}/app/controllers/dashboard_controller.rb +10 -6
  38. data/examples/{rails_app → 12_rails_app}/app/controllers/files_controller.rb +5 -5
  39. data/examples/{rails_app → 12_rails_app}/app/controllers/memories_controller.rb +11 -7
  40. data/examples/{rails_app → 12_rails_app}/app/controllers/robots_controller.rb +8 -8
  41. data/examples/12_rails_app/app/controllers/tags_controller.rb +36 -0
  42. data/examples/{rails_app → 12_rails_app}/app/views/dashboard/index.html.erb +2 -2
  43. data/examples/{rails_app → 12_rails_app}/app/views/files/new.html.erb +5 -2
  44. data/examples/{rails_app → 12_rails_app}/app/views/memories/_memory_card.html.erb +3 -3
  45. data/examples/{rails_app → 12_rails_app}/app/views/memories/deleted.html.erb +3 -3
  46. data/examples/{rails_app → 12_rails_app}/app/views/memories/edit.html.erb +3 -3
  47. data/examples/{rails_app → 12_rails_app}/app/views/memories/show.html.erb +4 -4
  48. data/examples/{rails_app → 12_rails_app}/app/views/robots/index.html.erb +2 -2
  49. data/examples/{rails_app → 12_rails_app}/app/views/robots/show.html.erb +4 -4
  50. data/examples/{rails_app → 12_rails_app}/app/views/search/index.html.erb +1 -1
  51. data/examples/{rails_app → 12_rails_app}/app/views/tags/index.html.erb +2 -2
  52. data/examples/{rails_app → 12_rails_app}/app/views/tags/show.html.erb +1 -1
  53. data/examples/12_rails_app/config/initializers/htm.rb +7 -0
  54. data/examples/12_rails_app/config/initializers/rack.rb +5 -0
  55. data/examples/README.md +230 -211
  56. data/examples/examples_helper.rb +138 -0
  57. data/lib/htm/config/builder.rb +167 -0
  58. data/lib/htm/config/database.rb +317 -0
  59. data/lib/htm/config/defaults.yml +37 -9
  60. data/lib/htm/config/section.rb +74 -0
  61. data/lib/htm/config/validator.rb +83 -0
  62. data/lib/htm/config.rb +64 -360
  63. data/lib/htm/database.rb +85 -127
  64. data/lib/htm/errors.rb +14 -0
  65. data/lib/htm/integrations/sinatra.rb +13 -44
  66. data/lib/htm/jobs/generate_embedding_job.rb +3 -4
  67. data/lib/htm/jobs/generate_propositions_job.rb +4 -5
  68. data/lib/htm/jobs/generate_tags_job.rb +16 -15
  69. data/lib/htm/loaders/defaults_loader.rb +23 -0
  70. data/lib/htm/loaders/markdown_loader.rb +17 -15
  71. data/lib/htm/loaders/xdg_config_loader.rb +9 -9
  72. data/lib/htm/long_term_memory/fulltext_search.rb +14 -14
  73. data/lib/htm/long_term_memory/hybrid_search.rb +396 -229
  74. data/lib/htm/long_term_memory/node_operations.rb +24 -23
  75. data/lib/htm/long_term_memory/relevance_scorer.rb +23 -20
  76. data/lib/htm/long_term_memory/robot_operations.rb +4 -4
  77. data/lib/htm/long_term_memory/tag_operations.rb +91 -77
  78. data/lib/htm/long_term_memory/vector_search.rb +4 -5
  79. data/lib/htm/long_term_memory.rb +13 -13
  80. data/lib/htm/mcp/cli.rb +115 -8
  81. data/lib/htm/mcp/resources.rb +4 -3
  82. data/lib/htm/mcp/server.rb +5 -4
  83. data/lib/htm/mcp/tools.rb +37 -28
  84. data/lib/htm/migration.rb +72 -0
  85. data/lib/htm/models/file_source.rb +52 -31
  86. data/lib/htm/models/node.rb +224 -108
  87. data/lib/htm/models/node_tag.rb +49 -28
  88. data/lib/htm/models/robot.rb +38 -27
  89. data/lib/htm/models/robot_node.rb +63 -35
  90. data/lib/htm/models/tag.rb +126 -123
  91. data/lib/htm/observability.rb +45 -41
  92. data/lib/htm/proposition_service.rb +76 -7
  93. data/lib/htm/railtie.rb +2 -2
  94. data/lib/htm/robot_group.rb +30 -18
  95. data/lib/htm/sequel_config.rb +215 -0
  96. data/lib/htm/sql_builder.rb +14 -16
  97. data/lib/htm/tag_service.rb +78 -0
  98. data/lib/htm/tasks.rb +3 -0
  99. data/lib/htm/version.rb +1 -1
  100. data/lib/htm/workflows/remember_workflow.rb +6 -5
  101. data/lib/htm.rb +26 -22
  102. data/lib/tasks/db.rake +0 -2
  103. data/lib/tasks/doc.rake +2 -2
  104. data/lib/tasks/files.rake +11 -18
  105. data/lib/tasks/htm.rake +190 -62
  106. data/lib/tasks/jobs.rake +179 -54
  107. data/lib/tasks/tags.rake +8 -13
  108. data/scripts/backfill_parent_tags.rb +376 -0
  109. data/scripts/normalize_plural_tags.rb +335 -0
  110. metadata +109 -80
  111. data/examples/rails_app/app/controllers/tags_controller.rb +0 -30
  112. data/examples/sinatra_app/Gemfile.lock +0 -166
  113. data/lib/htm/active_record_config.rb +0 -104
  114. /data/examples/{config_file_example → 02_config_file_example}/README.md +0 -0
  115. /data/examples/{config_file_example → 02_config_file_example}/config/htm.local.yml +0 -0
  116. /data/examples/{config_file_example → 02_config_file_example}/custom_config.yml +0 -0
  117. /data/examples/{config_file_example → 02_config_file_example}/show_config.rb +0 -0
  118. /data/examples/{example_app → 06_example_app}/Rakefile +0 -0
  119. /data/examples/{cli_app → 07_cli_app}/README.md +0 -0
  120. /data/examples/{sinatra_app → 08_sinatra_app}/Gemfile +0 -0
  121. /data/examples/{telemetry → 10_telemetry}/README.md +0 -0
  122. /data/examples/{telemetry → 10_telemetry}/grafana/dashboards/htm-metrics.json +0 -0
  123. /data/examples/{rails_app → 12_rails_app}/.gitignore +0 -0
  124. /data/examples/{rails_app → 12_rails_app}/Procfile.dev +0 -0
  125. /data/examples/{rails_app → 12_rails_app}/README.md +0 -0
  126. /data/examples/{rails_app → 12_rails_app}/Rakefile +0 -0
  127. /data/examples/{rails_app → 12_rails_app}/app/assets/stylesheets/application.css +0 -0
  128. /data/examples/{rails_app → 12_rails_app}/app/assets/stylesheets/inter-font.css +0 -0
  129. /data/examples/{rails_app → 12_rails_app}/app/controllers/application_controller.rb +0 -0
  130. /data/examples/{rails_app → 12_rails_app}/app/controllers/search_controller.rb +0 -0
  131. /data/examples/{rails_app → 12_rails_app}/app/javascript/application.js +0 -0
  132. /data/examples/{rails_app → 12_rails_app}/app/javascript/controllers/application.js +0 -0
  133. /data/examples/{rails_app → 12_rails_app}/app/javascript/controllers/index.js +0 -0
  134. /data/examples/{rails_app → 12_rails_app}/app/views/files/index.html.erb +0 -0
  135. /data/examples/{rails_app → 12_rails_app}/app/views/files/show.html.erb +0 -0
  136. /data/examples/{rails_app → 12_rails_app}/app/views/layouts/application.html.erb +0 -0
  137. /data/examples/{rails_app → 12_rails_app}/app/views/memories/index.html.erb +0 -0
  138. /data/examples/{rails_app → 12_rails_app}/app/views/memories/new.html.erb +0 -0
  139. /data/examples/{rails_app → 12_rails_app}/app/views/robots/new.html.erb +0 -0
  140. /data/examples/{rails_app → 12_rails_app}/app/views/shared/_navbar.html.erb +0 -0
  141. /data/examples/{rails_app → 12_rails_app}/app/views/shared/_stat_card.html.erb +0 -0
  142. /data/examples/{rails_app → 12_rails_app}/bin/dev +0 -0
  143. /data/examples/{rails_app → 12_rails_app}/bin/rails +0 -0
  144. /data/examples/{rails_app → 12_rails_app}/bin/rake +0 -0
  145. /data/examples/{rails_app → 12_rails_app}/config/application.rb +0 -0
  146. /data/examples/{rails_app → 12_rails_app}/config/boot.rb +0 -0
  147. /data/examples/{rails_app → 12_rails_app}/config/database.yml +0 -0
  148. /data/examples/{rails_app → 12_rails_app}/config/environment.rb +0 -0
  149. /data/examples/{rails_app → 12_rails_app}/config/importmap.rb +0 -0
  150. /data/examples/{rails_app → 12_rails_app}/config/routes.rb +0 -0
  151. /data/examples/{rails_app → 12_rails_app}/config/tailwind.config.js +0 -0
  152. /data/examples/{rails_app → 12_rails_app}/config.ru +0 -0
  153. /data/examples/{rails_app → 12_rails_app}/log/.keep +0 -0
  154. /data/examples/{rails_app → 12_rails_app}/tmp/local_secret.txt +0 -0
@@ -105,29 +105,28 @@ class HTM
105
105
 
106
106
  prefix = table_alias ? "#{table_alias}." : ""
107
107
  full_column = "#{prefix}#{column}"
108
- conn = ActiveRecord::Base.connection
109
108
 
110
109
  case timeframe
111
110
  when Range
112
- begin_quoted = conn.quote(timeframe.begin.iso8601)
113
- end_quoted = conn.quote(timeframe.end.iso8601)
111
+ begin_quoted = HTM.db.literal(timeframe.begin.iso8601)
112
+ end_quoted = HTM.db.literal(timeframe.end.iso8601)
114
113
  "(#{full_column} BETWEEN #{begin_quoted} AND #{end_quoted})"
115
114
  when Array
116
115
  conditions = timeframe.map do |range|
117
- begin_quoted = conn.quote(range.begin.iso8601)
118
- end_quoted = conn.quote(range.end.iso8601)
116
+ begin_quoted = HTM.db.literal(range.begin.iso8601)
117
+ end_quoted = HTM.db.literal(range.end.iso8601)
119
118
  "(#{full_column} BETWEEN #{begin_quoted} AND #{end_quoted})"
120
119
  end
121
120
  "(#{conditions.join(' OR ')})"
122
121
  end
123
122
  end
124
123
 
125
- # Apply timeframe filter to ActiveRecord scope
124
+ # Apply timeframe filter to Sequel dataset
126
125
  #
127
- # @param scope [ActiveRecord::Relation] Base scope
126
+ # @param scope [Sequel::Dataset] Base dataset
128
127
  # @param timeframe [nil, Range, Array<Range>] Time range(s)
129
128
  # @param column [Symbol] Column name (default: :created_at)
130
- # @return [ActiveRecord::Relation] Scoped query
129
+ # @return [Sequel::Dataset] Filtered dataset
131
130
  #
132
131
  def apply_timeframe(scope, timeframe, column: :created_at)
133
132
  return scope if timeframe.nil?
@@ -136,8 +135,8 @@ class HTM
136
135
  when Range
137
136
  scope.where(column => timeframe)
138
137
  when Array
139
- conditions = timeframe.map { |range| scope.where(column => range) }
140
- conditions.reduce { |result, condition| result.or(condition) }
138
+ conditions = timeframe.map { |range| Sequel.expr(column => range) }
139
+ scope.where(Sequel.|(*conditions))
141
140
  else
142
141
  scope
143
142
  end
@@ -155,23 +154,22 @@ class HTM
155
154
 
156
155
  prefix = table_alias ? "#{table_alias}." : ""
157
156
  full_column = "#{prefix}#{column}"
158
- conn = ActiveRecord::Base.connection
159
157
 
160
- quoted_metadata = conn.quote(metadata.to_json)
158
+ quoted_metadata = HTM.db.literal(metadata.to_json)
161
159
  "(#{full_column} @> #{quoted_metadata}::jsonb)"
162
160
  end
163
161
 
164
- # Apply metadata filter to ActiveRecord scope
162
+ # Apply metadata filter to Sequel dataset
165
163
  #
166
- # @param scope [ActiveRecord::Relation] Base scope
164
+ # @param scope [Sequel::Dataset] Base dataset
167
165
  # @param metadata [Hash] Metadata to filter by
168
166
  # @param column [String] Column name (default: "metadata")
169
- # @return [ActiveRecord::Relation] Scoped query
167
+ # @return [Sequel::Dataset] Filtered dataset
170
168
  #
171
169
  def apply_metadata(scope, metadata, column: "metadata")
172
170
  return scope if metadata.nil? || metadata.empty?
173
171
 
174
- scope.where("#{column} @> ?::jsonb", metadata.to_json)
172
+ scope.where(Sequel.lit("#{column} @> ?::jsonb", metadata.to_json))
175
173
  end
176
174
  end
177
175
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'errors'
4
+ require 'active_support/core_ext/string/inflections'
4
5
 
5
6
  class HTM
6
7
  # Tag Service - Processes and validates hierarchical tags
@@ -113,6 +114,8 @@ class HTM
113
114
  valid_tags = []
114
115
 
115
116
  tags.each do |tag|
117
+ # Normalize: convert plural levels to singular
118
+ tag = singularize_tag_levels(tag)
116
119
  # Check format
117
120
  unless tag.match?(TAG_FORMAT)
118
121
  HTM.logger.warn "TagService: Invalid tag format, skipping: #{tag}"
@@ -191,5 +194,80 @@ class HTM
191
194
  depth: levels.size
192
195
  }
193
196
  end
197
+
198
+ # Words that should NOT be singularized (proper nouns, technical terms, etc.)
199
+ SINGULARIZE_SKIP_LIST = %w[
200
+ rails kubernetes aws gcp azure s3 ios macos redis postgres
201
+ postgresql mysql jenkins travis github gitlab mkdocs devops
202
+ analytics statistics mathematics physics ethics dynamics
203
+ graphics linguistics economics robotics
204
+ pages windows
205
+ ].freeze
206
+
207
+ # Normalize tag levels to singular form
208
+ #
209
+ # Converts plural levels to singular using ActiveSupport's singularize.
210
+ # This ensures taxonomy consistency (e.g., "users" -> "user").
211
+ #
212
+ # Skips:
213
+ # - Proper nouns and technical terms (Rails, MkDocs, etc.)
214
+ # - Words ending in -ics (analytics, robotics, etc.)
215
+ # - Words that don't end in common plural patterns
216
+ #
217
+ # @param tag [String] Tag with potentially plural levels
218
+ # @return [String] Tag with all levels singularized
219
+ #
220
+ def self.singularize_tag_levels(tag)
221
+ levels = tag.split(':')
222
+ singularized = levels.map do |level|
223
+ singularize_level(level)
224
+ end
225
+ singularized.join(':')
226
+ rescue NoMethodError
227
+ # singularize not available (ActiveSupport not loaded)
228
+ tag
229
+ end
230
+
231
+ # Singularize a single tag level with safety checks
232
+ #
233
+ # @param level [String] Single tag level
234
+ # @return [String] Singularized level or original if skipped
235
+ #
236
+ def self.singularize_level(level)
237
+ # Skip if in the skip list
238
+ return level if SINGULARIZE_SKIP_LIST.include?(level.downcase)
239
+
240
+ # Skip words ending in -ics (usually singular: analytics, robotics, etc.)
241
+ return level if level.end_with?('ics')
242
+
243
+ # Skip words ending in -ous (adjectives: victorious, precious, etc.)
244
+ return level if level.end_with?('ous')
245
+
246
+ # Skip words ending in -ss (class, access, etc.)
247
+ return level if level.end_with?('ss')
248
+
249
+ # Skip single-letter or very short words
250
+ return level if level.length <= 2
251
+
252
+ # Only singularize if it looks like a regular plural
253
+ # (ends in s but not ss, ics, ous)
254
+ unless level.end_with?('s')
255
+ return level
256
+ end
257
+
258
+ singular = level.singularize
259
+
260
+ # Sanity check: if singularize made it weird, keep original
261
+ # (e.g., "pages" -> "page" is fine, but "bus" -> "bu" is not)
262
+ if singular.length < level.length - 2
263
+ return level
264
+ end
265
+
266
+ if singular != level
267
+ HTM.logger.debug "TagService: Normalized '#{level}' to '#{singular}'"
268
+ end
269
+
270
+ singular
271
+ end
194
272
  end
195
273
  end
data/lib/htm/tasks.rb CHANGED
@@ -65,6 +65,9 @@
65
65
  #
66
66
 
67
67
  if defined?(Rake)
68
+ # Load HTM before loading rake tasks
69
+ require_relative '../htm'
70
+
68
71
  # Load the rake tasks
69
72
  load File.expand_path('../tasks/htm.rake', __dir__)
70
73
  load File.expand_path('../tasks/jobs.rake', __dir__)
data/lib/htm/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class HTM
4
- VERSION = '0.0.20'
4
+ VERSION = '0.0.30'
5
5
  end
@@ -135,11 +135,12 @@ class HTM
135
135
  manual_tags = result.value[:tags] || []
136
136
 
137
137
  if is_new
138
- # Add manual tags immediately
138
+ # Add manual tags immediately (including parent tags)
139
139
  if manual_tags.any?
140
140
  manual_tags.each do |tag_name|
141
- tag = HTM::Models::Tag.find_or_create_by!(name: tag_name)
142
- HTM::Models::NodeTag.find_or_create_by!(node_id: node_id, tag_id: tag.id)
141
+ HTM::Models::Tag.find_or_create_with_ancestors(tag_name).each do |tag|
142
+ HTM::Models::NodeTag.find_or_create(node_id: node_id, tag_id: tag.id)
143
+ end
143
144
  end
144
145
  end
145
146
 
@@ -152,7 +153,7 @@ class HTM
152
153
  else
153
154
  # For existing nodes, only add manual tags
154
155
  if manual_tags.any?
155
- node = HTM::Models::Node.find(node_id)
156
+ node = HTM::Models::Node[node_id]
156
157
  node.add_tags(manual_tags)
157
158
  end
158
159
  end
@@ -196,7 +197,7 @@ class HTM
196
197
  htm.working_memory.add(node_id, result.value[:content], token_count: token_count, access_count: 0)
197
198
 
198
199
  # Mark as in working memory
199
- robot_node.update!(working_memory: true)
200
+ robot_node.update(working_memory: true)
200
201
 
201
202
  # Update robot activity
202
203
  htm.long_term_memory.update_robot_activity(result.value[:robot_id])
data/lib/htm.rb CHANGED
@@ -4,7 +4,7 @@ require_relative "htm/version"
4
4
  require_relative "htm/errors"
5
5
  require_relative "htm/config"
6
6
  require_relative "htm/circuit_breaker"
7
- require_relative "htm/active_record_config"
7
+ require_relative "htm/sequel_config"
8
8
  require_relative "htm/database"
9
9
  require_relative "htm/long_term_memory"
10
10
  require_relative "htm/working_memory"
@@ -86,8 +86,8 @@ class HTM
86
86
  db_cache_size: 1000,
87
87
  db_cache_ttl: 300
88
88
  )
89
- # Establish ActiveRecord connection if not already connected
90
- HTM::ActiveRecordConfig.establish_connection! unless HTM::ActiveRecordConfig.connected?
89
+ # Establish Sequel connection if not already connected
90
+ HTM::SequelConfig.establish_connection! unless HTM::SequelConfig.db
91
91
 
92
92
  @robot_name = robot_name || "robot_#{SecureRandom.uuid[0..7]}"
93
93
 
@@ -179,7 +179,7 @@ class HTM
179
179
 
180
180
  # For existing nodes, only add manual tags if provided
181
181
  if tags.any?
182
- node = HTM::Models::Node.find(node_id)
182
+ node = HTM::Models::Node[node_id]
183
183
  node.add_tags(tags)
184
184
  HTM.logger.info "Added #{tags.length} manual tags to existing node #{node_id}"
185
185
  end
@@ -194,7 +194,7 @@ class HTM
194
194
  @working_memory.add(node_id, content, token_count: token_count, access_count: 0)
195
195
 
196
196
  # Mark node as in working memory in the robot_nodes join table
197
- result[:robot_node].update!(working_memory: true)
197
+ result[:robot_node].update(working_memory: true)
198
198
 
199
199
  update_robot_activity
200
200
  node_id
@@ -339,7 +339,7 @@ class HTM
339
339
  end
340
340
 
341
341
  # Verify node exists (including soft-deleted for restore scenarios)
342
- node = HTM::Models::Node.with_deleted.find_by(id: node_id)
342
+ node = HTM::Models::Node.with_deleted.first(id: node_id)
343
343
  raise HTM::NotFoundError, "Node not found: #{node_id}" unless node
344
344
 
345
345
  if soft
@@ -388,8 +388,8 @@ class HTM
388
388
  end
389
389
 
390
390
  # Find all nodes containing the substring (case-insensitive)
391
- matching_nodes = HTM::Models::Node.where("content ILIKE ?", "%#{content_substring}%")
392
- node_ids = matching_nodes.pluck(:id)
391
+ matching_nodes = HTM::Models::Node.where(Sequel.ilike(:content, "%#{content_substring}%"))
392
+ node_ids = matching_nodes.select_map(:id)
393
393
 
394
394
  if node_ids.empty?
395
395
  HTM.logger.info "No nodes found containing: #{content_substring}"
@@ -419,7 +419,7 @@ class HTM
419
419
  raise ArgumentError, "Node ID cannot be nil" if node_id.nil?
420
420
 
421
421
  # Find including soft-deleted nodes
422
- node = HTM::Models::Node.with_deleted.find_by(id: node_id)
422
+ node = HTM::Models::Node.with_deleted.first(id: node_id)
423
423
  raise HTM::NotFoundError, "Node not found: #{node_id}" unless node
424
424
 
425
425
  unless node.deleted?
@@ -472,7 +472,7 @@ class HTM
472
472
  # Update database: mark all as evicted from working memory
473
473
  count = HTM::Models::RobotNode
474
474
  .where(robot_id: @robot_id, working_memory: true)
475
- .update_all(working_memory: false)
475
+ .update(working_memory: false)
476
476
 
477
477
  HTM.logger.info "Cleared #{count} nodes from working memory"
478
478
  count
@@ -536,17 +536,17 @@ class HTM
536
536
  # Get all nodes loaded from a specific file
537
537
  #
538
538
  # @param file_path [String] Path to the source file
539
- # @return [ActiveRecord::Relation] Nodes from this file, ordered by chunk_position
539
+ # @return [Array<HTM::Models::Node>] Nodes from this file, ordered by chunk_position
540
540
  #
541
541
  # @example
542
542
  # nodes = htm.nodes_from_file('/path/to/doc.md')
543
543
  # nodes.each { |node| puts node.content }
544
544
  #
545
545
  def nodes_from_file(file_path)
546
- source = HTM::Models::FileSource.find_by(file_path: File.expand_path(file_path))
547
- return HTM::Models::Node.none unless source
546
+ source = HTM::Models::FileSource.first(file_path: File.expand_path(file_path))
547
+ return [] unless source
548
548
 
549
- HTM::Models::Node.from_source(source.id)
549
+ HTM::Models::Node.from_source(source.id).all
550
550
  end
551
551
 
552
552
  # Unload a file (soft-delete all its chunks and remove source record)
@@ -559,12 +559,12 @@ class HTM
559
559
  # puts "Unloaded #{count} chunks"
560
560
  #
561
561
  def unload_file(file_path)
562
- source = HTM::Models::FileSource.find_by(file_path: File.expand_path(file_path))
562
+ source = HTM::Models::FileSource.first(file_path: File.expand_path(file_path))
563
563
  return 0 unless source
564
564
 
565
565
  count = source.soft_delete_chunks!
566
566
  @long_term_memory.clear_cache!
567
- source.destroy
567
+ source.delete
568
568
 
569
569
  update_robot_activity
570
570
  count
@@ -589,11 +589,16 @@ class HTM
589
589
  end
590
590
 
591
591
  def enqueue_tags_job(node_id, manual_tags: [])
592
- # Add manual tags immediately if provided
592
+ # Add manual tags immediately if provided (including all parent tags)
593
+ # For "database:postgresql:extensions", this creates and associates:
594
+ # - "database"
595
+ # - "database:postgresql"
596
+ # - "database:postgresql:extensions"
593
597
  if manual_tags.any?
594
598
  manual_tags.each do |tag_name|
595
- tag = HTM::Models::Tag.find_or_create_by!(name: tag_name)
596
- HTM::Models::NodeTag.find_or_create_by!(node_id: node_id, tag_id: tag.id)
599
+ HTM::Models::Tag.find_or_create_with_ancestors(tag_name).each do |tag|
600
+ HTM::Models::NodeTag.find_or_create(node_id: node_id, tag_id: tag.id)
601
+ end
597
602
  end
598
603
  end
599
604
 
@@ -637,9 +642,8 @@ class HTM
637
642
  )
638
643
 
639
644
  # Mark node as in working memory in the robot_nodes join table
640
- HTM::Models::RobotNode
641
- .find_by(robot_id: @robot_id, node_id: node_id)
642
- &.update!(working_memory: true)
645
+ robot_node = HTM::Models::RobotNode.first(robot_id: @robot_id, node_id: node_id)
646
+ robot_node&.update(working_memory: true)
643
647
  end
644
648
 
645
649
  # Validation helper methods
data/lib/tasks/db.rake CHANGED
@@ -3,7 +3,6 @@
3
3
  namespace :db do
4
4
  desc "Run database migrations"
5
5
  task :migrate do
6
- require_relative '../htm'
7
6
 
8
7
  HTM::Database.migrate
9
8
  puts "Database migrations completed successfully"
@@ -11,7 +10,6 @@ namespace :db do
11
10
 
12
11
  desc "Setup database schema (includes migrations)"
13
12
  task :setup do
14
- require_relative '../htm'
15
13
 
16
14
  HTM::Database.setup
17
15
  puts "Database setup completed successfully"
data/lib/tasks/doc.rake CHANGED
@@ -206,7 +206,7 @@ namespace :htm do
206
206
  # Skip error classes and internal classes
207
207
  next if class_name.end_with?("Error")
208
208
  next if class_name.include?("Railtie")
209
- next if class_name.include?("ActiveRecordConfig")
209
+ next if class_name.include?("SequelConfig")
210
210
 
211
211
  # Get description
212
212
  simple_name = basename
@@ -228,7 +228,7 @@ namespace :htm do
228
228
  basename = File.basename(file, ".html")
229
229
  next if basename.end_with?("Error")
230
230
  next if basename == "Railtie"
231
- next if basename == "ActiveRecordConfig"
231
+ next if basename == "SequelConfig"
232
232
 
233
233
  desc = descriptions[basename] || "#{basename} class"
234
234
  classes << ["HTM::#{basename}", desc, "yard/HTM/#{basename}.html"]
data/lib/tasks/files.rake CHANGED
@@ -12,7 +12,6 @@ namespace :htm do
12
12
  namespace :files do
13
13
  desc "Load a markdown file into long-term memory. Usage: rake htm:files:load[path/to/file.md]"
14
14
  task :load, [:path] do |_t, args|
15
- require 'htm'
16
15
 
17
16
  path = args[:path]
18
17
  unless path
@@ -27,7 +26,7 @@ namespace :htm do
27
26
  end
28
27
 
29
28
  # Ensure database connection
30
- HTM::ActiveRecordConfig.establish_connection!
29
+ HTM::SequelConfig.establish_connection!
31
30
 
32
31
  htm = HTM.new(robot_name: "FileLoader")
33
32
  force = ENV['FORCE'] == 'true'
@@ -49,7 +48,6 @@ namespace :htm do
49
48
 
50
49
  desc "Load all markdown files from a directory. Usage: rake htm:files:load_dir[path/to/dir]"
51
50
  task :load_dir, [:path, :pattern] do |_t, args|
52
- require 'htm'
53
51
 
54
52
  path = args[:path]
55
53
  unless path
@@ -65,7 +63,7 @@ namespace :htm do
65
63
  end
66
64
 
67
65
  # Ensure database connection
68
- HTM::ActiveRecordConfig.establish_connection!
66
+ HTM::SequelConfig.establish_connection!
69
67
 
70
68
  htm = HTM.new(robot_name: "FileLoader")
71
69
  pattern = args[:pattern] || '**/*.md'
@@ -104,10 +102,9 @@ namespace :htm do
104
102
 
105
103
  desc "List all loaded file sources"
106
104
  task :list do
107
- require 'htm'
108
105
 
109
106
  # Ensure database connection
110
- HTM::ActiveRecordConfig.establish_connection!
107
+ HTM::SequelConfig.establish_connection!
111
108
 
112
109
  sources = HTM::Models::FileSource.order(:file_path)
113
110
  count = sources.count
@@ -136,7 +133,6 @@ namespace :htm do
136
133
 
137
134
  desc "Show details for a loaded file. Usage: rake htm:files:info[path/to/file.md]"
138
135
  task :info, [:path] do |_t, args|
139
- require 'htm'
140
136
 
141
137
  path = args[:path]
142
138
  unless path
@@ -146,11 +142,11 @@ namespace :htm do
146
142
  end
147
143
 
148
144
  # Ensure database connection
149
- HTM::ActiveRecordConfig.establish_connection!
145
+ HTM::SequelConfig.establish_connection!
150
146
 
151
147
  # Try to find by exact path or expanded path
152
- source = HTM::Models::FileSource.find_by(file_path: path) ||
153
- HTM::Models::FileSource.find_by(file_path: File.expand_path(path))
148
+ source = HTM::Models::FileSource.first(file_path: path) ||
149
+ HTM::Models::FileSource.first(file_path: File.expand_path(path))
154
150
 
155
151
  unless source
156
152
  puts "Error: File not loaded: #{path}"
@@ -192,7 +188,6 @@ namespace :htm do
192
188
 
193
189
  desc "Unload a file from memory. Usage: rake htm:files:unload[path/to/file.md]"
194
190
  task :unload, [:path] do |_t, args|
195
- require 'htm'
196
191
 
197
192
  path = args[:path]
198
193
  unless path
@@ -202,7 +197,7 @@ namespace :htm do
202
197
  end
203
198
 
204
199
  # Ensure database connection
205
- HTM::ActiveRecordConfig.establish_connection!
200
+ HTM::SequelConfig.establish_connection!
206
201
 
207
202
  htm = HTM.new(robot_name: "FileLoader")
208
203
  result = htm.unload_file(path)
@@ -216,10 +211,9 @@ namespace :htm do
216
211
 
217
212
  desc "Sync all loaded files (reload changed files)"
218
213
  task :sync do
219
- require 'htm'
220
214
 
221
215
  # Ensure database connection
222
- HTM::ActiveRecordConfig.establish_connection!
216
+ HTM::SequelConfig.establish_connection!
223
217
 
224
218
  htm = HTM.new(robot_name: "FileLoader")
225
219
  sources = HTM::Models::FileSource.all
@@ -263,18 +257,17 @@ namespace :htm do
263
257
 
264
258
  desc "Show file loading statistics"
265
259
  task :stats do
266
- require 'htm'
267
260
 
268
261
  # Ensure database connection
269
- HTM::ActiveRecordConfig.establish_connection!
262
+ HTM::SequelConfig.establish_connection!
270
263
 
271
264
  total_sources = HTM::Models::FileSource.count
272
- total_chunks = HTM::Models::Node.where.not(source_id: nil).count
265
+ total_chunks = HTM::Models::Node.exclude(source_id: nil).count
273
266
 
274
267
  # Count files needing sync (checking actual file mtime)
275
268
  needs_sync = 0
276
269
  missing = 0
277
- HTM::Models::FileSource.find_each do |source|
270
+ HTM::Models::FileSource.paged_each do |source|
278
271
  if File.exist?(source.file_path)
279
272
  current_mtime = File.mtime(source.file_path)
280
273
  needs_sync += 1 if source.needs_sync?(current_mtime)