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
@@ -0,0 +1,330 @@
1
+ # Remaining Improvements from Analysis
2
+
3
+ This document contains the improvements that have NOT yet been implemented from the episodic-memory and claude-mem analysis.
4
+
5
+ **Note:** The "index" command to generate embeddings for existing facts has been completed (2026-01-23).
6
+
7
+ ---
8
+
9
+ ## 1. Background Processing for Hooks
10
+
11
+ ### What Episodic-Memory Does
12
+
13
+ **Background Sync**:
14
+ ```bash
15
+ # SessionStart hook
16
+ episodic-memory sync --background
17
+ ```
18
+
19
+ Runs in background, user continues working immediately.
20
+
21
+ **File**: `src/sync.ts`
22
+
23
+ ### What We Should Do
24
+
25
+ **Priority**: MEDIUM
26
+
27
+ **Implementation**:
28
+
29
+ 1. **Background processing flag**:
30
+ ```ruby
31
+ # lib/claude_memory/commands/hook_command.rb
32
+ def call(args)
33
+ opts = parse_options(args, { async: false })
34
+
35
+ if opts[:async]
36
+ # Fork and detach
37
+ pid = fork do
38
+ Process.setsid # Detach from terminal
39
+ execute_hook(subcommand, payload)
40
+ end
41
+ Process.detach(pid)
42
+
43
+ stdout.puts "Hook execution started in background (PID: #{pid})"
44
+ return Hook::ExitCodes::SUCCESS
45
+ end
46
+
47
+ execute_hook(subcommand, payload)
48
+ end
49
+ ```
50
+
51
+ 2. **Hook configuration**:
52
+ ```json
53
+ {
54
+ "hooks": {
55
+ "SessionStart": [{
56
+ "matcher": "startup|resume",
57
+ "hooks": [{
58
+ "type": "command",
59
+ "command": "claude-memory hook ingest --async",
60
+ "async": true
61
+ }]
62
+ }]
63
+ }
64
+ }
65
+ ```
66
+
67
+ **Benefits**:
68
+ - Non-blocking hooks (user continues working immediately)
69
+ - Better user experience for long-running operations
70
+ - Doesn't delay session startup
71
+
72
+ **Trade-offs**:
73
+ - Background process management complexity
74
+ - Need logging for background execution
75
+ - Potential race conditions if multiple sessions start simultaneously
76
+
77
+ ---
78
+
79
+ ## 2. ROI Metrics and Token Economics
80
+
81
+ ### What claude-mem Does
82
+
83
+ **Discovery Token Tracking**:
84
+ - `discovery_tokens` field on observations table
85
+ - Tracks tokens spent discovering each piece of knowledge
86
+ - Cumulative metrics in session summaries
87
+ - Footer displays ROI: "Access 10k tokens for 2,500t"
88
+
89
+ **File**: `src/services/sqlite/Database.ts`
90
+
91
+ ```typescript
92
+ observations: {
93
+ id: INTEGER PRIMARY KEY,
94
+ title: TEXT,
95
+ narrative: TEXT,
96
+ discovery_tokens: INTEGER, // ← Cost tracking
97
+ created_at_epoch: INTEGER
98
+ }
99
+
100
+ session_summaries: {
101
+ cumulative_discovery_tokens: INTEGER, // ← Running total
102
+ observation_count: INTEGER
103
+ }
104
+ ```
105
+
106
+ **Context Footer Example**:
107
+ ```markdown
108
+ 💡 **Token Economics:**
109
+ - Context shown: 2,500 tokens
110
+ - Research captured: 10,000 tokens
111
+ - ROI: 4x compression
112
+ ```
113
+
114
+ ### What We Should Do
115
+
116
+ **Priority**: MEDIUM
117
+
118
+ **Implementation**:
119
+
120
+ 1. **Add metrics table**:
121
+ ```ruby
122
+ create_table :ingestion_metrics do
123
+ primary_key :id
124
+ foreign_key :content_item_id, :content_items
125
+ Integer :input_tokens
126
+ Integer :output_tokens
127
+ Integer :facts_extracted
128
+ DateTime :created_at
129
+ end
130
+ ```
131
+
132
+ 2. **Track during distillation**:
133
+ ```ruby
134
+ # lib/claude_memory/distill/distiller.rb
135
+ def distill(content)
136
+ response = api_call(content)
137
+ facts = extract_facts(response)
138
+
139
+ store_metrics(
140
+ input_tokens: response.usage.input_tokens,
141
+ output_tokens: response.usage.output_tokens,
142
+ facts_extracted: facts.size
143
+ )
144
+
145
+ facts
146
+ end
147
+ ```
148
+
149
+ 3. **Display in CLI**:
150
+ ```ruby
151
+ # claude-memory stats
152
+ def stats_cmd
153
+ metrics = store.aggregate_metrics
154
+ puts "Token Economics:"
155
+ puts " Input: #{metrics[:input_tokens]} tokens"
156
+ puts " Output: #{metrics[:output_tokens]} tokens"
157
+ puts " Facts: #{metrics[:facts_extracted]}"
158
+ puts " Efficiency: #{metrics[:facts_extracted] / metrics[:input_tokens].to_f} facts/token"
159
+ end
160
+ ```
161
+
162
+ 4. **Add to published snapshot**:
163
+ ```markdown
164
+ <!-- At bottom of .claude/rules/claude_memory.generated.md -->
165
+
166
+ ---
167
+
168
+ *Memory stats: 145 facts from 12,500 ingested tokens (86 facts/1k tokens)*
169
+ ```
170
+
171
+ **Benefits**:
172
+ - Visibility into memory system efficiency
173
+ - Justifies API costs (shows compression ratio)
174
+ - Helps tune distillation prompts for better extraction
175
+
176
+ **Trade-offs**:
177
+ - Requires API usage tracking
178
+ - Adds database complexity
179
+ - May not be meaningful for all distiller implementations
180
+
181
+ ---
182
+
183
+ ## 3. Structured Logging
184
+
185
+ ### Implementation
186
+
187
+ **Priority**: LOW
188
+
189
+ 1. **Add structured logger**:
190
+ ```ruby
191
+ # lib/claude_memory/logging/logger.rb
192
+ module ClaudeMemory
193
+ module Logging
194
+ class Logger
195
+ def initialize(output = $stderr, level: :info)
196
+ @output = output
197
+ @level = level
198
+ end
199
+
200
+ def info(message, metadata = {})
201
+ log(:info, message, metadata)
202
+ end
203
+
204
+ def error(message, exception: nil, metadata = {})
205
+ log(:error, message, metadata.merge(exception: exception&.message))
206
+ end
207
+
208
+ private
209
+
210
+ def log(level, message, metadata)
211
+ return if should_skip?(level)
212
+
213
+ log_entry = {
214
+ timestamp: Time.now.iso8601,
215
+ level: level,
216
+ message: message
217
+ }.merge(metadata)
218
+
219
+ @output.puts JSON.generate(log_entry)
220
+ end
221
+ end
222
+ end
223
+ end
224
+ ```
225
+
226
+ 2. **Usage in Ingester**:
227
+ ```ruby
228
+ def ingest(...)
229
+ logger.info("Starting ingestion",
230
+ session_id: session_id,
231
+ transcript_path: transcript_path
232
+ )
233
+
234
+ begin
235
+ # ... ingestion logic ...
236
+
237
+ logger.info("Ingestion complete",
238
+ content_items_created: 1,
239
+ facts_extracted: facts.size
240
+ )
241
+ rescue => e
242
+ logger.error("Ingestion failed",
243
+ exception: e,
244
+ session_id: session_id
245
+ )
246
+ raise
247
+ end
248
+ end
249
+ ```
250
+
251
+ **Benefits**:
252
+ - Better debugging with structured data
253
+ - Easy log parsing and analysis
254
+ - Consistent log format
255
+
256
+ **Trade-offs**:
257
+ - Additional complexity
258
+ - Log output may be verbose
259
+
260
+ ---
261
+
262
+
263
+ ## Features to Avoid
264
+
265
+ ### 1. Chroma Vector Database
266
+
267
+ **Their Approach**: Hybrid SQLite FTS5 + Chroma vector search.
268
+
269
+ **Our Take**: **Skip it.** Adds significant complexity:
270
+
271
+ - Python dependency
272
+ - ChromaDB server
273
+ - Embedding generation
274
+ - Sync overhead
275
+
276
+ **Alternative**: We've implemented lightweight TF-IDF embeddings without external dependencies.
277
+
278
+ ### 2. Claude Agent SDK for Distillation
279
+
280
+ **Their Approach**: Use `@anthropic-ai/claude-agent-sdk` for observation compression.
281
+
282
+ **Our Take**: **Skip it.** We already have `Distill::Distiller` interface. SDK adds:
283
+
284
+ - Node.js dependency
285
+ - Subprocess management
286
+ - Complex event loop
287
+
288
+ **Alternative**: Direct API calls via `anthropic-rb` gem (if we implement distiller).
289
+
290
+ ### 3. Worker Service Background Process
291
+
292
+ **Their Approach**: Long-running worker with HTTP API + MCP wrapper.
293
+
294
+ **Our Take**: **Skip it.** We use MCP server directly:
295
+
296
+ - No background process to manage
297
+ - No port conflicts
298
+ - No PID files
299
+ - Simpler deployment
300
+
301
+ **Alternative**: Keep stdio-based MCP server. Add HTTP transport only if needed.
302
+
303
+ ### 4. Web Viewer UI
304
+
305
+ **Their Approach**: React-based web UI at `http://localhost:37777`.
306
+
307
+ **Our Take**: **Skip for MVP.** Significant effort for uncertain value:
308
+
309
+ - React + esbuild
310
+ - SSE implementation
311
+ - State management
312
+ - CSS/theming
313
+
314
+ **Alternative**: CLI output is sufficient. Add web UI if users request it.
315
+
316
+ ---
317
+
318
+ ## Implementation Priorities
319
+
320
+ ### Medium Priority
321
+
322
+ 1. **Background Processing** - Non-blocking hooks for better UX
323
+ 2. **ROI Metrics** - Track token economics for distillation
324
+
325
+ ### Low Priority
326
+
327
+ 3. **Structured Logging** - Better debugging with JSON logs
328
+ 4. **Health Monitoring** - Only if we add background worker
329
+ 5. **Web Viewer UI** - Only if users request visualization
330
+ 6. **Configuration-Driven Context** - Only if users request snapshot customization
data/lefthook.yml CHANGED
@@ -8,3 +8,16 @@ pre-commit:
8
8
  stage_fixed: true
