claude_memory 0.2.0 → 0.3.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 +4 -4
- data/.claude/.mind.mv2.o2N83S +0 -0
- data/.claude/CLAUDE.md +1 -0
- data/.claude/rules/claude_memory.generated.md +28 -9
- data/.claude/settings.local.json +9 -1
- data/.claude/skills/check-memory/SKILL.md +77 -0
- data/.claude/skills/improve/SKILL.md +532 -0
- data/.claude/skills/improve/feature-patterns.md +1221 -0
- data/.claude/skills/quality-update/SKILL.md +229 -0
- data/.claude/skills/quality-update/implementation-guide.md +346 -0
- data/.claude/skills/review-commit/SKILL.md +199 -0
- data/.claude/skills/review-for-quality/SKILL.md +154 -0
- data/.claude/skills/review-for-quality/expert-checklists.md +79 -0
- data/.claude/skills/setup-memory/SKILL.md +168 -0
- data/.claude/skills/study-repo/SKILL.md +307 -0
- data/.claude/skills/study-repo/analysis-template.md +323 -0
- data/.claude/skills/study-repo/focus-examples.md +327 -0
- data/CHANGELOG.md +133 -0
- data/CLAUDE.md +130 -11
- data/README.md +117 -10
- data/db/migrations/001_create_initial_schema.rb +117 -0
- data/db/migrations/002_add_project_scoping.rb +33 -0
- data/db/migrations/003_add_session_metadata.rb +42 -0
- data/db/migrations/004_add_fact_embeddings.rb +20 -0
- data/db/migrations/005_add_incremental_sync.rb +21 -0
- data/db/migrations/006_add_operation_tracking.rb +40 -0
- data/db/migrations/007_add_ingestion_metrics.rb +26 -0
- data/docs/.claude/mind.mv2.lock +0 -0
- data/docs/GETTING_STARTED.md +587 -0
- data/docs/RELEASE_NOTES_v0.2.0.md +0 -1
- data/docs/RUBY_COMMUNITY_POST_v0.2.0.md +0 -2
- data/docs/architecture.md +9 -8
- data/docs/auto_init_design.md +230 -0
- data/docs/improvements.md +557 -731
- data/docs/influence/.gitkeep +13 -0
- data/docs/influence/grepai.md +933 -0
- data/docs/influence/qmd.md +2195 -0
- data/docs/plugin.md +257 -11
- data/docs/quality_review.md +472 -1273
- data/docs/remaining_improvements.md +330 -0
- data/lefthook.yml +13 -0
- data/lib/claude_memory/commands/checks/claude_md_check.rb +41 -0
- data/lib/claude_memory/commands/checks/database_check.rb +120 -0
- data/lib/claude_memory/commands/checks/hooks_check.rb +112 -0
- data/lib/claude_memory/commands/checks/reporter.rb +110 -0
- data/lib/claude_memory/commands/checks/snapshot_check.rb +30 -0
- data/lib/claude_memory/commands/doctor_command.rb +12 -129
- data/lib/claude_memory/commands/help_command.rb +1 -0
- data/lib/claude_memory/commands/hook_command.rb +9 -2
- data/lib/claude_memory/commands/index_command.rb +169 -0
- data/lib/claude_memory/commands/ingest_command.rb +1 -1
- data/lib/claude_memory/commands/init_command.rb +5 -197
- data/lib/claude_memory/commands/initializers/database_ensurer.rb +30 -0
- data/lib/claude_memory/commands/initializers/global_initializer.rb +85 -0
- data/lib/claude_memory/commands/initializers/hooks_configurator.rb +156 -0
- data/lib/claude_memory/commands/initializers/mcp_configurator.rb +56 -0
- data/lib/claude_memory/commands/initializers/memory_instructions_writer.rb +135 -0
- data/lib/claude_memory/commands/initializers/project_initializer.rb +111 -0
- data/lib/claude_memory/commands/recover_command.rb +75 -0
- data/lib/claude_memory/commands/registry.rb +5 -1
- data/lib/claude_memory/commands/stats_command.rb +239 -0
- data/lib/claude_memory/commands/uninstall_command.rb +226 -0
- data/lib/claude_memory/core/batch_loader.rb +32 -0
- data/lib/claude_memory/core/concept_ranker.rb +73 -0
- data/lib/claude_memory/core/embedding_candidate_builder.rb +37 -0
- data/lib/claude_memory/core/fact_collector.rb +51 -0
- data/lib/claude_memory/core/fact_query_builder.rb +154 -0
- data/lib/claude_memory/core/fact_ranker.rb +113 -0
- data/lib/claude_memory/core/result_builder.rb +54 -0
- data/lib/claude_memory/core/result_sorter.rb +25 -0
- data/lib/claude_memory/core/scope_filter.rb +61 -0
- data/lib/claude_memory/core/text_builder.rb +29 -0
- data/lib/claude_memory/embeddings/generator.rb +161 -0
- data/lib/claude_memory/embeddings/similarity.rb +69 -0
- data/lib/claude_memory/hook/handler.rb +4 -3
- data/lib/claude_memory/index/lexical_fts.rb +7 -2
- data/lib/claude_memory/infrastructure/operation_tracker.rb +158 -0
- data/lib/claude_memory/infrastructure/schema_validator.rb +206 -0
- data/lib/claude_memory/ingest/content_sanitizer.rb +6 -7
- data/lib/claude_memory/ingest/ingester.rb +99 -15
- data/lib/claude_memory/ingest/metadata_extractor.rb +57 -0
- data/lib/claude_memory/ingest/tool_extractor.rb +71 -0
- data/lib/claude_memory/mcp/response_formatter.rb +331 -0
- data/lib/claude_memory/mcp/server.rb +19 -0
- data/lib/claude_memory/mcp/setup_status_analyzer.rb +73 -0
- data/lib/claude_memory/mcp/tool_definitions.rb +279 -0
- data/lib/claude_memory/mcp/tool_helpers.rb +80 -0
- data/lib/claude_memory/mcp/tools.rb +330 -320
- data/lib/claude_memory/recall/dual_query_template.rb +63 -0
- data/lib/claude_memory/recall.rb +304 -237
- data/lib/claude_memory/resolve/resolver.rb +52 -49
- data/lib/claude_memory/store/sqlite_store.rb +210 -144
- data/lib/claude_memory/store/store_manager.rb +6 -6
- data/lib/claude_memory/sweep/sweeper.rb +6 -0
- data/lib/claude_memory/version.rb +1 -1
- data/lib/claude_memory.rb +35 -3
- metadata +71 -11
- data/.claude/.mind.mv2.aLCUZd +0 -0
- data/.claude/memory.sqlite3 +0 -0
- data/.mcp.json +0 -11
- /data/docs/{feature_adoption_plan.md → plans/feature_adoption_plan.md} +0 -0
- /data/docs/{feature_adoption_plan_revised.md → plans/feature_adoption_plan_revised.md} +0 -0
- /data/docs/{plan.md → plans/plan.md} +0 -0
- /data/docs/{updated_plan.md → plans/updated_plan.md} +0 -0
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "fileutils"
|
|
4
|
-
require "json"
|
|
5
|
-
|
|
6
3
|
module ClaudeMemory
|
|
7
4
|
module Commands
|
|
8
5
|
# Initializes ClaudeMemory in a project or globally
|
|
6
|
+
# Delegates to specialized initializer classes for actual setup
|
|
9
7
|
class InitCommand < BaseCommand
|
|
10
8
|
def call(args)
|
|
11
9
|
opts = parse_options(args, {global: false}) do |o|
|
|
@@ -15,203 +13,13 @@ module ClaudeMemory
|
|
|
15
13
|
end
|
|
16
14
|
return 1 if opts.nil?
|
|
17
15
|
|
|
18
|
-
if opts[:global]
|
|
19
|
-
|
|
16
|
+
initializer = if opts[:global]
|
|
17
|
+
Initializers::GlobalInitializer.new(stdout, stderr, stdin)
|
|
20
18
|
else
|
|
21
|
-
|
|
19
|
+
Initializers::ProjectInitializer.new(stdout, stderr, stdin)
|
|
22
20
|
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
private
|
|
26
|
-
|
|
27
|
-
def init_local
|
|
28
|
-
stdout.puts "Initializing ClaudeMemory (project-local)...\n\n"
|
|
29
|
-
|
|
30
|
-
manager = ClaudeMemory::Store::StoreManager.new
|
|
31
|
-
manager.ensure_global!
|
|
32
|
-
stdout.puts "✓ Global database: #{manager.global_db_path}"
|
|
33
|
-
manager.ensure_project!
|
|
34
|
-
stdout.puts "✓ Project database: #{manager.project_db_path}"
|
|
35
|
-
manager.close
|
|
36
|
-
|
|
37
|
-
FileUtils.mkdir_p(".claude/rules")
|
|
38
|
-
stdout.puts "✓ Created .claude/rules directory"
|
|
39
|
-
|
|
40
|
-
configure_project_hooks
|
|
41
|
-
configure_project_mcp
|
|
42
|
-
install_output_style
|
|
43
|
-
|
|
44
|
-
stdout.puts "\n=== Setup Complete ===\n"
|
|
45
|
-
stdout.puts "ClaudeMemory is now configured for this project."
|
|
46
|
-
stdout.puts "\nDatabases:"
|
|
47
|
-
stdout.puts " Global: ~/.claude/memory.sqlite3 (user-wide knowledge)"
|
|
48
|
-
stdout.puts " Project: .claude/memory.sqlite3 (project-specific)"
|
|
49
|
-
stdout.puts "\nNext steps:"
|
|
50
|
-
stdout.puts " 1. Restart Claude Code to load the new configuration"
|
|
51
|
-
stdout.puts " 2. Use Claude Code normally - transcripts will be ingested automatically"
|
|
52
|
-
stdout.puts " 3. Run 'claude-memory promote <fact_id>' to move facts to global"
|
|
53
|
-
stdout.puts " 4. Run 'claude-memory doctor' to verify setup"
|
|
54
|
-
|
|
55
|
-
0
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def init_global
|
|
59
|
-
stdout.puts "Initializing ClaudeMemory (global only)...\n\n"
|
|
60
|
-
|
|
61
|
-
manager = ClaudeMemory::Store::StoreManager.new
|
|
62
|
-
manager.ensure_global!
|
|
63
|
-
stdout.puts "✓ Created global database: #{manager.global_db_path}"
|
|
64
|
-
manager.close
|
|
65
|
-
|
|
66
|
-
configure_global_hooks
|
|
67
|
-
configure_global_mcp
|
|
68
|
-
configure_global_memory
|
|
69
|
-
|
|
70
|
-
stdout.puts "\n=== Global Setup Complete ===\n"
|
|
71
|
-
stdout.puts "ClaudeMemory is now configured globally."
|
|
72
|
-
stdout.puts "\nNote: Run 'claude-memory init' in each project for project-specific memory."
|
|
73
|
-
|
|
74
|
-
0
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def configure_global_hooks
|
|
78
|
-
settings_path = File.join(Dir.home, ".claude", "settings.json")
|
|
79
|
-
FileUtils.mkdir_p(File.dirname(settings_path))
|
|
80
|
-
|
|
81
|
-
db_path = ClaudeMemory.global_db_path
|
|
82
|
-
ingest_cmd = "claude-memory hook ingest --db #{db_path}"
|
|
83
|
-
sweep_cmd = "claude-memory hook sweep --db #{db_path}"
|
|
84
|
-
|
|
85
|
-
hooks_config = build_hooks_config(ingest_cmd, sweep_cmd)
|
|
86
|
-
|
|
87
|
-
existing = load_json_file(settings_path)
|
|
88
|
-
existing["hooks"] ||= {}
|
|
89
|
-
existing["hooks"].merge!(hooks_config["hooks"])
|
|
90
|
-
|
|
91
|
-
File.write(settings_path, JSON.pretty_generate(existing))
|
|
92
|
-
stdout.puts "✓ Configured hooks in #{settings_path}"
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def configure_global_mcp
|
|
96
|
-
mcp_path = File.join(Dir.home, ".claude.json")
|
|
97
|
-
|
|
98
|
-
existing = load_json_file(mcp_path)
|
|
99
|
-
existing["mcpServers"] ||= {}
|
|
100
|
-
existing["mcpServers"]["claude-memory"] = {
|
|
101
|
-
"type" => "stdio",
|
|
102
|
-
"command" => "claude-memory",
|
|
103
|
-
"args" => ["serve-mcp"]
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
File.write(mcp_path, JSON.pretty_generate(existing))
|
|
107
|
-
stdout.puts "✓ Configured MCP server in #{mcp_path}"
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def configure_global_memory
|
|
111
|
-
global_claude_dir = File.join(Dir.home, ".claude")
|
|
112
|
-
claude_md_path = File.join(global_claude_dir, "CLAUDE.md")
|
|
113
|
-
|
|
114
|
-
memory_instruction = <<~MD
|
|
115
|
-
# ClaudeMemory
|
|
116
|
-
|
|
117
|
-
ClaudeMemory is installed globally. Use these MCP tools:
|
|
118
|
-
- `memory.recall` - Search for relevant facts
|
|
119
|
-
- `memory.explain` - Get detailed fact provenance
|
|
120
|
-
- `memory.conflicts` - Show open contradictions
|
|
121
|
-
- `memory.status` - Check system health
|
|
122
|
-
MD
|
|
123
|
-
|
|
124
|
-
FileUtils.mkdir_p(global_claude_dir)
|
|
125
|
-
if File.exist?(claude_md_path)
|
|
126
|
-
content = File.read(claude_md_path)
|
|
127
|
-
unless content.include?("ClaudeMemory")
|
|
128
|
-
File.write(claude_md_path, content + "\n\n" + memory_instruction)
|
|
129
|
-
end
|
|
130
|
-
else
|
|
131
|
-
File.write(claude_md_path, memory_instruction)
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
stdout.puts "✓ Updated #{claude_md_path}"
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def configure_project_hooks
|
|
138
|
-
settings_path = ".claude/settings.json"
|
|
139
|
-
FileUtils.mkdir_p(File.dirname(settings_path))
|
|
140
|
-
|
|
141
|
-
db_path = ClaudeMemory.project_db_path
|
|
142
|
-
ingest_cmd = "claude-memory hook ingest --db #{db_path}"
|
|
143
|
-
sweep_cmd = "claude-memory hook sweep --db #{db_path}"
|
|
144
|
-
|
|
145
|
-
hooks_config = build_hooks_config(ingest_cmd, sweep_cmd)
|
|
146
|
-
|
|
147
|
-
existing = load_json_file(settings_path)
|
|
148
|
-
existing["hooks"] ||= {}
|
|
149
|
-
existing["hooks"].merge!(hooks_config["hooks"])
|
|
150
|
-
|
|
151
|
-
File.write(settings_path, JSON.pretty_generate(existing))
|
|
152
|
-
stdout.puts "✓ Configured hooks in #{settings_path}"
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def configure_project_mcp
|
|
156
|
-
mcp_path = ".claude.json"
|
|
157
|
-
|
|
158
|
-
existing = load_json_file(mcp_path)
|
|
159
|
-
existing["mcpServers"] ||= {}
|
|
160
|
-
existing["mcpServers"]["claude-memory"] = {
|
|
161
|
-
"type" => "stdio",
|
|
162
|
-
"command" => "claude-memory",
|
|
163
|
-
"args" => ["serve-mcp"]
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
File.write(mcp_path, JSON.pretty_generate(existing))
|
|
167
|
-
stdout.puts "✓ Configured MCP server in #{mcp_path}"
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def install_output_style
|
|
171
|
-
style_source = File.join(__dir__, "../../output_styles/claude_memory.json")
|
|
172
|
-
style_dest = ".claude/output_styles/claude_memory.json"
|
|
173
|
-
|
|
174
|
-
return unless File.exist?(style_source)
|
|
175
|
-
|
|
176
|
-
FileUtils.mkdir_p(File.dirname(style_dest))
|
|
177
|
-
FileUtils.cp(style_source, style_dest)
|
|
178
|
-
stdout.puts "✓ Installed output style at #{style_dest}"
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def build_hooks_config(ingest_cmd, sweep_cmd)
|
|
182
|
-
{
|
|
183
|
-
"hooks" => {
|
|
184
|
-
"Stop" => [{
|
|
185
|
-
"hooks" => [
|
|
186
|
-
{"type" => "command", "command" => ingest_cmd, "timeout" => 10}
|
|
187
|
-
]
|
|
188
|
-
}],
|
|
189
|
-
"SessionStart" => [{
|
|
190
|
-
"hooks" => [
|
|
191
|
-
{"type" => "command", "command" => ingest_cmd, "timeout" => 10}
|
|
192
|
-
]
|
|
193
|
-
}],
|
|
194
|
-
"PreCompact" => [{
|
|
195
|
-
"hooks" => [
|
|
196
|
-
{"type" => "command", "command" => ingest_cmd, "timeout" => 30},
|
|
197
|
-
{"type" => "command", "command" => sweep_cmd, "timeout" => 30}
|
|
198
|
-
]
|
|
199
|
-
}],
|
|
200
|
-
"SessionEnd" => [{
|
|
201
|
-
"hooks" => [
|
|
202
|
-
{"type" => "command", "command" => ingest_cmd, "timeout" => 30},
|
|
203
|
-
{"type" => "command", "command" => sweep_cmd, "timeout" => 30}
|
|
204
|
-
]
|
|
205
|
-
}]
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
end
|
|
209
21
|
|
|
210
|
-
|
|
211
|
-
return {} unless File.exist?(path)
|
|
212
|
-
JSON.parse(File.read(path))
|
|
213
|
-
rescue JSON::ParserError
|
|
214
|
-
{}
|
|
22
|
+
initializer.initialize_memory
|
|
215
23
|
end
|
|
216
24
|
end
|
|
217
25
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClaudeMemory
|
|
4
|
+
module Commands
|
|
5
|
+
module Initializers
|
|
6
|
+
# Ensures databases are created and ready
|
|
7
|
+
class DatabaseEnsurer
|
|
8
|
+
def initialize(stdout)
|
|
9
|
+
@stdout = stdout
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def ensure_project_databases
|
|
13
|
+
manager = ClaudeMemory::Store::StoreManager.new
|
|
14
|
+
manager.ensure_global!
|
|
15
|
+
@stdout.puts "✓ Global database: #{manager.global_db_path}"
|
|
16
|
+
manager.ensure_project!
|
|
17
|
+
@stdout.puts "✓ Project database: #{manager.project_db_path}"
|
|
18
|
+
manager.close
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def ensure_global_database
|
|
22
|
+
manager = ClaudeMemory::Store::StoreManager.new
|
|
23
|
+
manager.ensure_global!
|
|
24
|
+
@stdout.puts "✓ Created global database: #{manager.global_db_path}"
|
|
25
|
+
manager.close
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClaudeMemory
|
|
4
|
+
module Commands
|
|
5
|
+
module Initializers
|
|
6
|
+
# Orchestrates global ClaudeMemory initialization
|
|
7
|
+
class GlobalInitializer
|
|
8
|
+
def initialize(stdout, stderr, stdin)
|
|
9
|
+
@stdout = stdout
|
|
10
|
+
@stderr = stderr
|
|
11
|
+
@stdin = stdin
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize_memory
|
|
15
|
+
@stdout.puts "Initializing ClaudeMemory (global only)...\n\n"
|
|
16
|
+
|
|
17
|
+
# Check for existing hooks in global settings
|
|
18
|
+
hooks_config = HooksConfigurator.new(@stdout)
|
|
19
|
+
global_settings = File.join(Dir.home, ".claude", "settings.json")
|
|
20
|
+
if hooks_config.has_claude_memory_hooks?(global_settings)
|
|
21
|
+
handle_existing_hooks(hooks_config, global_settings)
|
|
22
|
+
return 0 if @skip_initialization
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
ensure_database
|
|
26
|
+
configure_hooks unless @skip_hooks
|
|
27
|
+
configure_mcp
|
|
28
|
+
configure_memory_instructions
|
|
29
|
+
|
|
30
|
+
print_completion_message
|
|
31
|
+
0
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def handle_existing_hooks(hooks_config, global_settings)
|
|
37
|
+
@stdout.puts "⚠️ Existing claude-memory hooks detected in #{global_settings}"
|
|
38
|
+
@stdout.puts "\nOptions:"
|
|
39
|
+
@stdout.puts " 1. Update to current version (recommended)"
|
|
40
|
+
@stdout.puts " 2. Remove hooks (uninstall)"
|
|
41
|
+
@stdout.puts " 3. Leave as-is (skip)"
|
|
42
|
+
@stdout.print "\nChoice [1]: "
|
|
43
|
+
|
|
44
|
+
choice = @stdin.gets.to_s.strip
|
|
45
|
+
choice = "1" if choice.empty?
|
|
46
|
+
|
|
47
|
+
case choice
|
|
48
|
+
when "2"
|
|
49
|
+
@stdout.puts "\nRemoving hooks..."
|
|
50
|
+
hooks_config.remove_hooks_from_file(global_settings)
|
|
51
|
+
@stdout.puts "✓ Hooks removed. Run 'claude-memory uninstall --global' for full cleanup."
|
|
52
|
+
@skip_initialization = true
|
|
53
|
+
when "3"
|
|
54
|
+
@stdout.puts "\nSkipping hook configuration."
|
|
55
|
+
@skip_hooks = true
|
|
56
|
+
else
|
|
57
|
+
@stdout.puts "\nUpdating hooks..."
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def ensure_database
|
|
62
|
+
DatabaseEnsurer.new(@stdout).ensure_global_database
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def configure_hooks
|
|
66
|
+
HooksConfigurator.new(@stdout).configure_global_hooks
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def configure_mcp
|
|
70
|
+
McpConfigurator.new(@stdout).configure_global_mcp
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def configure_memory_instructions
|
|
74
|
+
MemoryInstructionsWriter.new(@stdout).write_global_instructions
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def print_completion_message
|
|
78
|
+
@stdout.puts "\n=== Global Setup Complete ===\n"
|
|
79
|
+
@stdout.puts "ClaudeMemory is now configured globally."
|
|
80
|
+
@stdout.puts "\nNote: Run 'claude-memory init' in each project for project-specific memory."
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module ClaudeMemory
|
|
7
|
+
module Commands
|
|
8
|
+
module Initializers
|
|
9
|
+
# Configures Claude Code hooks for ClaudeMemory
|
|
10
|
+
class HooksConfigurator
|
|
11
|
+
def initialize(stdout)
|
|
12
|
+
@stdout = stdout
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def configure_project_hooks
|
|
16
|
+
settings_path = ".claude/settings.json"
|
|
17
|
+
FileUtils.mkdir_p(File.dirname(settings_path))
|
|
18
|
+
|
|
19
|
+
db_path = ClaudeMemory.project_db_path
|
|
20
|
+
ingest_cmd = "claude-memory hook ingest --db #{db_path}"
|
|
21
|
+
sweep_cmd = "claude-memory hook sweep --db #{db_path}"
|
|
22
|
+
|
|
23
|
+
hooks_config = build_hooks_config(ingest_cmd, sweep_cmd)
|
|
24
|
+
|
|
25
|
+
existing = load_json_file(settings_path)
|
|
26
|
+
existing["hooks"] ||= {}
|
|
27
|
+
merge_hooks!(existing["hooks"], hooks_config["hooks"])
|
|
28
|
+
|
|
29
|
+
File.write(settings_path, JSON.pretty_generate(existing))
|
|
30
|
+
@stdout.puts "✓ Configured hooks in #{settings_path}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def configure_global_hooks
|
|
34
|
+
settings_path = File.join(Dir.home, ".claude", "settings.json")
|
|
35
|
+
FileUtils.mkdir_p(File.dirname(settings_path))
|
|
36
|
+
|
|
37
|
+
db_path = ClaudeMemory.global_db_path
|
|
38
|
+
ingest_cmd = "claude-memory hook ingest --db #{db_path}"
|
|
39
|
+
sweep_cmd = "claude-memory hook sweep --db #{db_path}"
|
|
40
|
+
|
|
41
|
+
hooks_config = build_hooks_config(ingest_cmd, sweep_cmd)
|
|
42
|
+
|
|
43
|
+
existing = load_json_file(settings_path)
|
|
44
|
+
existing["hooks"] ||= {}
|
|
45
|
+
merge_hooks!(existing["hooks"], hooks_config["hooks"])
|
|
46
|
+
|
|
47
|
+
File.write(settings_path, JSON.pretty_generate(existing))
|
|
48
|
+
@stdout.puts "✓ Configured hooks in #{settings_path}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def has_claude_memory_hooks?(settings_path)
|
|
52
|
+
return false unless File.exist?(settings_path)
|
|
53
|
+
|
|
54
|
+
begin
|
|
55
|
+
config = JSON.parse(File.read(settings_path))
|
|
56
|
+
return false unless config["hooks"]
|
|
57
|
+
|
|
58
|
+
config["hooks"].values.flatten.any? do |hook_array|
|
|
59
|
+
next false unless hook_array.is_a?(Hash) && hook_array["hooks"].is_a?(Array)
|
|
60
|
+
hook_array["hooks"].any? { |h| h["command"]&.include?("claude-memory") }
|
|
61
|
+
end
|
|
62
|
+
rescue JSON::ParserError
|
|
63
|
+
false
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def remove_hooks_from_file(settings_path)
|
|
68
|
+
return unless File.exist?(settings_path)
|
|
69
|
+
|
|
70
|
+
begin
|
|
71
|
+
config = JSON.parse(File.read(settings_path))
|
|
72
|
+
rescue JSON::ParserError
|
|
73
|
+
return
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
return unless config["hooks"]
|
|
77
|
+
|
|
78
|
+
config["hooks"].each do |event, hook_arrays|
|
|
79
|
+
next unless hook_arrays.is_a?(Array)
|
|
80
|
+
|
|
81
|
+
# Filter out hook arrays that contain claude-memory commands
|
|
82
|
+
hook_arrays.reject! do |hook_array|
|
|
83
|
+
next false unless hook_array["hooks"].is_a?(Array)
|
|
84
|
+
hook_array["hooks"].any? { |h| h["command"]&.include?("claude-memory") }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Remove empty event keys
|
|
88
|
+
config["hooks"].delete(event) if hook_arrays.empty?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Remove hooks key entirely if empty
|
|
92
|
+
config.delete("hooks") if config["hooks"].empty?
|
|
93
|
+
|
|
94
|
+
File.write(settings_path, JSON.pretty_generate(config))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def build_hooks_config(ingest_cmd, sweep_cmd)
|
|
100
|
+
{
|
|
101
|
+
"hooks" => {
|
|
102
|
+
"Stop" => [{
|
|
103
|
+
"hooks" => [
|
|
104
|
+
{"type" => "command", "command" => ingest_cmd, "timeout" => 10}
|
|
105
|
+
]
|
|
106
|
+
}],
|
|
107
|
+
"SessionStart" => [{
|
|
108
|
+
"hooks" => [
|
|
109
|
+
{"type" => "command", "command" => ingest_cmd, "timeout" => 10}
|
|
110
|
+
]
|
|
111
|
+
}],
|
|
112
|
+
"PreCompact" => [{
|
|
113
|
+
"hooks" => [
|
|
114
|
+
{"type" => "command", "command" => ingest_cmd, "timeout" => 30},
|
|
115
|
+
{"type" => "command", "command" => sweep_cmd, "timeout" => 30}
|
|
116
|
+
]
|
|
117
|
+
}],
|
|
118
|
+
"SessionEnd" => [{
|
|
119
|
+
"hooks" => [
|
|
120
|
+
{"type" => "command", "command" => ingest_cmd, "timeout" => 30},
|
|
121
|
+
{"type" => "command", "command" => sweep_cmd, "timeout" => 30}
|
|
122
|
+
]
|
|
123
|
+
}]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def load_json_file(path)
|
|
129
|
+
return {} unless File.exist?(path)
|
|
130
|
+
JSON.parse(File.read(path))
|
|
131
|
+
rescue JSON::ParserError
|
|
132
|
+
{}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def merge_hooks!(existing_hooks, new_hooks)
|
|
136
|
+
new_hooks.each do |event, hook_arrays|
|
|
137
|
+
existing_hooks[event] ||= []
|
|
138
|
+
|
|
139
|
+
hook_arrays.each do |hook_array|
|
|
140
|
+
commands = hook_array["hooks"].map { |h| h["command"] }
|
|
141
|
+
|
|
142
|
+
existing_commands = existing_hooks[event].flat_map do |existing_array|
|
|
143
|
+
existing_array["hooks"]&.map { |h| h["command"] } || []
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Add hook array if none of its commands are already present
|
|
147
|
+
unless commands.any? { |cmd| existing_commands.any? { |existing| existing&.include?("claude-memory") && existing.include?(cmd.split.last) } }
|
|
148
|
+
existing_hooks[event] << hook_array
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module ClaudeMemory
|
|
7
|
+
module Commands
|
|
8
|
+
module Initializers
|
|
9
|
+
# Configures MCP server for ClaudeMemory
|
|
10
|
+
class McpConfigurator
|
|
11
|
+
def initialize(stdout)
|
|
12
|
+
@stdout = stdout
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def configure_project_mcp
|
|
16
|
+
mcp_path = ".claude.json"
|
|
17
|
+
|
|
18
|
+
existing = load_json_file(mcp_path)
|
|
19
|
+
existing["mcpServers"] ||= {}
|
|
20
|
+
existing["mcpServers"]["claude-memory"] = {
|
|
21
|
+
"type" => "stdio",
|
|
22
|
+
"command" => "claude-memory",
|
|
23
|
+
"args" => ["serve-mcp"]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
File.write(mcp_path, JSON.pretty_generate(existing))
|
|
27
|
+
@stdout.puts "✓ Configured MCP server in #{mcp_path}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def configure_global_mcp
|
|
31
|
+
mcp_path = File.join(Dir.home, ".claude.json")
|
|
32
|
+
|
|
33
|
+
existing = load_json_file(mcp_path)
|
|
34
|
+
existing["mcpServers"] ||= {}
|
|
35
|
+
existing["mcpServers"]["claude-memory"] = {
|
|
36
|
+
"type" => "stdio",
|
|
37
|
+
"command" => "claude-memory",
|
|
38
|
+
"args" => ["serve-mcp"]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
File.write(mcp_path, JSON.pretty_generate(existing))
|
|
42
|
+
@stdout.puts "✓ Configured MCP server in #{mcp_path}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def load_json_file(path)
|
|
48
|
+
return {} unless File.exist?(path)
|
|
49
|
+
JSON.parse(File.read(path))
|
|
50
|
+
rescue JSON::ParserError
|
|
51
|
+
{}
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module ClaudeMemory
|
|
6
|
+
module Commands
|
|
7
|
+
module Initializers
|
|
8
|
+
# Writes ClaudeMemory instructions to CLAUDE.md files
|
|
9
|
+
class MemoryInstructionsWriter
|
|
10
|
+
def initialize(stdout)
|
|
11
|
+
@stdout = stdout
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def write_project_instructions
|
|
15
|
+
claude_dir = ".claude"
|
|
16
|
+
claude_md_path = File.join(claude_dir, "CLAUDE.md")
|
|
17
|
+
|
|
18
|
+
memory_instruction = <<~MD
|
|
19
|
+
<!-- ClaudeMemory v#{ClaudeMemory::VERSION} -->
|
|
20
|
+
# ClaudeMemory
|
|
21
|
+
|
|
22
|
+
This project has ClaudeMemory enabled for both project-specific and global knowledge.
|
|
23
|
+
|
|
24
|
+
## Memory-First Workflow
|
|
25
|
+
|
|
26
|
+
**IMPORTANT: Check memory BEFORE reading files or exploring code.**
|
|
27
|
+
|
|
28
|
+
### Workflow Pattern
|
|
29
|
+
|
|
30
|
+
1. **Query memory first**: `memory.recall "<topic>"` or use shortcuts
|
|
31
|
+
2. **Review results**: Understand existing knowledge and decisions
|
|
32
|
+
3. **Explore if needed**: Use Read/Grep only if memory is insufficient
|
|
33
|
+
4. **Combine context**: Merge recalled facts with code exploration
|
|
34
|
+
|
|
35
|
+
### Specialized Shortcuts
|
|
36
|
+
|
|
37
|
+
- `memory.decisions` - Project decisions (ALWAYS check before implementing)
|
|
38
|
+
- `memory.conventions` - Global coding preferences
|
|
39
|
+
- `memory.architecture` - Framework choices and patterns
|
|
40
|
+
- `memory.conflicts` - Contradictions that need resolution
|
|
41
|
+
|
|
42
|
+
### Scope Awareness
|
|
43
|
+
|
|
44
|
+
- **Project facts**: Apply only to this project (e.g., "uses PostgreSQL")
|
|
45
|
+
- **Global facts**: Apply everywhere (e.g., "prefers 4-space tabs")
|
|
46
|
+
- Use `scope: "project"` or `scope: "global"` to filter queries
|
|
47
|
+
|
|
48
|
+
### When Memory Helps Most
|
|
49
|
+
|
|
50
|
+
- "Where is X handled?" → `memory.recall "X handling"`
|
|
51
|
+
- "How do we do Y?" → `memory.recall "Y pattern"`
|
|
52
|
+
- "Why did we choose Z?" → `memory.decisions`
|
|
53
|
+
- Before writing code → `memory.conventions`
|
|
54
|
+
|
|
55
|
+
See published snapshot: `.claude/rules/claude_memory.generated.md`
|
|
56
|
+
MD
|
|
57
|
+
|
|
58
|
+
FileUtils.mkdir_p(claude_dir)
|
|
59
|
+
if File.exist?(claude_md_path)
|
|
60
|
+
content = File.read(claude_md_path)
|
|
61
|
+
unless content.include?("ClaudeMemory")
|
|
62
|
+
File.write(claude_md_path, content + "\n\n" + memory_instruction)
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
File.write(claude_md_path, memory_instruction)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
@stdout.puts "✓ Updated #{claude_md_path}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def write_global_instructions
|
|
72
|
+
global_claude_dir = File.join(Dir.home, ".claude")
|
|
73
|
+
claude_md_path = File.join(global_claude_dir, "CLAUDE.md")
|
|
74
|
+
|
|
75
|
+
memory_instruction = <<~MD
|
|
76
|
+
<!-- ClaudeMemory v#{ClaudeMemory::VERSION} -->
|
|
77
|
+
# ClaudeMemory
|
|
78
|
+
|
|
79
|
+
ClaudeMemory provides long-term memory across all your sessions.
|
|
80
|
+
|
|
81
|
+
## Memory-First Workflow
|
|
82
|
+
|
|
83
|
+
**IMPORTANT: Always check memory BEFORE reading files or exploring code.**
|
|
84
|
+
|
|
85
|
+
When you receive a question or task:
|
|
86
|
+
1. **First**: Use `memory.recall` with a relevant query
|
|
87
|
+
2. **Then**: If memory is insufficient, explore with Read/Grep/Glob
|
|
88
|
+
3. **Combine**: Use recalled facts + code exploration for complete context
|
|
89
|
+
|
|
90
|
+
### When to Check Memory
|
|
91
|
+
|
|
92
|
+
- Before answering "How does X work?" questions
|
|
93
|
+
- Before implementing features (check for patterns and decisions)
|
|
94
|
+
- Before debugging (check for known issues)
|
|
95
|
+
- When you make a mistake (recall correct approach)
|
|
96
|
+
|
|
97
|
+
### Quick-Access Tools
|
|
98
|
+
|
|
99
|
+
- `memory.recall` - General knowledge search (USE THIS FIRST)
|
|
100
|
+
- `memory.decisions` - Architectural decisions and constraints
|
|
101
|
+
- `memory.conventions` - Coding style and preferences
|
|
102
|
+
- `memory.architecture` - Framework and pattern choices
|
|
103
|
+
- `memory.explain` - Detailed provenance for specific facts
|
|
104
|
+
- `memory.conflicts` - Open contradictions
|
|
105
|
+
- `memory.status` - System health check
|
|
106
|
+
|
|
107
|
+
### Example Queries
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
memory.recall "authentication flow"
|
|
111
|
+
memory.recall "error handling patterns"
|
|
112
|
+
memory.recall "database setup"
|
|
113
|
+
memory.decisions (before implementing features)
|
|
114
|
+
memory.conventions (before writing code)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The memory system contains distilled knowledge from previous sessions. Using it saves time and provides better answers.
|
|
118
|
+
MD
|
|
119
|
+
|
|
120
|
+
FileUtils.mkdir_p(global_claude_dir)
|
|
121
|
+
if File.exist?(claude_md_path)
|
|
122
|
+
content = File.read(claude_md_path)
|
|
123
|
+
unless content.include?("ClaudeMemory")
|
|
124
|
+
File.write(claude_md_path, content + "\n\n" + memory_instruction)
|
|
125
|
+
end
|
|
126
|
+
else
|
|
127
|
+
File.write(claude_md_path, memory_instruction)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
@stdout.puts "✓ Updated #{claude_md_path}"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|