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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/.mind.mv2.o2N83S +0 -0
  3. data/.claude/CLAUDE.md +1 -0
  4. data/.claude/rules/claude_memory.generated.md +28 -9
  5. data/.claude/settings.local.json +9 -1
  6. data/.claude/skills/check-memory/SKILL.md +77 -0
  7. data/.claude/skills/improve/SKILL.md +532 -0
  8. data/.claude/skills/improve/feature-patterns.md +1221 -0
  9. data/.claude/skills/quality-update/SKILL.md +229 -0
  10. data/.claude/skills/quality-update/implementation-guide.md +346 -0
  11. data/.claude/skills/review-commit/SKILL.md +199 -0
  12. data/.claude/skills/review-for-quality/SKILL.md +154 -0
  13. data/.claude/skills/review-for-quality/expert-checklists.md +79 -0
  14. data/.claude/skills/setup-memory/SKILL.md +168 -0
  15. data/.claude/skills/study-repo/SKILL.md +307 -0
  16. data/.claude/skills/study-repo/analysis-template.md +323 -0
  17. data/.claude/skills/study-repo/focus-examples.md +327 -0
  18. data/CHANGELOG.md +133 -0
  19. data/CLAUDE.md +130 -11
  20. data/README.md +117 -10
  21. data/db/migrations/001_create_initial_schema.rb +117 -0
  22. data/db/migrations/002_add_project_scoping.rb +33 -0
  23. data/db/migrations/003_add_session_metadata.rb +42 -0
  24. data/db/migrations/004_add_fact_embeddings.rb +20 -0
  25. data/db/migrations/005_add_incremental_sync.rb +21 -0
  26. data/db/migrations/006_add_operation_tracking.rb +40 -0
  27. data/db/migrations/007_add_ingestion_metrics.rb +26 -0
  28. data/docs/.claude/mind.mv2.lock +0 -0
  29. data/docs/GETTING_STARTED.md +587 -0
  30. data/docs/RELEASE_NOTES_v0.2.0.md +0 -1
  31. data/docs/RUBY_COMMUNITY_POST_v0.2.0.md +0 -2
  32. data/docs/architecture.md +9 -8
  33. data/docs/auto_init_design.md +230 -0
  34. data/docs/improvements.md +557 -731
  35. data/docs/influence/.gitkeep +13 -0
  36. data/docs/influence/grepai.md +933 -0
  37. data/docs/influence/qmd.md +2195 -0
  38. data/docs/plugin.md +257 -11
  39. data/docs/quality_review.md +472 -1273
  40. data/docs/remaining_improvements.md +330 -0
  41. data/lefthook.yml +13 -0
  42. data/lib/claude_memory/commands/checks/claude_md_check.rb +41 -0
  43. data/lib/claude_memory/commands/checks/database_check.rb +120 -0
  44. data/lib/claude_memory/commands/checks/hooks_check.rb +112 -0
  45. data/lib/claude_memory/commands/checks/reporter.rb +110 -0
  46. data/lib/claude_memory/commands/checks/snapshot_check.rb +30 -0
  47. data/lib/claude_memory/commands/doctor_command.rb +12 -129
  48. data/lib/claude_memory/commands/help_command.rb +1 -0
  49. data/lib/claude_memory/commands/hook_command.rb +9 -2
  50. data/lib/claude_memory/commands/index_command.rb +169 -0
  51. data/lib/claude_memory/commands/ingest_command.rb +1 -1
  52. data/lib/claude_memory/commands/init_command.rb +5 -197
  53. data/lib/claude_memory/commands/initializers/database_ensurer.rb +30 -0
  54. data/lib/claude_memory/commands/initializers/global_initializer.rb +85 -0
  55. data/lib/claude_memory/commands/initializers/hooks_configurator.rb +156 -0
  56. data/lib/claude_memory/commands/initializers/mcp_configurator.rb +56 -0
  57. data/lib/claude_memory/commands/initializers/memory_instructions_writer.rb +135 -0
  58. data/lib/claude_memory/commands/initializers/project_initializer.rb +111 -0
  59. data/lib/claude_memory/commands/recover_command.rb +75 -0
  60. data/lib/claude_memory/commands/registry.rb +5 -1
  61. data/lib/claude_memory/commands/stats_command.rb +239 -0
  62. data/lib/claude_memory/commands/uninstall_command.rb +226 -0
  63. data/lib/claude_memory/core/batch_loader.rb +32 -0
  64. data/lib/claude_memory/core/concept_ranker.rb +73 -0
  65. data/lib/claude_memory/core/embedding_candidate_builder.rb +37 -0
  66. data/lib/claude_memory/core/fact_collector.rb +51 -0
  67. data/lib/claude_memory/core/fact_query_builder.rb +154 -0
  68. data/lib/claude_memory/core/fact_ranker.rb +113 -0
  69. data/lib/claude_memory/core/result_builder.rb +54 -0
  70. data/lib/claude_memory/core/result_sorter.rb +25 -0
  71. data/lib/claude_memory/core/scope_filter.rb +61 -0
  72. data/lib/claude_memory/core/text_builder.rb +29 -0
  73. data/lib/claude_memory/embeddings/generator.rb +161 -0
  74. data/lib/claude_memory/embeddings/similarity.rb +69 -0
  75. data/lib/claude_memory/hook/handler.rb +4 -3
  76. data/lib/claude_memory/index/lexical_fts.rb +7 -2
  77. data/lib/claude_memory/infrastructure/operation_tracker.rb +158 -0
  78. data/lib/claude_memory/infrastructure/schema_validator.rb +206 -0
  79. data/lib/claude_memory/ingest/content_sanitizer.rb +6 -7
  80. data/lib/claude_memory/ingest/ingester.rb +99 -15
  81. data/lib/claude_memory/ingest/metadata_extractor.rb +57 -0
  82. data/lib/claude_memory/ingest/tool_extractor.rb +71 -0
  83. data/lib/claude_memory/mcp/response_formatter.rb +331 -0
  84. data/lib/claude_memory/mcp/server.rb +19 -0
  85. data/lib/claude_memory/mcp/setup_status_analyzer.rb +73 -0
  86. data/lib/claude_memory/mcp/tool_definitions.rb +279 -0
  87. data/lib/claude_memory/mcp/tool_helpers.rb +80 -0
  88. data/lib/claude_memory/mcp/tools.rb +330 -320
  89. data/lib/claude_memory/recall/dual_query_template.rb +63 -0
  90. data/lib/claude_memory/recall.rb +304 -237
  91. data/lib/claude_memory/resolve/resolver.rb +52 -49
  92. data/lib/claude_memory/store/sqlite_store.rb +210 -144
  93. data/lib/claude_memory/store/store_manager.rb +6 -6
  94. data/lib/claude_memory/sweep/sweeper.rb +6 -0
  95. data/lib/claude_memory/version.rb +1 -1
  96. data/lib/claude_memory.rb +35 -3
  97. metadata +71 -11
  98. data/.claude/.mind.mv2.aLCUZd +0 -0
  99. data/.claude/memory.sqlite3 +0 -0
  100. data/.mcp.json +0 -11
  101. /data/docs/{feature_adoption_plan.md → plans/feature_adoption_plan.md} +0 -0
  102. /data/docs/{feature_adoption_plan_revised.md → plans/feature_adoption_plan_revised.md} +0 -0
  103. /data/docs/{plan.md → plans/plan.md} +0 -0
  104. /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
- init_global
16
+ initializer = if opts[:global]
17
+ Initializers::GlobalInitializer.new(stdout, stderr, stdin)
20
18
  else
21
- init_local
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
- def load_json_file(path)
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