claude_memory 0.9.1 → 0.10.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/memory.sqlite3 +0 -0
- data/.claude/skills/dashboard/SKILL.md +42 -0
- data/.claude-plugin/marketplace.json +1 -1
- data/.claude-plugin/plugin.json +1 -1
- data/CHANGELOG.md +86 -0
- data/CLAUDE.md +21 -5
- data/README.md +32 -2
- data/db/migrations/015_add_activity_events.rb +26 -0
- data/db/migrations/016_add_moment_feedback.rb +22 -0
- data/db/migrations/017_add_last_recalled_at.rb +15 -0
- data/docs/1_0_punchlist.md +190 -0
- data/docs/EXAMPLES.md +41 -2
- data/docs/GETTING_STARTED.md +31 -4
- data/docs/architecture.md +22 -7
- data/docs/audit-queries.md +131 -0
- data/docs/dashboard.md +172 -0
- data/docs/improvements.md +465 -9
- data/docs/influence/cq.md +187 -0
- data/docs/plugin.md +13 -6
- data/docs/quality_review.md +489 -172
- data/docs/reflection_memory_as_accumulating_judgment.md +67 -0
- data/lib/claude_memory/activity_log.rb +86 -0
- data/lib/claude_memory/commands/census_command.rb +210 -0
- data/lib/claude_memory/commands/completion_command.rb +3 -0
- data/lib/claude_memory/commands/dashboard_command.rb +54 -0
- data/lib/claude_memory/commands/dedupe_conflicts_command.rb +55 -0
- data/lib/claude_memory/commands/digest_command.rb +181 -0
- data/lib/claude_memory/commands/hook_command.rb +34 -0
- data/lib/claude_memory/commands/reclassify_references_command.rb +56 -0
- data/lib/claude_memory/commands/registry.rb +6 -1
- data/lib/claude_memory/commands/skills/distill-transcripts.md +13 -1
- data/lib/claude_memory/commands/stats_command.rb +38 -1
- data/lib/claude_memory/commands/sweep_command.rb +2 -0
- data/lib/claude_memory/configuration.rb +16 -0
- data/lib/claude_memory/core/relative_time.rb +9 -0
- data/lib/claude_memory/dashboard/api.rb +610 -0
- data/lib/claude_memory/dashboard/conflicts.rb +279 -0
- data/lib/claude_memory/dashboard/efficacy.rb +127 -0
- data/lib/claude_memory/dashboard/fact_presenter.rb +109 -0
- data/lib/claude_memory/dashboard/health.rb +175 -0
- data/lib/claude_memory/dashboard/index.html +2707 -0
- data/lib/claude_memory/dashboard/knowledge.rb +136 -0
- data/lib/claude_memory/dashboard/moments.rb +244 -0
- data/lib/claude_memory/dashboard/reuse.rb +97 -0
- data/lib/claude_memory/dashboard/scoped_fact_resolver.rb +95 -0
- data/lib/claude_memory/dashboard/server.rb +211 -0
- data/lib/claude_memory/dashboard/timeline.rb +68 -0
- data/lib/claude_memory/dashboard/trust.rb +285 -0
- data/lib/claude_memory/distill/reference_material_detector.rb +78 -0
- data/lib/claude_memory/hook/auto_memory_mirror.rb +112 -0
- data/lib/claude_memory/hook/context_injector.rb +97 -3
- data/lib/claude_memory/hook/handler.rb +50 -3
- data/lib/claude_memory/mcp/handlers/management_handlers.rb +8 -0
- data/lib/claude_memory/mcp/query_guide.rb +11 -0
- data/lib/claude_memory/mcp/text_summary.rb +29 -0
- data/lib/claude_memory/mcp/tool_definitions.rb +13 -0
- data/lib/claude_memory/mcp/tools.rb +148 -0
- data/lib/claude_memory/publish.rb +13 -21
- data/lib/claude_memory/recall/stale_detector.rb +67 -0
- data/lib/claude_memory/resolve/predicate_policy.rb +2 -0
- data/lib/claude_memory/resolve/resolver.rb +41 -11
- data/lib/claude_memory/store/llm_cache.rb +68 -0
- data/lib/claude_memory/store/metrics_aggregator.rb +96 -0
- data/lib/claude_memory/store/schema_manager.rb +1 -1
- data/lib/claude_memory/store/sqlite_store.rb +47 -143
- data/lib/claude_memory/store/store_manager.rb +29 -0
- data/lib/claude_memory/sweep/maintenance.rb +216 -0
- data/lib/claude_memory/sweep/recall_timestamp_refresher.rb +83 -0
- data/lib/claude_memory/sweep/sweeper.rb +2 -0
- data/lib/claude_memory/version.rb +1 -1
- data/lib/claude_memory.rb +22 -0
- metadata +49 -1
data/docs/quality_review.md
CHANGED
|
@@ -1,285 +1,602 @@
|
|
|
1
1
|
# Code Quality Review - Ruby Best Practices
|
|
2
2
|
|
|
3
|
-
**Review Date:** 2026-
|
|
4
|
-
**Previous Review:** 2026-
|
|
5
|
-
**Last Quality Update:** 2026-
|
|
3
|
+
**Review Date:** 2026-04-28
|
|
4
|
+
**Previous Review:** 2026-04-22 (6 days ago)
|
|
5
|
+
**Last Quality Update:** 2026-04-22 (4 items completed — LLMCache + MetricsAggregator extractions, Publish DRY, Dashboard specs)
|
|
6
|
+
**Codebase Growth:** 17,014 → 19,025 LOC (+2,011, +12% in 6 days)
|
|
7
|
+
|
|
8
|
+
> **Post-review update (2026-04-28, same session):** Items #34, #32, #35, #36 (quick wins + missing command specs) and the first two of the six proposed `Dashboard::API` extractions (#31: `Dashboard::Timeline` + `Dashboard::Health`) landed before the v0.10.0 release commit. `dashboard/api.rb` dropped 807 → 607 LOC (-200, -25%), reversing the regression and bringing it back under the 2026-04-22 baseline (627). The remaining four extractions (`RecallQuery`, `RecallTriggerFinder`, `UserPromptExtractor`, `FactsQuery`) are deferred to 0.10.1. Findings below describe the *pre-update* state captured at review time.
|
|
6
9
|
|
|
7
10
|
---
|
|
8
11
|
|
|
9
12
|
## Executive Summary
|
|
10
13
|
|
|
11
|
-
The
|
|
14
|
+
Six days, +2,011 LOC. The headline finding: **the watch-list item from 2026-04-22 (#28 — extract per-endpoint helpers from `Dashboard::API`) was not just deferred, it actively regressed.** `dashboard/api.rb` grew from 627 → 807 LOC (+180, +29%), is now the only file in `lib/` over 750 lines, and gained four new methods all exceeding 15 lines. Method-size pressure increased: the previous worst case (`recall` at 39 lines) is now `timeline` at 52 lines, and the file has 11 methods over 15 lines (vs 11 last review) but with a higher mean.
|
|
15
|
+
|
|
16
|
+
The codebase is otherwise healthy. Five **new dashboard subsystems** (`moments.rb`, `reuse.rb`, `trust.rb`, `scoped_fact_resolver.rb`, plus `efficacy.rb` carried over) shipped with **direct spec files**. Three new schema migrations (v15/v16/v17) all wrap DDL with idempotent `create_table?` / `add_column` and have **per-migration specs plus round-trip specs from v12, v13, and v14 forward to v17** (a deliberate process improvement noted in `feedback_round_trip_migration_specs.md`). Four new sweep operations (`dedupe_open_conflicts`, `reclassify_references`) gained spec coverage in `sweep/maintenance_spec.rb`.
|
|
17
|
+
|
|
18
|
+
**What regressed:**
|
|
19
|
+
- `dashboard/api.rb` 627 → 807 LOC (+180). Watch-list item not addressed.
|
|
20
|
+
- `sweep/maintenance.rb` 334 → 456 LOC (+122). Two of the four new methods are 50+ lines (`dedupe_open_conflicts` 58, `restore_multi_value_supersessions` 57).
|
|
21
|
+
- Sleep-based test latency grew. `dashboard/moments_spec.rb` and `dashboard/api_spec.rb` add 4 more `sleep 1.1` calls (+4.4s wall). Total sleep-based test cost in suite is now ~8.4s, up from ~4s.
|
|
22
|
+
- One new code smell: `digest_command.rb:128` calls `Dashboard::Trust.new(manager).send(:utilization)` — reaches into a private method instead of exposing `utilization` on the public Trust API.
|
|
12
23
|
|
|
13
|
-
|
|
24
|
+
**What was resolved or improved since 2026-04-22:**
|
|
25
|
+
- Round-trip migration specs from v12/v13/v14 → v17 added (release-blocker per `feedback_round_trip_migration_specs.md`).
|
|
26
|
+
- Per-migration specs for v13–v17 added under `spec/claude_memory/store/migrations/`.
|
|
27
|
+
- New dashboard subsystems shipped *with* specs (good pattern — Reuse, Moments, Trust, Knowledge, ScopedFactResolver all have direct specs).
|
|
28
|
+
- `lib/claude_memory/store/sqlite_store.rb` only grew 40 LOC (544 → 584); regrowth controlled.
|
|
14
29
|
|
|
15
|
-
**
|
|
16
|
-
**Resolved since last review:** 3 additional items (ExportCommand N+1, `discover_other_projects` bare rescue, embedding test coverage)
|
|
17
|
-
**Total remaining:** 12 items (0 high, 5 medium, 4 low, 5 carried forward — all structural work complete)
|
|
30
|
+
**New this review:** 4 items. 1 high-priority (Dashboard::API extraction, now urgent), 1 medium (`sweep/maintenance.rb` size), 1 low (`Time.parse` duplication across dashboard files), 1 quick win (`.send(:utilization)` smell in digest).
|
|
18
31
|
|
|
19
32
|
### Current Strengths
|
|
20
33
|
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
- 100% frozen_string_literal compliance (117 files)
|
|
26
|
-
- 1.84:1 test-to-code ratio (22,563 spec : 12,239 lib)
|
|
27
|
-
- New embedding subsystem: shared RSpec examples, duck-typed providers, Data.define value object
|
|
28
|
-
- Zero N+1 patterns in hot paths
|
|
29
|
-
- Proper batch loading via FactQueryBuilder
|
|
30
|
-
- Content-addressed dedup in IndexCommand
|
|
31
|
-
- DimensionCheck value object (functional core, no side effects)
|
|
32
|
-
- Zero known correctness bugs
|
|
34
|
+
- Migrations now ship with per-migration specs **and** cross-version round-trip specs — a deliberate release-readiness improvement that landed during this window
|
|
35
|
+
- New dashboard subsystems all have direct specs; spec count grew 156 → 188 files (+32)
|
|
36
|
+
- Domain objects, frozen string literals, transaction wrapping, no raw SQL, no N+1 in hot paths — all preserved
|
|
37
|
+
- Five files >300 LOC last review; eight now, but mostly because of new modules carrying single responsibilities (Moments 244, Trust 284, Conflicts 285), not god-object regrowth
|
|
33
38
|
|
|
34
39
|
---
|
|
35
40
|
|
|
36
41
|
## 1. Sandi Metz Perspective
|
|
37
42
|
|
|
38
43
|
### What's Been Fixed ✅
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
44
|
+
|
|
45
|
+
- `SQLiteStore` regrowth held steady at 584 LOC after the 2026-04-22 LLMCache + MetricsAggregator extractions; only +40 LOC over 6 days, and that's adding two new tables (`moment_feedback`, `activity_events`) with their CRUD wrappers
|
|
46
|
+
- New dashboard subsystems each landed under 300 LOC with focused responsibilities
|
|
47
|
+
- `Moments` (244 LOC) — feed-shape construction, no DB writes
|
|
48
|
+
- `Trust` (284 LOC) — sidebar aggregations, all reads
|
|
49
|
+
- `Reuse` (97 LOC) — top-N "most-used" panel
|
|
50
|
+
- `Knowledge` (136 LOC) — fact summary panel
|
|
51
|
+
- `ScopedFactResolver` (95 LOC) — pure helper
|
|
52
|
+
- Round-trip migration specs (`round_trip_v12_to_v17_spec.rb` etc.) — Sandi-style "test the contract, not the implementation"
|
|
47
53
|
|
|
48
54
|
### Critical Issues 🔴
|
|
49
55
|
|
|
50
|
-
|
|
56
|
+
#### A. `Dashboard::API` regressed: 627 → 807 LOC (+29%) — **carried-forward item became urgent**
|
|
57
|
+
|
|
58
|
+
`lib/claude_memory/dashboard/api.rb` was the watch-list item at the close of the 2026-04-22 review (#28). Six days later, instead of shrinking via per-endpoint extraction, it absorbed:
|
|
59
|
+
|
|
60
|
+
- `find_recall_trigger` (lib/claude_memory/dashboard/api.rb:193) — 32 lines, 5 SQL constructions, calls 3 helpers, JSON-parses event details
|
|
61
|
+
- `extract_user_prompt` (lib/claude_memory/dashboard/api.rb:237) — 29 lines, JSONL parsing, content type narrowing, plumbing-noise filtering
|
|
62
|
+
- `facts` (lib/claude_memory/dashboard/api.rb:373) — 39 lines (was 26), now also handles `stale_only` filtering with cross-store exclusion
|
|
63
|
+
- `facts_seen_in_recent_recalls` (lib/claude_memory/dashboard/api.rb:418) — 20 lines, scoped-pair aggregation
|
|
64
|
+
- `efficacy` (lib/claude_memory/dashboard/api.rb:439) — 31 lines (was 23), now branches on session_id with time-window correlation
|
|
65
|
+
- New micro-endpoints: `moments`, `trust`, `knowledge`, `reuse`, `moment_feedback`, `clear_moment_feedback`, `fact_detail`, `promote_fact`, `reject_fact`
|
|
66
|
+
|
|
67
|
+
The class now has **42 methods** (up from ~31) and **8 methods over 20 lines**. The methods that delegate cleanly (`conflicts`, `moments`, `trust`, `knowledge`, `reuse` — all 1-liners) are the right pattern; the rest of the file should follow that pattern.
|
|
68
|
+
|
|
69
|
+
**Method size table (current state):**
|
|
70
|
+
|
|
71
|
+
| Method | Line | Size | Concern |
|
|
72
|
+
|---|---|---|---|
|
|
73
|
+
| `timeline` | 471 | 52 | 3 separate Sequel aggregations + Ruby-side merge — should be `Dashboard::Timeline` |
|
|
74
|
+
| `vec_health` | 759 | 46 | Branchy status derivation over coverage stats |
|
|
75
|
+
| `recall` | 315 | 41 | Result flattening + bare rescue + actionable-hint branching |
|
|
76
|
+
| `facts` | 373 | 39 | Pagination + filter + cross-store stale exclusion |
|
|
77
|
+
| `activity_detail` | 149 | 37 | Joined fetch + linked facts + recall-trigger correlation |
|
|
78
|
+
| `hooks_health` | 704 | 32 | Multi-state status with fix messages |
|
|
79
|
+
| `find_recall_trigger` | 193 | 32 | Time-window query with session_id fallback |
|
|
80
|
+
| `efficacy` | 439 | 31 | Session-scope vs window-scope branching |
|
|
81
|
+
| `extract_user_prompt` | 237 | 29 | JSONL reverse-walk + plumbing filter |
|
|
82
|
+
| `session_summary` | 119 | 29 | Multi-event-type aggregation |
|
|
83
|
+
| `db_stats` | 647 | 28 | Predicate counts + entity counts + size stats |
|
|
84
|
+
|
|
85
|
+
**Proposed extractions** (each candidate is testable in isolation):
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
# lib/claude_memory/dashboard/timeline.rb — pure aggregation
|
|
89
|
+
class Timeline
|
|
90
|
+
def initialize(manager) = @manager = manager
|
|
91
|
+
def days = { days: build_days }
|
|
92
|
+
private
|
|
93
|
+
def build_days
|
|
94
|
+
return [] unless store
|
|
95
|
+
fact_rows, content_rows, event_rows = load_aggregations
|
|
96
|
+
merge_into_days(fact_rows, content_rows, event_rows)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# lib/claude_memory/dashboard/health.rb — already 4 health checks (db, hooks, vec, vectors)
|
|
101
|
+
class Health
|
|
102
|
+
def report = { status: overall(checks), checks: checks, version: VERSION }
|
|
103
|
+
private
|
|
104
|
+
def checks = [db_health("global"), db_health("project"), hooks_health, vec_health]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# lib/claude_memory/dashboard/recall_query.rb — wraps live recall + actionable error mapping
|
|
108
|
+
class RecallQuery
|
|
109
|
+
def call(params) = format_response(run(params))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# lib/claude_memory/dashboard/recall_trigger_finder.rb — pure time-window correlation
|
|
113
|
+
# lib/claude_memory/dashboard/user_prompt_extractor.rb — pure JSONL parsing
|
|
114
|
+
# lib/claude_memory/dashboard/facts_query.rb — pagination + stale exclusion
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
After these extractions `api.rb` should drop to **~250 LOC** of routing-and-delegation. The pattern was already proven by `Conflicts` / `Moments` / `Trust` / `Knowledge` / `Reuse`.
|
|
118
|
+
|
|
119
|
+
**File:** `lib/claude_memory/dashboard/api.rb`
|
|
120
|
+
**Effort:** 4–6 hours (5 extractions, each with a focused spec)
|
|
121
|
+
**Priority:** 🔴 — was medium last review, escalates to high because the trend line points at 1,000+ LOC by next sprint if uncorrected
|
|
122
|
+
**Expert principle:** Sandi Metz SRP; Bernhardt boundaries; Beck simple design
|
|
51
123
|
|
|
52
|
-
###
|
|
124
|
+
### Medium Issues 🟡
|
|
125
|
+
|
|
126
|
+
#### B. `sweep/maintenance.rb` grew 334 → 456 LOC (+122, +37%)
|
|
127
|
+
|
|
128
|
+
Last review noted maintenance.rb at 334 (after dropping from 456 earlier — see the review's appendix B). It's now back at 456. Two large methods landed:
|
|
129
|
+
|
|
130
|
+
- `dedupe_open_conflicts` (lib/claude_memory/sweep/maintenance.rb:273) — 58 lines, multi-step transaction (group → resolve duplicates → reattach provenance → reject losers → mark conflicts resolved)
|
|
131
|
+
- `reclassify_references` (lib/claude_memory/sweep/maintenance.rb:340) — 26 lines, transactional cleanup that requires `Distill::ReferenceMaterialDetector`
|
|
132
|
+
|
|
133
|
+
Plus the pre-existing `restore_multi_value_supersessions` (line 185, 57 lines).
|
|
134
|
+
|
|
135
|
+
These are all *one-shot historical cleanups* (per their docstrings). They don't belong in the regular sweep cycle — they're admin operations. Two options:
|
|
136
|
+
|
|
137
|
+
1. **Extract to `Sweep::HistoricalCleanup`** — a separate module for one-shot data fixes
|
|
138
|
+
2. **Keep in Maintenance but extract long methods** — e.g. `dedupe_open_conflicts` calls `pair_key`, but the inner per-group logic (lib/claude_memory/sweep/maintenance.rb:294-326) is 32 lines that could be `resolve_duplicate_group(keeper, duplicates)`
|
|
139
|
+
|
|
140
|
+
**File:** `lib/claude_memory/sweep/maintenance.rb`
|
|
141
|
+
**Effort:** 2 hours
|
|
142
|
+
**Priority:** 🟡 Medium
|
|
143
|
+
**Expert principle:** Sandi Metz SRP; Beck single level of abstraction
|
|
144
|
+
|
|
145
|
+
#### C. `digest_command.rb:128` calls private API via `.send`
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
# lib/claude_memory/commands/digest_command.rb:128
|
|
149
|
+
util = Dashboard::Trust.new(manager).send(:utilization)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
This is the only `.send` to a private method in `lib/`. Two paths forward:
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
# Option 1: Promote utilization to public on Trust (it already returns a documented Hash shape)
|
|
156
|
+
# lib/claude_memory/dashboard/trust.rb — remove `private` annotation above utilization
|
|
157
|
+
|
|
158
|
+
# Option 2: Extract Dashboard::Utilization as its own object
|
|
159
|
+
class Utilization
|
|
160
|
+
def initialize(manager) = @manager = manager
|
|
161
|
+
def report = { extracted:, used:, used_from_extracted:, ratio_pct:, window_days: }
|
|
162
|
+
end
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Option 2 is cleaner — Trust currently *also* exposes `utilization` indirectly through `snapshot`, so users have two paths to the same data. Extracting the calculator gives Digest, Trust, and any future caller one canonical interface.
|
|
53
166
|
|
|
54
|
-
|
|
167
|
+
**File:** `lib/claude_memory/commands/digest_command.rb:128`
|
|
168
|
+
**Effort:** 30 minutes
|
|
169
|
+
**Priority:** 🟡 Medium (works correctly, but tells future readers "private is negotiable")
|
|
170
|
+
**Expert principle:** Avdi Grimm tell-don't-ask; Sandi Metz dependency clarity
|
|
171
|
+
|
|
172
|
+
### Low Issues
|
|
173
|
+
|
|
174
|
+
| # | Issue | File:Line | Effort |
|
|
175
|
+
|---|---|---|---|
|
|
176
|
+
| 8 | `upsert_content_item` 11 keyword params (carried) | `store/sqlite_store.rb:193` | 1 hour |
|
|
177
|
+
| 32 | `parse_timestamp` duplicated in `dashboard/api.rb:565` and `dashboard/conflicts.rb:278` | both | 15 min |
|
|
178
|
+
| 33 | `stores_for(scope)` / `facts_stores_for(scope)` near-identical pattern | `dashboard/conflicts.rb:160`, `dashboard/api.rb:589` | 30 min |
|
|
55
179
|
|
|
56
180
|
---
|
|
57
181
|
|
|
58
182
|
## 2. Jeremy Evans Perspective
|
|
59
183
|
|
|
60
184
|
### What's Been Fixed ✅
|
|
61
|
-
- Batch queries in Recall pipeline (FactQueryBuilder)
|
|
62
|
-
- Transaction wrapping in Resolver
|
|
63
|
-
- ExportCommand N+1 eliminated
|
|
64
|
-
- `discover_other_projects` now catches specific exception types (`Sequel::DatabaseError, Extralite::Error, IOError`)
|
|
65
|
-
- **promote_fact transaction boundary fixed** — project data read before global transaction (already correct in code; verified and confirmed)
|
|
66
|
-
- **Provenance nil content_item_id fixed** — removed mandatory `content_item_id` validation from `Domain::Provenance`, allowing nil for promoted facts
|
|
67
185
|
|
|
68
|
-
|
|
186
|
+
- Migrations v15, v16, v17 all wrap DDL in idempotent `create_table?` / `add_column` and provide `down` blocks (v14's down is intentionally a no-op with comment)
|
|
187
|
+
- `Trust#extracted_fact_pairs` (lib/claude_memory/dashboard/trust.rb:231) and `used_fact_pairs` (line 248) batch via `select(:id)` + iteration — no per-row queries
|
|
188
|
+
- `Conflicts#load_facts_for_rows` (lib/claude_memory/dashboard/conflicts.rb:235) batches with `where(id: ids).as_hash(:id)` — explicit N+1 prevention
|
|
69
189
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
190
|
+
### Raw SQL Audit
|
|
191
|
+
|
|
192
|
+
No new raw SQL. The handful of `Sequel.lit` calls in `dashboard/api.rb` are all `DATE(...)` group-by helpers (lines 479, 487, 494) — required because Sequel doesn't have a portable `DATE(timestamp_string)` extractor for SQLite.
|
|
193
|
+
|
|
194
|
+
### Transaction Safety
|
|
195
|
+
|
|
196
|
+
New transactional methods all wrap correctly:
|
|
197
|
+
- `Sweep::Maintenance#dedupe_open_conflicts` — wraps in `@store.db.transaction` (line 289)
|
|
198
|
+
- `Sweep::Maintenance#reclassify_references` — wraps in `@store.db.transaction` (line 349)
|
|
199
|
+
- `SQLiteStore#upsert_moment_feedback` — wraps in `@db.transaction` (line 128)
|
|
200
|
+
|
|
201
|
+
### N+1 Audit (new dashboard panels)
|
|
73
202
|
|
|
74
|
-
|
|
203
|
+
- `Moments#build_moment` (lib/claude_memory/dashboard/moments.rb:125) calls `resolve_content` and `extracted_facts` per row. **Potential N+1 if a feed page surfaces 50 ingest moments.** `extracted_facts` runs `store.db[:facts].join(:provenance).where(content_item_id:)` per moment.
|
|
204
|
+
- `Trust#count_open_conflicts` (lib/claude_memory/dashboard/trust.rb:145) → `Conflicts#distinct_open_counts` walks both stores. Acceptable (fixed cardinality of 2).
|
|
205
|
+
- `Trust#used_fact_pairs` (lib/claude_memory/dashboard/trust.rb:248) loads up to N=500 events without limit. Could grow unbounded. Recommend explicit `.limit(...)` for safety.
|
|
75
206
|
|
|
76
|
-
**
|
|
207
|
+
**Recommendation:**
|
|
208
|
+
- Batch `extracted_facts` in `Moments`: collect all `content_item_id`s up front, run one `where(content_item_id: ids)` join, group results in Ruby.
|
|
209
|
+
- Add explicit `.limit` to `used_fact_pairs` (10,000 is a safe ceiling for a 30-day window).
|
|
210
|
+
|
|
211
|
+
**File:** `lib/claude_memory/dashboard/moments.rb:125,231`
|
|
212
|
+
**Effort:** 45 minutes
|
|
213
|
+
**Priority:** 🟡 Medium (will only bite at scale; fix proactively)
|
|
214
|
+
**Expert principle:** Jeremy Evans dataset hygiene
|
|
77
215
|
|
|
78
216
|
---
|
|
79
217
|
|
|
80
218
|
## 3. Kent Beck Perspective
|
|
81
219
|
|
|
82
220
|
### What's Been Fixed ✅
|
|
83
|
-
|
|
84
|
-
- `
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
- **5 critical untested files now have specs** — `similarity.rb` (10 tests), `metadata_extractor.rb` (9 tests), `tool_extractor.rb` (7 tests), `recover_command.rb` (3 tests), `schema_validator.rb` (6 tests). Total: +36 specs, suite now at 1444.
|
|
221
|
+
|
|
222
|
+
- **Migration spec coverage hit gold standard.** Per-migration specs for v13/v14/v15/v16/v17 + cross-version round-trips from v12, v13, and v14 all forward to v17. That's the canonical "test the seam" pattern. The lessons from `feedback_round_trip_migration_specs.md` are now codified in green tests.
|
|
223
|
+
- New commands `digest_command.rb` and `census_command.rb` shipped with direct specs
|
|
224
|
+
- New dashboard modules all have direct specs (`moments_spec.rb`, `reuse_spec.rb`, `trust_spec.rb`, `knowledge_spec.rb`, `scoped_fact_resolver_spec.rb`)
|
|
88
225
|
|
|
89
226
|
### High Priority Issues
|
|
90
227
|
|
|
91
|
-
|
|
228
|
+
#### D. Two new commands shipped without specs
|
|
92
229
|
|
|
93
|
-
|
|
230
|
+
| Command | LOC | Spec? |
|
|
231
|
+
|---|---|---|
|
|
232
|
+
| `commands/dedupe_conflicts_command.rb` | 55 | ❌ none |
|
|
233
|
+
| `commands/reclassify_references_command.rb` | 56 | ❌ none |
|
|
94
234
|
|
|
95
|
-
|
|
96
|
-
- `commands/stats_command.rb`, `commands/export_command.rb`
|
|
97
|
-
- `commands/changes_command.rb`, `commands/conflicts_command.rb`, `commands/explain_command.rb`
|
|
98
|
-
- `commands/recall_command.rb`, `commands/search_command.rb`
|
|
99
|
-
- `commands/sweep_command.rb`, `commands/publish_command.rb`, `commands/ingest_command.rb`
|
|
100
|
-
- `commands/db_init_command.rb`
|
|
101
|
-
- `commands/checks/` (6 files), `commands/initializers/` (5 files)
|
|
102
|
-
- `mcp/tool_helpers.rb`, `embeddings/fastembed_adapter.rb`, `distill/distiller.rb`
|
|
235
|
+
Both are thin wrappers over `Sweep::Maintenance` (which *is* tested), but the CLI-layer concerns — option parsing, scope routing, output format, dry-run flag flow-through — are uncovered.
|
|
103
236
|
|
|
104
|
-
|
|
237
|
+
The output format in particular has logic worth pinning:
|
|
238
|
+
- `dedupe_conflicts_command.rb:38-52` decides `DRY RUN` vs `DEDUPE`, separator length, decisions header
|
|
239
|
+
- `reclassify_references_command.rb:38-53` truncates objects to 100 chars + ellipsis
|
|
105
240
|
|
|
106
|
-
|
|
107
|
-
|---|-------|-----------|--------|
|
|
108
|
-
| 10 | **Sleep-based tests add 4+ seconds** | `spec/ingest/ingester_spec.rb:43,65,81` | 1 hour |
|
|
241
|
+
**Proposed:** Mirror `digest_command_spec.rb` (or `census_command_spec.rb`) — test option parsing, dry-run paths, and stdout shape via injected `StringIO`.
|
|
109
242
|
|
|
110
|
-
|
|
243
|
+
**Effort:** 30 min each (60 min total)
|
|
244
|
+
**Priority:** High — these are admin commands that mutate data; CLI ergonomics belong under test
|
|
111
245
|
|
|
112
|
-
|
|
246
|
+
#### E. `dashboard/server.rb` still untested
|
|
113
247
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
248
|
+
Carried over from 2026-04-22. The file has grown 189 → 211 LOC (+22) due to new endpoints (moments feedback POST/DELETE, conflict reject_similar). All branching is inside the request router (`handle_moments`, `handle_conflicts`).
|
|
249
|
+
|
|
250
|
+
WEBrick HTTP testing is awkward but not impossible — `Rack::MockRequest` works against the API class directly. Alternatively, exercise the routing by injecting a stub WEBrick request object.
|
|
251
|
+
|
|
252
|
+
**Effort:** 1.5 hours
|
|
253
|
+
**Priority:** Medium-Low
|
|
254
|
+
|
|
255
|
+
### Sleep-Based Test Latency Increased
|
|
256
|
+
|
|
257
|
+
Total sleep-based test cost in `bundle exec rspec`:
|
|
117
258
|
|
|
118
|
-
|
|
259
|
+
| Spec | sleep total | Notes |
|
|
260
|
+
|---|---|---|
|
|
261
|
+
| `spec/claude_memory/ingest/ingester_spec.rb` | 3.03s | mtime resolution, carried |
|
|
262
|
+
| `spec/claude_memory/publish_spec.rb` | 1.1s | carried |
|
|
263
|
+
| `spec/claude_memory/recall_spec.rb` | 0.01s | carried |
|
|
264
|
+
| `spec/claude_memory/dashboard/moments_spec.rb` | 2.2s | **NEW** ordering of activity events |
|
|
265
|
+
| `spec/claude_memory/dashboard/api_spec.rb` | 2.2s | **NEW** activity ordering tests |
|
|
266
|
+
| **Total** | **~8.5s** | up from ~4s last review |
|
|
119
267
|
|
|
120
|
-
|
|
268
|
+
The dashboard sleeps are because activity_events ordering depends on `occurred_at` ISO timestamps, and successive inserts in <1s produce the same timestamp. Two fixes:
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
# Option 1: Inject explicit timestamps (already supported via insert column)
|
|
272
|
+
store.activity_events.insert(occurred_at: Time.now.utc.iso8601, ...)
|
|
273
|
+
store.activity_events.insert(occurred_at: (Time.now + 1).utc.iso8601, ...)
|
|
274
|
+
|
|
275
|
+
# Option 2: Stub Time.now via Timecop or RSpec's allow(Time).to receive(:now)
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Option 1 requires no extra dep. Either eliminates 4.4s of wall time.
|
|
279
|
+
|
|
280
|
+
**File:** `spec/claude_memory/dashboard/moments_spec.rb:130,132`, `api_spec.rb:332,359`
|
|
281
|
+
**Effort:** 30 minutes
|
|
282
|
+
**Priority:** 🟡 Medium (test speed degrades CI loop)
|
|
283
|
+
**Expert principle:** Kent Beck fast feedback
|
|
284
|
+
|
|
285
|
+
### Carried-Forward Issues 🟡
|
|
286
|
+
|
|
287
|
+
| # | Issue | File:Line | Effort |
|
|
288
|
+
|---|---|---|---|
|
|
289
|
+
| 11 | No shared test factory | `spec/spec_helper.rb` | 1 hour |
|
|
121
290
|
|
|
122
291
|
---
|
|
123
292
|
|
|
124
293
|
## 4. Avdi Grimm Perspective
|
|
125
294
|
|
|
126
295
|
### What's Been Fixed ✅
|
|
127
|
-
- DimensionCheck returns a Result value object — no exceptions, no side effects
|
|
128
|
-
- `Embeddings.resolve` raises `ArgumentError` with clear message for unknown providers
|
|
129
|
-
- ApiAdapter raises with descriptive messages for missing API keys
|
|
130
|
-
- Duck typing for embedding providers (no base class)
|
|
131
|
-
- **ApiAdapter now uses typed `ApiError < StandardError`** instead of bare `raise "message"`
|
|
132
|
-
- **Resolver mutable state resolved** — verified that `project_path` and `scope` are already threaded as parameters through the entire method chain; no mutable instance state exists
|
|
133
296
|
|
|
134
|
-
|
|
297
|
+
- New code uses scoped rescues (`rescue Sequel::DatabaseError, JSON::ParserError`) over bare rescues by default. Of 18 new rescue clauses in dashboard files, **13 are scoped to specific exception types**, 5 are bare and all return safe defaults
|
|
298
|
+
- `Result` pattern preserved in embeddings paths
|
|
299
|
+
- `Core::RelativeTime.format` used consistently across new dashboard modules
|
|
300
|
+
|
|
301
|
+
### Bare Rescue Audit (full lib/, current count: 19 bare rescues)
|
|
302
|
+
|
|
303
|
+
The count grew from 5 → 19 because new dashboard code added 5 in `api.rb`. All are defensive (return safe shape):
|
|
304
|
+
|
|
305
|
+
| Location | Context | Returns | Verdict |
|
|
306
|
+
|---|---|---|---|
|
|
307
|
+
| `mcp/handlers/stats_handlers.rb:102` | `fts_legacy?` | `false` | Acceptable — boolean check |
|
|
308
|
+
| `mcp/instructions_builder.rb:147` | `vec_available?` | `false` | Acceptable |
|
|
309
|
+
| `sweep/maintenance.rb:140` | FTS prune | skips row | Acceptable |
|
|
310
|
+
| `commands/hook_command.rb:102` | forked handler | `nil` | Required |
|
|
311
|
+
| `commands/stats_command.rb:276` | `check_fts_format` | no-op | Acceptable |
|
|
312
|
+
| **`dashboard/api.rb:340` (new)** | recall live query | error hash | Acceptable — wide net for unfamiliar errors from Recall pipeline |
|
|
313
|
+
| **`dashboard/api.rb:672` (new)** | `db_stats` aggregation | `{exists:, error:}` | Acceptable |
|
|
314
|
+
| **`dashboard/api.rb:693` (new)** | `db_health` introspection | error hash | Acceptable |
|
|
315
|
+
| **`dashboard/api.rb:728` (new)** | `hooks_health` JSON read | error hash | Acceptable |
|
|
316
|
+
| **`dashboard/api.rb:797` (new)** | `vec_health` | error hash | Acceptable |
|
|
317
|
+
|
|
318
|
+
Verdict: per `Style/RescueStandardError` in Standard Ruby (rejected explicit-rescue change in last review), these are correct. **No action.**
|
|
319
|
+
|
|
320
|
+
### Carried-Forward Issues 🟡
|
|
135
321
|
|
|
136
322
|
| # | Issue | File:Line | Effort |
|
|
137
|
-
|
|
138
|
-
| 13 |
|
|
323
|
+
|---|---|---|---|
|
|
324
|
+
| 13 | Inconsistent payload validation | `hook/handler.rb:53-82` | 30 min |
|
|
325
|
+
|
|
326
|
+
Verified still present.
|
|
327
|
+
|
|
328
|
+
### New Concern
|
|
139
329
|
|
|
140
|
-
|
|
330
|
+
#### F. `digest_command.rb:128` reaches into `Trust`'s private API
|
|
331
|
+
|
|
332
|
+
Documented above (#C). Repeating here under the Avdi lens: the explicit `.send` is a public-API smell. Either the method shouldn't be private, or there should be a public wrapper. Choose.
|
|
141
333
|
|
|
142
334
|
---
|
|
143
335
|
|
|
144
336
|
## 5. Gary Bernhardt Perspective
|
|
145
337
|
|
|
146
338
|
### What's Been Fixed ✅
|
|
147
|
-
- DimensionCheck is pure: takes store + provider, returns immutable Result. No hidden side effects.
|
|
148
|
-
- `clear_stale_embeddings` was moved from hidden infrastructure setup to explicit command-level call.
|
|
149
|
-
- VectorIndex#clear! encapsulates vec0 table knowledge (no raw SQL in command).
|
|
150
|
-
- **Dead Configuration embedding accessors removed** — resolver and ApiAdapter read ENV directly, no unused indirection.
|
|
151
339
|
|
|
152
|
-
|
|
340
|
+
- New dashboard modules continue to honor the imperative-shell / functional-core split:
|
|
341
|
+
- `Trust` does only reads + transformation (no writes)
|
|
342
|
+
- `Moments` does reads + transformation
|
|
343
|
+
- `Reuse` does reads + transformation
|
|
344
|
+
- `Efficacy::Reporter` is **pure** (no DB) — takes events, returns a hash — Bernhardt's dream
|
|
345
|
+
- `Knowledge#summary` returns shaped data; UI logic stays out of the model
|
|
346
|
+
- New value-object-y data: `KIND_TO_EVENT_TYPES`, `FEED_EVENT_TYPES` are frozen module constants
|
|
153
347
|
|
|
154
|
-
|
|
155
|
-
|---|-------|-----------|--------|
|
|
156
|
-
| 14 | **I/O mixed with logic in discover_other_projects** | `mcp/tools.rb:565-614` | 1 hour |
|
|
348
|
+
### Boundaries
|
|
157
349
|
|
|
158
|
-
|
|
350
|
+
```
|
|
351
|
+
HTTP layer: Dashboard::Server (211 LOC, untested) ← imperative shell
|
|
352
|
+
JSON layer: Dashboard::API (807 LOC ⚠ growing) ← needs to shrink to routing
|
|
353
|
+
Subsystems: Conflicts, Moments, Trust, Knowledge, Reuse ← functional core (good)
|
|
354
|
+
Pure helpers: Efficacy::Reporter, ScopedFactResolver ← pure (excellent)
|
|
355
|
+
Query layer: Recall, store datasets ← impure but isolated
|
|
356
|
+
```
|
|
159
357
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
358
|
+
`API` is the wrong layer to be doing JSONL parsing (`extract_user_prompt`), time-window correlation (`find_recall_trigger`), or 3-source aggregation (`timeline`). Each of those wants to be its own pure object.
|
|
359
|
+
|
|
360
|
+
### Test Speed Regression
|
|
361
|
+
|
|
362
|
+
Sleep-based tests are dollar-bills the suite is burning every CI run. Eliminating them is functional-core hygiene — the test should pin behavior, not wait for clock state.
|
|
363
|
+
|
|
364
|
+
### Carried-Forward Issues 🟡
|
|
163
365
|
|
|
164
366
|
| # | Issue | File:Line | Effort |
|
|
165
|
-
|
|
166
|
-
|
|
|
367
|
+
|---|---|---|---|
|
|
368
|
+
| 15 | Sweeper mutable state | `sweep/sweeper.rb:16-17` | 20 min |
|
|
369
|
+
| 16 | `Dir.chdir` in publish tests | `spec/publish_spec.rb:14` | 15 min |
|
|
167
370
|
|
|
168
371
|
---
|
|
169
372
|
|
|
170
373
|
## 6. General Ruby Idioms
|
|
171
374
|
|
|
172
|
-
###
|
|
173
|
-
- **Silent exception swallowing resolved** — 3 bare rescue blocks now log via `ClaudeMemory.logger.debug(...)`:
|
|
174
|
-
- `mcp/instructions_builder.rb:29`
|
|
175
|
-
- `hook/context_injector.rb:47`
|
|
176
|
-
- `commands/checks/vec_check.rb:55`
|
|
375
|
+
### New Items
|
|
177
376
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
377
|
+
| # | Issue | File:Line | Severity | Effort |
|
|
378
|
+
|---|---|---|---|---|
|
|
379
|
+
| 31 | `Dashboard::API` 807 LOC, 11 methods >15 lines (regression of #28) | `dashboard/api.rb` | 🔴 High | 4–6 hours |
|
|
380
|
+
| 32 | `parse_timestamp(value)` duplicated verbatim in api.rb:565 and conflicts.rb:278 | both | 🟢 Low | 15 min |
|
|
381
|
+
| 33 | `stores_for` / `facts_stores_for` near-identical between Conflicts and API | `conflicts.rb:160`, `api.rb:589` | 🟢 Low | 30 min |
|
|
382
|
+
| 34 | `digest_command.rb:128` uses `.send(:utilization)` to call private | `digest_command.rb:128` | 🟡 Medium | 30 min |
|
|
383
|
+
| 35 | Sleep-based dashboard tests add 4.4s to suite | `dashboard/{moments,api}_spec.rb` | 🟡 Medium | 30 min |
|
|
384
|
+
| 36 | DedupeConflictsCommand and ReclassifyReferencesCommand untested | `commands/` | High | 60 min |
|
|
385
|
+
| 37 | `sweep/maintenance.rb` regrew to 456 LOC; 3 methods >50 lines | `sweep/maintenance.rb` | 🟡 Medium | 2 hours |
|
|
386
|
+
| 38 | `Moments#extracted_facts` per-moment join (potential N+1 at 50-row pages) | `moments.rb:231` | 🟡 Medium | 30 min |
|
|
387
|
+
|
|
388
|
+
### Carried-Forward Items
|
|
181
389
|
|
|
182
390
|
| # | Issue | File:Line | Severity | Effort |
|
|
183
|
-
|
|
184
|
-
| 17 |
|
|
185
|
-
|
|
|
391
|
+
|---|---|---|---|---|
|
|
392
|
+
| 17 | ResponseFormatter duplication | `mcp/response_formatter.rb` | 🟡 Medium | 1 hour |
|
|
393
|
+
| 28 | ~~Dashboard::API method extraction~~ — **escalated to #31** | — | — | — |
|
|
394
|
+
| 8 | `upsert_content_item` 11 keyword params | `store/sqlite_store.rb:193` | 🟢 Low | 1 hour |
|
|
395
|
+
| 10 | Sleep-based ingester tests | `spec/ingest/ingester_spec.rb` | 🟢 Low | 1 hour |
|
|
396
|
+
| 11 | No shared test factory | `spec/spec_helper.rb` | 🟢 Low | 1 hour |
|
|
186
397
|
|
|
187
398
|
---
|
|
188
399
|
|
|
189
400
|
## 7. Positive Observations
|
|
190
401
|
|
|
191
|
-
- **
|
|
192
|
-
- **
|
|
193
|
-
- **
|
|
194
|
-
- **
|
|
195
|
-
- **
|
|
196
|
-
- **Good file organization**: ~1 class per file, consistent naming, clear module nesting
|
|
197
|
-
- **Strong test culture**: 1.84:1 test-to-code ratio, behavior-focused tests
|
|
198
|
-
- **Infrastructure abstractions**: `FileSystem`, `InMemoryFileSystem` enable fast tests
|
|
199
|
-
- **Core::Result monad**: Consistent Success/Failure pattern throughout
|
|
200
|
-
- **New embedding subsystem**: Clean duck typing with shared RSpec examples verifying provider contract. DimensionCheck is a textbook value object — pure function, immutable result, no side effects. The resolver uses simple case/when (no over-engineered factory/registry).
|
|
201
|
-
- **VectorIndex#clear!**: Properly encapsulates destructive vec0 operation behind the abstraction boundary
|
|
402
|
+
- **Migration discipline** — round-trip specs, per-migration specs, idempotent DDL. The "treat round-trip migration specs as a release blocker" lesson from `feedback_round_trip_migration_specs.md` got operationalized in 5 days
|
|
403
|
+
- **New commands ship with specs** — DigestCommand and CensusCommand both got direct specs; the two that didn't (Dedupe + Reclassify) are 55-line wrappers over already-tested Maintenance methods, so the gap is small
|
|
404
|
+
- **Dashboard subsystem decomposition** — when 5 new panels (Moments, Reuse, Trust, Knowledge, ScopedFactResolver) all land as their own classes with their own specs, the module-extraction muscle is strong
|
|
405
|
+
- **`Efficacy::Reporter` purity** — 128 LOC, zero I/O, takes events and returns shape. Spec is fast and readable. This is the model the rest of dashboard/ should converge on
|
|
406
|
+
- **No raw SQL added; no N+1 in hot paths; transaction safety maintained** — across +2,011 LOC in 6 days
|
|
202
407
|
|
|
203
408
|
---
|
|
204
409
|
|
|
205
410
|
## 8. Priority Refactoring Recommendations
|
|
206
411
|
|
|
207
|
-
### High Priority (
|
|
412
|
+
### High Priority (This Week — pre-0.10.0 release)
|
|
208
413
|
|
|
209
|
-
|
|
414
|
+
| # | Item | File:Line | Effort | Impact |
|
|
415
|
+
|---|---|---|---|---|
|
|
416
|
+
| 31 | Extract `Dashboard::Timeline` / `Health` / `RecallQuery` / `RecallTriggerFinder` / `UserPromptExtractor` / `FactsQuery` from API | `dashboard/api.rb` | 4–6 hours | API drops 807→~250 LOC; reverses regression |
|
|
417
|
+
| 36 | Add `dedupe_conflicts_command_spec.rb` + `reclassify_references_command_spec.rb` | `spec/claude_memory/commands/` | 1 hour | CLI surface tested |
|
|
210
418
|
|
|
211
419
|
### Medium Priority (Next Sprint)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
|
216
|
-
|
|
|
217
|
-
|
|
|
218
|
-
|
|
|
420
|
+
|
|
421
|
+
| # | Item | File:Line | Effort |
|
|
422
|
+
|---|---|---|---|
|
|
423
|
+
| 34 | Promote `Trust#utilization` to public OR extract `Dashboard::Utilization` | `dashboard/trust.rb`, `digest_command.rb:128` | 30 min |
|
|
424
|
+
| 35 | Replace sleep-based dashboard tests with explicit timestamps | `dashboard/{moments,api}_spec.rb` | 30 min |
|
|
425
|
+
| 37 | Extract long methods from `sweep/maintenance.rb` (`dedupe_open_conflicts`, `restore_multi_value_supersessions`) OR move one-shot cleanups to `Sweep::HistoricalCleanup` | `sweep/maintenance.rb` | 2 hours |
|
|
426
|
+
| 38 | Batch `Moments#extracted_facts` to avoid 50-row N+1 | `moments.rb:231` | 30 min |
|
|
427
|
+
| 17 | ResponseFormatter consolidation (carried) | `mcp/response_formatter.rb` | 1 hour |
|
|
428
|
+
| 13 | Payload validator for hook events (carried) | `hook/handler.rb` | 30 min |
|
|
429
|
+
| E | `dashboard/server_spec.rb` (carried) | `spec/claude_memory/dashboard/` | 1.5 hours |
|
|
219
430
|
|
|
220
431
|
### Low Priority (Later)
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
|
225
|
-
|
|
|
226
|
-
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
|
235
|
-
|
|
432
|
+
|
|
433
|
+
| # | Item | Effort |
|
|
434
|
+
|---|---|---|
|
|
435
|
+
| 32 | DRY `parse_timestamp` (`api.rb:565` ↔ `conflicts.rb:278`) | 15 min |
|
|
436
|
+
| 33 | DRY `stores_for` / `facts_stores_for` | 30 min |
|
|
437
|
+
| 8 | `ContentItemAttributes` value object | 1 hour |
|
|
438
|
+
| 10 | Replace sleep-based ingester tests | 1 hour |
|
|
439
|
+
| 11 | Shared test factory | 1 hour |
|
|
440
|
+
| 15 | Sweeper mutable state | 20 min |
|
|
441
|
+
| 16 | `Dir.chdir` in publish tests | 15 min |
|
|
442
|
+
|
|
443
|
+
### Quick Wins (Today)
|
|
444
|
+
|
|
445
|
+
| # | Item | Effort |
|
|
446
|
+
|---|---|---|
|
|
447
|
+
| 32 | Extract `parse_timestamp` to `Core::RelativeTime` (it already lives there as a value module) | 15 min |
|
|
448
|
+
| 34 | Promote `Trust#utilization` to public | 5 min |
|
|
449
|
+
| 35 | Inject timestamps into dashboard spec inserts | 30 min |
|
|
236
450
|
|
|
237
451
|
---
|
|
238
452
|
|
|
239
453
|
## 9. Conclusion
|
|
240
454
|
|
|
241
|
-
|
|
455
|
+
In 6 days the codebase grew 12% (+2,011 LOC). Most of that growth was healthy — five new dashboard subsystems with specs, three migrations with both per-version and round-trip specs, two new admin commands wrapping already-tested maintenance methods. Migration discipline in particular leveled up: the lesson from `feedback_round_trip_migration_specs.md` shipped as actual release-blocking spec coverage.
|
|
242
456
|
|
|
243
|
-
|
|
457
|
+
**The headline regression is `Dashboard::API`.** Last review marked it medium-priority for per-endpoint extraction. Six days later it's gained 180 LOC, four new methods over 15 lines, and one method (`timeline`) that's now 52 lines. This is the file that most rewards extraction — it's already surrounded by collaborators (`Conflicts`, `Moments`, `Trust`, `Knowledge`, `Reuse`) that prove the per-endpoint pattern works. Doing the extraction now reverses the trend; deferring lets it accumulate another 200 LOC by next review.
|
|
458
|
+
|
|
459
|
+
**Recommended next-action set, in order:**
|
|
460
|
+
|
|
461
|
+
1. **`/quality-update`** to apply #31 (Dashboard::API extraction) and #36 (missing command specs). Target: api.rb ≤ 300 LOC, all commands tested.
|
|
462
|
+
2. Quick wins #32 + #34 + #35 in the same session (~75 min total).
|
|
463
|
+
3. Schedule #37 and #38 for the next sprint — neither is urgent but both compound if left alone.
|
|
464
|
+
4. After #31 lands, `/review-for-quality` again pre-0.10.0 release to confirm the regression closed.
|
|
465
|
+
|
|
466
|
+
The 0.10.0 release should not ship with `dashboard/api.rb` at 807 LOC — the per-endpoint extraction is well-defined, well-precedented, and small-batch (5 extractions × ~1hr each). Doing it before tag is the difference between landing 0.10.0 with a healthy dashboard subsystem vs. burying tech debt in the headline feature of the release.
|
|
244
467
|
|
|
245
468
|
---
|
|
246
469
|
|
|
247
470
|
## Appendix A: Metrics Comparison
|
|
248
471
|
|
|
249
|
-
| Metric |
|
|
250
|
-
|
|
251
|
-
| Ruby files (lib) |
|
|
252
|
-
| LOC (lib) |
|
|
253
|
-
| LOC (spec) |
|
|
254
|
-
|
|
|
255
|
-
| Test
|
|
256
|
-
|
|
|
257
|
-
| Files >
|
|
258
|
-
| Bare rescues (
|
|
259
|
-
|
|
|
260
|
-
| N+1 patterns (
|
|
261
|
-
|
|
|
262
|
-
|
|
|
472
|
+
| Metric | Mar 9 | Mar 19 | Apr 22 (review) | Apr 22 (after update) | **Apr 28 (this review)** |
|
|
473
|
+
|---|---|---|---|---|---|
|
|
474
|
+
| Ruby files (lib) | 112 | 117 | 148 | 150 | **161** (+11 new modules) |
|
|
475
|
+
| LOC (lib) | 11,392 | 12,239 | 17,014 | 17,031 | **19,025** (+2,011) |
|
|
476
|
+
| LOC (spec) | 21,632 | 22,563 | 28,074 | 28,490 | **31,079** (+2,605) |
|
|
477
|
+
| Spec files | 128 | 122 | 154 | 156 | **188** (+32) |
|
|
478
|
+
| Test-to-code ratio | 1.90:1 | 1.84:1 | 1.65:1 | 1.67:1 | **1.63:1** ⬇️ |
|
|
479
|
+
| Files >500 lines | 3 | 0 | 2 | 1 | **2** ⬆️ (api.rb 807, sqlite_store.rb 584) |
|
|
480
|
+
| Files >300 lines | 9 | 9 | 10 | 8 | **8** (same count, different mix) |
|
|
481
|
+
| Bare rescues (justified) | 1 | 0 | 5 | 5 | **19** (14 new, all defensive) |
|
|
482
|
+
| Bare rescues (unsafe) | 0 | 0 | 0 | 0 | **0** ✅ |
|
|
483
|
+
| N+1 patterns (hot paths) | 0 | 0 | 0 | 0 | **0** ✅ |
|
|
484
|
+
| Pure logic classes | 20+ | 22+ | 25+ | 27+ | **32+** (+5 new dashboard modules) |
|
|
485
|
+
| Migration round-trip specs | 0 | 0 | 0 | 0 | **3** (v12→v17, v13→v17, v14→v17) ✅ |
|
|
486
|
+
| Per-migration specs | 0 | 0 | 0 | 0 | **13** (001–017 minus a few) ✅ |
|
|
487
|
+
| Sleep-based test cost | — | — | ~4s | ~4s | **~8.5s** ⬆️ |
|
|
488
|
+
| Untested new commands | — | — | 0 | 0 | **2** (dedupe-conflicts, reclassify-references) |
|
|
489
|
+
| Known correctness bugs | — | 0 | 0 | 0 | **0** ✅ |
|
|
263
490
|
|
|
264
491
|
## Appendix B: File Size Report
|
|
265
492
|
|
|
266
|
-
| File | Mar
|
|
267
|
-
|
|
268
|
-
| `
|
|
269
|
-
| `
|
|
270
|
-
| `
|
|
271
|
-
| `
|
|
272
|
-
| `mcp/
|
|
273
|
-
| `commands/
|
|
274
|
-
| `
|
|
275
|
-
| `
|
|
276
|
-
| `
|
|
277
|
-
| `
|
|
278
|
-
| `
|
|
279
|
-
| `
|
|
280
|
-
| `
|
|
281
|
-
| `
|
|
493
|
+
| File | Mar 19 | Apr 22 (review) | Apr 22 (after update) | **Apr 28 (this review)** | Trend |
|
|
494
|
+
|---|---|---|---|---|---|
|
|
495
|
+
| `dashboard/api.rb` | — | 627 🆕 | 627 | **807** | ⬆️ +180 (+29%) — **regression** |
|
|
496
|
+
| `store/sqlite_store.rb` | 386 | 683 | 544 | **584** | ⬆️ +40 (new tables) |
|
|
497
|
+
| `mcp/tool_definitions.rb` | 334 | 459 | 459 | **459** | — |
|
|
498
|
+
| `sweep/maintenance.rb` | — | 334 | 334 | **456** | ⬆️ +122 — new |
|
|
499
|
+
| `mcp/response_formatter.rb` | 396 | 397 | 397 | **397** | — |
|
|
500
|
+
| `commands/stats_command.rb` | 250 | 346 | 346 | **383** | ⬆️ +37 |
|
|
501
|
+
| `recall/query_core.rb` | 357 | 371 | 371 | **371** | — |
|
|
502
|
+
| `mcp/text_summary.rb` | 258 | 313 | 313 | **313** | — |
|
|
503
|
+
| `dashboard/conflicts.rb` | — | 195 | 195 | **285** | ⬆️ +90 (dedup grouping logic) |
|
|
504
|
+
| `dashboard/trust.rb` | — | — | — | **284** | 🆕 new feed-first sidebar |
|
|
505
|
+
| `resolve/resolver.rb` | 195 | 254 | 254 | **268** | ⬆️ +14 (dedupe + scope_hint fix) |
|
|
506
|
+
| `mcp/tools.rb` | 104 | 249 | 249 | **264** | ⬆️ +15 |
|
|
507
|
+
| `commands/index_command.rb` | 272 | 259 | 259 | **259** | — |
|
|
508
|
+
| `commands/hook_command.rb` | 214 | 215 | 215 | **249** | ⬆️ +34 |
|
|
509
|
+
| `publish.rb` | 221 | 256 | 248 | **248** | — |
|
|
510
|
+
| `dashboard/moments.rb` | — | — | — | **244** | 🆕 feed primitive |
|
|
511
|
+
| `commands/uninstall_command.rb` | 226 | 226 | 226 | **226** | — |
|
|
512
|
+
| `hook/context_injector.rb` | — | 214 | 214 | **225** | ⬆️ +11 |
|
|
513
|
+
| `store/store_manager.rb` | — | 215 | 215 | **215** | — |
|
|
514
|
+
| `infrastructure/schema_validator.rb` | 215 | 215 | 215 | **215** | — |
|
|
515
|
+
| `commands/census_command.rb` | — | — | — | **210** | 🆕 predicate census |
|
|
516
|
+
| `mcp/handlers/setup_handlers.rb` | 211 | 211 | 211 | **211** | — |
|
|
517
|
+
| `dashboard/server.rb` | — | 189 | 189 | **211** | ⬆️ +22 (new endpoints) |
|
|
518
|
+
| `embeddings/model_registry.rb` | — | — | — | **210** | 🆕 |
|
|
519
|
+
| `mcp/server.rb` | — | 206 | 206 | **206** | — |
|
|
520
|
+
| `mcp/handlers/stats_handlers.rb` | — | 205 | 205 | **205** | — |
|
|
521
|
+
| `commands/initializers/hooks_configurator.rb` | — | — | — | **200** | — |
|
|
522
|
+
| `commands/embeddings_command.rb` | — | — | — | **198** | — |
|
|
523
|
+
| `ingest/ingester.rb` | — | — | — | **190** | — |
|
|
524
|
+
| `index/vector_index.rb` | 184 | 184 | 184 | **184** | — |
|
|
525
|
+
| `commands/digest_command.rb` | — | — | — | **181** | 🆕 weekly digest |
|
|
526
|
+
| `mcp/handlers/management_handlers.rb` | — | — | — | **177** | — |
|
|
527
|
+
| `ingest/observation_compressor.rb` | — | — | — | **177** | 🆕 tool-specific compression |
|
|
528
|
+
| `recall.rb` | 94 | 175 | 175 | **175** | — |
|
|
529
|
+
| `core/fact_query_builder.rb` | — | — | — | **174** | — |
|
|
530
|
+
| `mcp/error_classifier.rb` | — | — | — | **171** | — |
|
|
531
|
+
| `embeddings/generator.rb` | — | — | — | **165** | — |
|
|
532
|
+
| `index/lexical_fts.rb` | — | — | — | **153** | — |
|
|
533
|
+
| `dashboard/knowledge.rb` | — | — | — | **136** | 🆕 |
|
|
534
|
+
| `dashboard/efficacy.rb` | — | 127 | 127 | **127** | — |
|
|
535
|
+
| `dashboard/fact_presenter.rb` | — | 109 | 109 | **109** | — |
|
|
536
|
+
| `dashboard/reuse.rb` | — | — | — | **97** | 🆕 |
|
|
537
|
+
| `dashboard/scoped_fact_resolver.rb` | — | — | — | **95** | 🆕 |
|
|
538
|
+
| `commands/reclassify_references_command.rb` | — | — | — | **56** | 🆕 (untested) |
|
|
539
|
+
| `commands/dedupe_conflicts_command.rb` | — | — | — | **55** | 🆕 (untested) |
|
|
540
|
+
|
|
541
|
+
## Appendix C: Methods >15 Lines in Watch-List Files
|
|
542
|
+
|
|
543
|
+
### `dashboard/api.rb` (807 LOC, **42 methods**)
|
|
544
|
+
|
|
545
|
+
| Method | Line | Size | Action |
|
|
546
|
+
|---|---|---|---|
|
|
547
|
+
| `timeline` | 471 | 52 | Extract `Dashboard::Timeline` |
|
|
548
|
+
| `vec_health` | 759 | 46 | Extract into `Dashboard::Health` |
|
|
549
|
+
| `recall` | 315 | 41 | Extract `Dashboard::RecallQuery` |
|
|
550
|
+
| `facts` | 373 | 39 | Extract `Dashboard::FactsQuery` |
|
|
551
|
+
| `activity_detail` | 149 | 37 | Extract event-detail builder |
|
|
552
|
+
| `hooks_health` | 704 | 32 | Extract into `Dashboard::Health` |
|
|
553
|
+
| `find_recall_trigger` | 193 | 32 | Extract `Dashboard::RecallTriggerFinder` |
|
|
554
|
+
| `efficacy` | 439 | 31 | Move session-window logic into `Efficacy::Loader` |
|
|
555
|
+
| `extract_user_prompt` | 237 | 29 | Extract `Dashboard::UserPromptExtractor` |
|
|
556
|
+
| `session_summary` | 119 | 29 | Extract aggregator |
|
|
557
|
+
| `db_stats` | 647 | 28 | Extract into `Dashboard::Health` |
|
|
558
|
+
| `db_health` | 676 | 25 | Extract into `Dashboard::Health` |
|
|
559
|
+
| `load_content_item` | 603 | 21 | Could move into `FactPresenter` or its own loader |
|
|
560
|
+
| `activity` | 48 | 20 | Acceptable — thin wrapper |
|
|
561
|
+
| `facts_seen_in_recent_recalls` | 418 | 20 | Move into `Dashboard::FactsQuery` |
|
|
562
|
+
| `collect_configured_hook_types` | 739 | 19 | Move into `Dashboard::Health` |
|
|
563
|
+
| `serialize_recall_fact` | 545 | 19 | Move into `Dashboard::RecallQuery` |
|
|
564
|
+
| `health` | 14 | 18 | Becomes 3-liner after `Dashboard::Health` extraction |
|
|
565
|
+
| `reject_fact` | 294 | 16 | Acceptable — public surface |
|
|
566
|
+
|
|
567
|
+
### `sweep/maintenance.rb` (456 LOC)
|
|
568
|
+
|
|
569
|
+
| Method | Line | Size | Action |
|
|
570
|
+
|---|---|---|---|
|
|
571
|
+
| `dedupe_open_conflicts` | 273 | 58 | Extract per-group `resolve_duplicate_group` helper |
|
|
572
|
+
| `restore_multi_value_supersessions` | 185 | 57 | Already documented; could extract `compute_restore_decisions` |
|
|
573
|
+
| `dedupe_multi_value_facts` | 58 | 34 | Acceptable — well-bounded transactional op |
|
|
574
|
+
| `reclassify_references` | 340 | 26 | Acceptable |
|
|
575
|
+
| `prune_old_content` | 130 | 16 | Acceptable |
|
|
576
|
+
|
|
577
|
+
### `store/sqlite_store.rb` (584 LOC)
|
|
578
|
+
|
|
579
|
+
| Method | Line | Size | Notes |
|
|
580
|
+
|---|---|---|---|
|
|
581
|
+
| `upsert_content_item` | 193 | 27 | 11 kwargs (carried #8) |
|
|
582
|
+
| `reject_fact` | 410 | 25 | Conflict resolution in transaction |
|
|
583
|
+
| `insert_fact` | 332 | 22 | Many optional fields |
|
|
584
|
+
| `upsert_moment_feedback` | 123 | 21 | New — transaction with retry |
|
|
585
|
+
| `update_fact` | 373 | 19 | Generic update via allowed-keys |
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
## Historical Reviews
|
|
590
|
+
|
|
591
|
+
Earlier reviews (Jan 29, Feb 4, Mar 9, Mar 19) tracked the codebase from ~8,000 → 12,239 LOC. Their highlights, preserved here:
|
|
592
|
+
|
|
593
|
+
- **Jan 29 (initial)** — Identified Tools and Recall god-object risks; introduced first metrics baseline.
|
|
594
|
+
- **Feb 4** — Carried-forward items #17–#25 (DateTime migration, command manager helper, release_connections polymorphism, provenance batch insert, result objects). All still low-priority and open.
|
|
595
|
+
- **Mar 9** — Three files >500 LOC; bare rescue counted; vector index work landed.
|
|
596
|
+
- **Mar 19** — Successful refactor wave: `RetryHandler` + `SchemaManager` extracted from `SQLiteStore` (547 → 386); `Tools` reduced to 104-line dispatcher with 6 handler modules; `Recall` to 94-line facade. **Established the module-inclusion pattern** that has been reused successfully for LLMCache, MetricsAggregator, and the dashboard subsystems.
|
|
597
|
+
|
|
598
|
+
The 2026-04-22 review absorbed the 39% codebase growth (+4,775 LOC) without correctness regressions and resolved its top two watch-items (`SQLiteStore` regrowth, dashboard test coverage). It left `Dashboard::API` extraction as a medium-priority watch item — which the present review (2026-04-28) escalates to high-priority based on the 180-LOC regression in 6 days.
|
|
282
599
|
|
|
283
600
|
---
|
|
284
601
|
|
|
285
|
-
**Next review:** After
|
|
602
|
+
**Next review:** After #31 (Dashboard::API extraction) lands, or pre-0.10.0 release tag.
|