Adding more features

This commit is contained in:
2026-01-15 20:58:56 -05:00
parent 84c8bcf92c
commit f5df1a9ac0
40 changed files with 9145 additions and 458 deletions

View File

@@ -111,7 +111,7 @@ function M.compute_relevance(node, opts)
return score
end
--- Traverse graph from seed nodes
--- Traverse graph from seed nodes (basic traversal)
---@param seed_ids string[] Starting node IDs
---@param depth number Traversal depth
---@param edge_types? EdgeType[] Edge types to follow
@@ -157,6 +157,73 @@ local function traverse(seed_ids, depth, edge_types)
return discovered
end
--- Spreading activation - mimics human associative memory
--- Activation spreads from seed nodes along edges, decaying by weight
--- Nodes accumulate activation from multiple paths (like neural pathways)
---@param seed_activations table<string, number> Initial activations {node_id: activation}
---@param max_iterations number Max spread iterations (default 3)
---@param decay number Activation decay per hop (default 0.5)
---@param threshold number Minimum activation to continue spreading (default 0.1)
---@return table<string, number> Final activations {node_id: accumulated_activation}
local function spreading_activation(seed_activations, max_iterations, decay, threshold)
local edge_mod = get_edge_module()
max_iterations = max_iterations or 3
decay = decay or 0.5
threshold = threshold or 0.1
-- Accumulated activation for each node
local activation = {}
for node_id, act in pairs(seed_activations) do
activation[node_id] = act
end
-- Current frontier with their activation levels
local frontier = {}
for node_id, act in pairs(seed_activations) do
frontier[node_id] = act
end
-- Spread activation iteratively
for _ = 1, max_iterations do
local next_frontier = {}
for source_id, source_activation in pairs(frontier) do
-- Get all outgoing edges
local edges = edge_mod.get_edges(source_id, nil, "both")
for _, edge in ipairs(edges) do
-- Determine target (could be source or target of edge)
local target_id = edge.s == source_id and edge.t or edge.s
-- Calculate spreading activation
-- Activation = source_activation * edge_weight * decay
local edge_weight = edge.p and edge.p.w or 0.5
local spread_amount = source_activation * edge_weight * decay
-- Only spread if above threshold
if spread_amount >= threshold then
-- Accumulate activation (multiple paths add up)
activation[target_id] = (activation[target_id] or 0) + spread_amount
-- Add to next frontier if not already processed with higher activation
if not next_frontier[target_id] or next_frontier[target_id] < spread_amount then
next_frontier[target_id] = spread_amount
end
end
end
end
-- Stop if no more spreading
if vim.tbl_count(next_frontier) == 0 then
break
end
frontier = next_frontier
end
return activation
end
--- Execute a query across all dimensions
---@param opts QueryOpts Query options
---@return QueryResult
@@ -236,28 +303,49 @@ function M.execute(opts)
end
end
-- 4. Combine and deduplicate
-- 4. Combine all found nodes and compute seed activations
local all_nodes = {}
local seed_activations = {}
for _, category in pairs(results) do
for id, node in pairs(category) do
if not all_nodes[id] then
all_nodes[id] = node
-- Compute initial activation based on relevance
local relevance = M.compute_relevance(node, opts)
seed_activations[id] = relevance
end
end
end
-- 5. Score and rank
-- 5. Apply spreading activation - like human associative memory
-- Activation spreads from seed nodes along edges, accumulating
-- Nodes connected to multiple relevant seeds get higher activation
local final_activations = spreading_activation(
seed_activations,
opts.spread_iterations or 3, -- How far activation spreads
opts.spread_decay or 0.5, -- How much activation decays per hop
opts.spread_threshold or 0.05 -- Minimum activation to continue spreading
)
-- 6. Score and rank by combined activation
local scored = {}
for id, node in pairs(all_nodes) do
local relevance = M.compute_relevance(node, opts)
table.insert(scored, { node = node, relevance = relevance })
for id, activation in pairs(final_activations) do
local node = all_nodes[id] or node_mod.get(id)
if node then
all_nodes[id] = node
-- Final score = spreading activation + base relevance
local base_relevance = M.compute_relevance(node, opts)
local final_score = (activation * 0.6) + (base_relevance * 0.4)
table.insert(scored, { node = node, relevance = final_score, activation = activation })
end
end
table.sort(scored, function(a, b)
return a.relevance > b.relevance
end)
-- 6. Apply limit
-- 7. Apply limit
local limit = opts.limit or 50
local result_nodes = {}
local truncated = #scored > limit
@@ -266,7 +354,7 @@ function M.execute(opts)
table.insert(result_nodes, scored[i].node)
end
-- 7. Get edges between result nodes
-- 8. Get edges between result nodes
local edge_mod = get_edge_module()
local result_edges = {}
local node_ids = {}
@@ -291,11 +379,17 @@ function M.execute(opts)
file_count = vim.tbl_count(results.file),
temporal_count = vim.tbl_count(results.temporal),
total_scored = #scored,
seed_nodes = vim.tbl_count(seed_activations),
activated_nodes = vim.tbl_count(final_activations),
},
truncated = truncated,
}
end
--- Expose spreading activation for direct use
--- Useful for custom activation patterns or debugging
M.spreading_activation = spreading_activation
--- Find nodes by file
---@param filepath string File path
---@param limit? number Max results

View File

@@ -9,6 +9,10 @@ local M = {}
---@param event LearnEvent Learning event
---@return boolean
function M.detect(event)
if not event or not event.type then
return false
end
local valid_types = {
"code_completion",
"file_indexed",