9
9
  tests:
10
10
  run: bundle exec rspec
11
+ quality-review:
12
+ run: |
13
+ staged_ruby=$(git diff --cached --name-only --diff-filter=ACM | grep '\.rb$' || true)
14
+ if [ -n "$staged_ruby" ]; then
15
+ echo "Running quality review on staged changes..."
16
+ claude -p "/review-commit" \
17
+ --no-session-persistence \
18
+ --model haiku \
19
+ --allowedTools "Bash Read Grep Glob" \
20
+ --no-chrome
21
+ else
22
+ echo "No Ruby files staged, skipping quality review"
23
+ fi
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeMemory
4
+ module Commands
5
+ module Checks
6
+ # Checks if CLAUDE.md exists and imports snapshot
7
+ class ClaudeMdCheck
8
+ CLAUDE_MD_PATH = ".claude/CLAUDE.md"
9
+
10
+ def call
11
+ unless File.exist?(CLAUDE_MD_PATH)
12
+ return {
13
+ status: :warning,
14
+ label: "claude_md",
15
+ message: "No .claude/CLAUDE.md found",
16
+ details: {path: CLAUDE_MD_PATH}
17
+ }
18
+ end
19
+
20
+ content = File.read(CLAUDE_MD_PATH)
21
+
22
+ if content.include?("claude_memory.generated.md")
23
+ {
24
+ status: :ok,
25
+ label: "claude_md",
26
+ message: "CLAUDE.md imports snapshot",
27
+ details: {path: CLAUDE_MD_PATH}
28
+ }
29
+ else
30
+ {
31
+ status: :warning,
32
+ label: "claude_md",
33
+ message: "CLAUDE.md does not import snapshot",
34
+ details: {path: CLAUDE_MD_PATH}
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeMemory
4
+ module Commands
5
+ module Checks
6
+ # Checks database existence, schema, and health
7
+ class DatabaseCheck
8
+ def initialize(db_path, label)
9
+ @db_path = db_path
10
+ @label = label
11
+ end
12
+
13
+ def call
14
+ unless File.exist?(@db_path)
15
+ return missing_database_result
16
+ end
17
+
18
+ check_database_health
19
+ rescue => e
20
+ {
21
+ status: :error,
22
+ label: @label,
23
+ message: "#{@label} database error: #{e.message}",
24
+ details: {}
25
+ }
26
+ end
27
+
28
+ private
29
+
30
+ def missing_database_result
31
+ if @label == "global"
32
+ {
33
+ status: :error,
34
+ label: @label,
35
+ message: "Global database not found: #{@db_path}",
36
+ details: {}
37
+ }
38
+ else
39
+ {
40
+ status: :warning,
41
+ label: @label,
42
+ message: "Project database not found: #{@db_path} (run 'claude-memory init')",
43
+ details: {}
44
+ }
45
+ end
46
+ end
47
+
48
+ def check_database_health
49
+ store = ClaudeMemory::Store::SQLiteStore.new(@db_path)
50
+
51
+ details = {
52
+ path: @db_path,
53
+ adapter: "extralite",
54
+ schema_version: store.schema_version,
55
+ fact_count: store.facts.count,
56
+ content_count: store.content_items.count,
57
+ conflict_count: store.conflicts.where(status: "open").count,
58
+ last_ingest: store.content_items.max(:ingested_at)
59
+ }
60
+
61
+ warnings = []
62
+ errors = []
63
+
64
+ # Check for open conflicts
65
+ if details[:conflict_count] > 0
66
+ warnings << "#{details[:conflict_count]} open conflict(s) need resolution"
67
+ end
68
+
69
+ # Check for missing ingests
70
+ if details[:last_ingest].nil? && @label == "project"
71
+ warnings << "No content has been ingested yet"
72
+ end
73
+
74
+ # Check for stuck operations
75
+ tracker = ClaudeMemory::Infrastructure::OperationTracker.new(store)
76
+ stuck_ops = tracker.stuck_operations
77
+ if stuck_ops.any?
78
+ stuck_ops.each do |op|
79
+ warnings << "Stuck operation '#{op[:operation_type]}' (started #{op[:started_at]}). Run 'claude-memory recover' to reset."
80
+ end
81
+ end
82
+ details[:stuck_operations] = stuck_ops.size
83
+
84
+ # Run schema validation
85
+ validator = ClaudeMemory::Infrastructure::SchemaValidator.new(store)
86
+ validation = validator.validate
87
+ details[:schema_valid] = validation[:valid]
88
+
89
+ # Collect validation issues
90
+ if validation[:issues].any?
91
+ validation[:issues].each do |issue|
92
+ if issue[:severity] == "error"
93
+ errors << issue[:message]
94
+ else
95
+ warnings << issue[:message]
96
+ end
97
+ end
98
+ end
99
+
100
+ store.close
101
+
102
+ status = if errors.any?
103
+ :error
104
+ else
105
+ (warnings.any? ? :warning : :ok)
106
+ end
107
+
108
+ {
109
+ status: status,
110
+ label: @label,
111
+ message: "#{@label.capitalize} database exists: #{@db_path}",
112
+ details: details,
113
+ warnings: warnings,
114
+ errors: errors
115
+ }
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module ClaudeMemory
6
+ module Commands
7
+ module Checks
8
+ # Checks hooks configuration in settings files
9
+ class HooksCheck
10
+ SETTINGS_PATHS = [".claude/settings.json", ".claude/settings.local.json"].freeze
11
+ EXPECTED_HOOKS = %w[Stop SessionStart PreCompact SessionEnd].freeze
12
+
13
+ def call
14
+ hooks_found = false
15
+ warnings = []
16
+ paths_checked = []
17
+
18
+ SETTINGS_PATHS.each do |path|
19
+ next unless File.exist?(path)
20
+
21
+ paths_checked << path
22
+ result = check_settings_file(path)
23
+
24
+ if result[:has_hooks]
25
+ hooks_found = true
26
+ warnings.concat(result[:warnings])
27
+ end
28
+ end
29
+
30
+ # Check for orphaned hooks
31
+ if hooks_found && !mcp_configured?
32
+ warnings << "Orphaned hooks detected: claude-memory hooks are configured but MCP server is not"
33
+ warnings << "Run 'claude-memory uninstall' to remove hooks, or 'claude-memory init' to reconfigure"
34
+ end
35
+
36
+ if hooks_found
37
+ {
38
+ status: warnings.any? ? :warning : :ok,
39
+ label: "hooks",
40
+ message: "Hooks configured in #{paths_checked.join(", ")}",
41
+ details: {
42
+ paths: paths_checked,
43
+ fallback_available: false
44
+ },
45
+ warnings: warnings
46
+ }
47
+ else
48
+ {
49
+ status: :warning,
50
+ label: "hooks",
51
+ message: "No hooks configured. Run 'claude-memory init' or configure manually.",
52
+ details: {
53
+ paths: SETTINGS_PATHS,
54
+ fallback_available: true,
55
+ fallback_commands: [
56
+ "claude-memory ingest --session-id <id> --transcript-path <path>",
57
+ "claude-memory sweep --budget 5",
58
+ "claude-memory publish"
59
+ ]
60
+ },
61
+ warnings: []
62
+ }
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def check_settings_file(path)
69
+ warnings = []
70
+
71
+ begin
72
+ config = JSON.parse(File.read(path))
73
+
74
+ unless config["hooks"]&.any?
75
+ return {has_hooks: false, warnings: []}
76
+ end
77
+
78
+ # Check if any hooks contain claude-memory commands
79
+ claude_memory_hooks = config["hooks"].values.flatten.any? do |hook_array|
80
+ next false unless hook_array.is_a?(Hash) && hook_array["hooks"].is_a?(Array)
81
+ hook_array["hooks"].any? { |h| h["command"]&.include?("claude-memory") }
82
+ end
83
+
84
+ return {has_hooks: false, warnings: []} unless claude_memory_hooks
85
+
86
+ # Check for missing recommended hooks
87
+ missing = EXPECTED_HOOKS - config["hooks"].keys
88
+ if missing.any?
89
+ warnings << "Missing recommended hooks in #{path}: #{missing.join(", ")}"
90
+ end
91
+
92
+ {has_hooks: true, warnings: warnings}
93
+ rescue JSON::ParserError
94
+ {has_hooks: false, warnings: ["Invalid JSON in #{path}"]}
95
+ end
96
+ end
97
+
98
+ def mcp_configured?
99
+ mcp_path = ".claude.json"
100
+ return false unless File.exist?(mcp_path)
101
+
102
+ begin
103
+ config = JSON.parse(File.read(mcp_path))
104
+ config["mcpServers"]&.key?("claude-memory")
105
+ rescue JSON::ParserError
106
+ false
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end