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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +60 -0
- data/Rakefile +104 -18
- data/db/migrate/00001_enable_extensions.rb +9 -5
- data/db/migrate/00002_create_robots.rb +18 -6
- data/db/migrate/00003_create_file_sources.rb +30 -17
- data/db/migrate/00004_create_nodes.rb +60 -48
- data/db/migrate/00005_create_tags.rb +24 -12
- data/db/migrate/00006_create_node_tags.rb +28 -13
- data/db/migrate/00007_create_robot_nodes.rb +40 -26
- data/db/schema.sql +17 -1
- data/db/seeds.rb +33 -33
- data/docs/database/naming-convention.md +244 -0
- data/docs/database_rake_tasks.md +31 -0
- data/docs/development/rake-tasks.md +80 -35
- data/docs/guides/mcp-server.md +70 -1
- data/examples/.envrc +6 -0
- data/examples/.gitignore +2 -0
- data/examples/00_create_examples_db.rb +94 -0
- data/examples/{basic_usage.rb → 01_basic_usage.rb} +12 -16
- data/examples/{custom_llm_configuration.rb → 03_custom_llm_configuration.rb} +13 -3
- data/examples/{file_loader_usage.rb → 04_file_loader_usage.rb} +11 -14
- data/examples/{timeframe_demo.rb → 05_timeframe_demo.rb} +10 -3
- data/examples/{example_app → 06_example_app}/app.rb +15 -15
- data/examples/{cli_app → 07_cli_app}/htm_cli.rb +15 -22
- data/examples/08_sinatra_app/Gemfile.lock +241 -0
- data/examples/{sinatra_app → 08_sinatra_app}/app.rb +19 -18
- data/examples/{mcp_client.rb → 09_mcp_client.rb} +5 -8
- data/examples/{telemetry → 10_telemetry}/SETUP_README.md +1 -1
- data/examples/{telemetry → 10_telemetry}/demo.rb +14 -10
- data/examples/11_robot_groups/README.md +335 -0
- data/examples/{robot_groups → 11_robot_groups/lib}/robot_worker.rb +17 -3
- data/examples/{robot_groups → 11_robot_groups}/multi_process.rb +9 -9
- data/examples/{robot_groups → 11_robot_groups}/same_process.rb +9 -12
- data/examples/{rails_app → 12_rails_app}/Gemfile +3 -0
- data/examples/{rails_app → 12_rails_app}/Gemfile.lock +87 -58
- data/examples/{rails_app → 12_rails_app}/app/controllers/dashboard_controller.rb +10 -6
- data/examples/{rails_app → 12_rails_app}/app/controllers/files_controller.rb +5 -5
- data/examples/{rails_app → 12_rails_app}/app/controllers/memories_controller.rb +11 -7
- data/examples/{rails_app → 12_rails_app}/app/controllers/robots_controller.rb +8 -8
- data/examples/12_rails_app/app/controllers/tags_controller.rb +36 -0
- data/examples/{rails_app → 12_rails_app}/app/views/dashboard/index.html.erb +2 -2
- data/examples/{rails_app → 12_rails_app}/app/views/files/new.html.erb +5 -2
- data/examples/{rails_app → 12_rails_app}/app/views/memories/_memory_card.html.erb +3 -3
- data/examples/{rails_app → 12_rails_app}/app/views/memories/deleted.html.erb +3 -3
- data/examples/{rails_app → 12_rails_app}/app/views/memories/edit.html.erb +3 -3
- data/examples/{rails_app → 12_rails_app}/app/views/memories/show.html.erb +4 -4
- data/examples/{rails_app → 12_rails_app}/app/views/robots/index.html.erb +2 -2
- data/examples/{rails_app → 12_rails_app}/app/views/robots/show.html.erb +4 -4
- data/examples/{rails_app → 12_rails_app}/app/views/search/index.html.erb +1 -1
- data/examples/{rails_app → 12_rails_app}/app/views/tags/index.html.erb +2 -2
- data/examples/{rails_app → 12_rails_app}/app/views/tags/show.html.erb +1 -1
- data/examples/12_rails_app/config/initializers/htm.rb +7 -0
- data/examples/12_rails_app/config/initializers/rack.rb +5 -0
- data/examples/README.md +230 -211
- data/examples/examples_helper.rb +138 -0
- data/lib/htm/config/builder.rb +167 -0
- data/lib/htm/config/database.rb +317 -0
- data/lib/htm/config/defaults.yml +37 -9
- data/lib/htm/config/section.rb +74 -0
- data/lib/htm/config/validator.rb +83 -0
- data/lib/htm/config.rb +64 -360
- data/lib/htm/database.rb +85 -127
- data/lib/htm/errors.rb +14 -0
- data/lib/htm/integrations/sinatra.rb +13 -44
- data/lib/htm/jobs/generate_embedding_job.rb +3 -4
- data/lib/htm/jobs/generate_propositions_job.rb +4 -5
- data/lib/htm/jobs/generate_tags_job.rb +16 -15
- data/lib/htm/loaders/defaults_loader.rb +23 -0
- data/lib/htm/loaders/markdown_loader.rb +17 -15
- data/lib/htm/loaders/xdg_config_loader.rb +9 -9
- data/lib/htm/long_term_memory/fulltext_search.rb +14 -14
- data/lib/htm/long_term_memory/hybrid_search.rb +396 -229
- data/lib/htm/long_term_memory/node_operations.rb +24 -23
- data/lib/htm/long_term_memory/relevance_scorer.rb +23 -20
- data/lib/htm/long_term_memory/robot_operations.rb +4 -4
- data/lib/htm/long_term_memory/tag_operations.rb +91 -77
- data/lib/htm/long_term_memory/vector_search.rb +4 -5
- data/lib/htm/long_term_memory.rb +13 -13
- data/lib/htm/mcp/cli.rb +115 -8
- data/lib/htm/mcp/resources.rb +4 -3
- data/lib/htm/mcp/server.rb +5 -4
- data/lib/htm/mcp/tools.rb +37 -28
- data/lib/htm/migration.rb +72 -0
- data/lib/htm/models/file_source.rb +52 -31
- data/lib/htm/models/node.rb +224 -108
- data/lib/htm/models/node_tag.rb +49 -28
- data/lib/htm/models/robot.rb +38 -27
- data/lib/htm/models/robot_node.rb +63 -35
- data/lib/htm/models/tag.rb +126 -123
- data/lib/htm/observability.rb +45 -41
- data/lib/htm/proposition_service.rb +76 -7
- data/lib/htm/railtie.rb +2 -2
- data/lib/htm/robot_group.rb +30 -18
- data/lib/htm/sequel_config.rb +215 -0
- data/lib/htm/sql_builder.rb +14 -16
- data/lib/htm/tag_service.rb +78 -0
- data/lib/htm/tasks.rb +3 -0
- data/lib/htm/version.rb +1 -1
- data/lib/htm/workflows/remember_workflow.rb +6 -5
- data/lib/htm.rb +26 -22
- data/lib/tasks/db.rake +0 -2
- data/lib/tasks/doc.rake +2 -2
- data/lib/tasks/files.rake +11 -18
- data/lib/tasks/htm.rake +190 -62
- data/lib/tasks/jobs.rake +179 -54
- data/lib/tasks/tags.rake +8 -13
- data/scripts/backfill_parent_tags.rb +376 -0
- data/scripts/normalize_plural_tags.rb +335 -0
- metadata +109 -80
- data/examples/rails_app/app/controllers/tags_controller.rb +0 -30
- data/examples/sinatra_app/Gemfile.lock +0 -166
- data/lib/htm/active_record_config.rb +0 -104
- /data/examples/{config_file_example → 02_config_file_example}/README.md +0 -0
- /data/examples/{config_file_example → 02_config_file_example}/config/htm.local.yml +0 -0
- /data/examples/{config_file_example → 02_config_file_example}/custom_config.yml +0 -0
- /data/examples/{config_file_example → 02_config_file_example}/show_config.rb +0 -0
- /data/examples/{example_app → 06_example_app}/Rakefile +0 -0
- /data/examples/{cli_app → 07_cli_app}/README.md +0 -0
- /data/examples/{sinatra_app → 08_sinatra_app}/Gemfile +0 -0
- /data/examples/{telemetry → 10_telemetry}/README.md +0 -0
- /data/examples/{telemetry → 10_telemetry}/grafana/dashboards/htm-metrics.json +0 -0
- /data/examples/{rails_app → 12_rails_app}/.gitignore +0 -0
- /data/examples/{rails_app → 12_rails_app}/Procfile.dev +0 -0
- /data/examples/{rails_app → 12_rails_app}/README.md +0 -0
- /data/examples/{rails_app → 12_rails_app}/Rakefile +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/assets/stylesheets/application.css +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/assets/stylesheets/inter-font.css +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/controllers/application_controller.rb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/controllers/search_controller.rb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/javascript/application.js +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/javascript/controllers/application.js +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/javascript/controllers/index.js +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/views/files/index.html.erb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/views/files/show.html.erb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/views/layouts/application.html.erb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/views/memories/index.html.erb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/views/memories/new.html.erb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/views/robots/new.html.erb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/views/shared/_navbar.html.erb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/views/shared/_stat_card.html.erb +0 -0
- /data/examples/{rails_app → 12_rails_app}/bin/dev +0 -0
- /data/examples/{rails_app → 12_rails_app}/bin/rails +0 -0
- /data/examples/{rails_app → 12_rails_app}/bin/rake +0 -0
- /data/examples/{rails_app → 12_rails_app}/config/application.rb +0 -0
- /data/examples/{rails_app → 12_rails_app}/config/boot.rb +0 -0
- /data/examples/{rails_app → 12_rails_app}/config/database.yml +0 -0
- /data/examples/{rails_app → 12_rails_app}/config/environment.rb +0 -0
- /data/examples/{rails_app → 12_rails_app}/config/importmap.rb +0 -0
- /data/examples/{rails_app → 12_rails_app}/config/routes.rb +0 -0
- /data/examples/{rails_app → 12_rails_app}/config/tailwind.config.js +0 -0
- /data/examples/{rails_app → 12_rails_app}/config.ru +0 -0
- /data/examples/{rails_app → 12_rails_app}/log/.keep +0 -0
- /data/examples/{rails_app → 12_rails_app}/tmp/local_secret.txt +0 -0
data/lib/htm/sql_builder.rb
CHANGED
|
@@ -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 =
|
|
113
|
-
end_quoted =
|
|
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 =
|
|
118
|
-
end_quoted =
|
|
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
|
|
124
|
+
# Apply timeframe filter to Sequel dataset
|
|
126
125
|
#
|
|
127
|
-
# @param 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 [
|
|
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|
|
|
140
|
-
|
|
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 =
|
|
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
|
|
162
|
+
# Apply metadata filter to Sequel dataset
|
|
165
163
|
#
|
|
166
|
-
# @param 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 [
|
|
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
|
data/lib/htm/tag_service.rb
CHANGED
|
@@ -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
data/lib/htm/version.rb
CHANGED
|
@@ -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
|
-
|
|
142
|
-
|
|
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
|
|
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
|
|
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/
|
|
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
|
|
90
|
-
HTM::
|
|
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
|
|
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
|
|
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.
|
|
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(
|
|
392
|
-
node_ids = matching_nodes.
|
|
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.
|
|
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
|
-
.
|
|
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 [
|
|
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.
|
|
547
|
-
return
|
|
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.
|
|
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.
|
|
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
|
-
|
|
596
|
-
|
|
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
|
-
|
|
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?("
|
|
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 == "
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
145
|
+
HTM::SequelConfig.establish_connection!
|
|
150
146
|
|
|
151
147
|
# Try to find by exact path or expanded path
|
|
152
|
-
source = HTM::Models::FileSource.
|
|
153
|
-
HTM::Models::FileSource.
|
|
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::
|
|
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::
|
|
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::
|
|
262
|
+
HTM::SequelConfig.establish_connection!
|
|
270
263
|
|
|
271
264
|
total_sources = HTM::Models::FileSource.count
|
|
272
|
-
total_chunks = HTM::Models::Node.
|
|
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.
|
|
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)
|