Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
67f8e8e
[Docs] Re-study all 8 influencer repos (2026-06-30 notes)
codenamev Jun 30, 2026
4d344fd
[Docs] Consolidate influencer re-study into improvements.md (#76-#91)
codenamev Jun 30, 2026
71b4228
[Docs] Add 2026-07-01 full-codebase quality review
codenamev Jul 8, 2026
652fcaa
[Quality] Extract Core::Percentile from duplicated method
codenamev Jul 8, 2026
84b1909
[Quality] Inject clock into Observe::Reflector
codenamev Jul 8, 2026
9a2c64c
[Quality] Make increment_corroboration return update status
codenamev Jul 8, 2026
35af007
[Quality] Rename recent_observations min_priority -> max_priority
codenamev Jul 8, 2026
c1aea81
[Quality] Cap Trust#used_fact_pairs event load
codenamev Jul 8, 2026
a89f294
[Quality] Extract OtelWrites + ObservationWrites from SQLiteStore
codenamev Jul 8, 2026
c66f363
[Quality] Batch Dashboard::Moments content + fact loading (N+1 fix)
codenamev Jul 8, 2026
1586806
[Quality] Extract pure Observe::DedupPlanner from Reflector
codenamev Jul 8, 2026
2bc396f
[Docs] Mark 2026-07-01 review items done after /quality-update
codenamev Jul 8, 2026
46f8a26
[Quality] Wrap LexicalFTS#rebuild! in a transaction
codenamev Jul 8, 2026
6a6e309
[Quality] Extract Core::Jaccard for the shared set-math
codenamev Jul 8, 2026
156ca7c
[Quality] Extract Core::TokenBudget value object
codenamev Jul 8, 2026
de45e5e
[Quality] Extract Observe::ObservationStats (collapse triplicated agg…
codenamev Jul 9, 2026
fffe18b
[Quality] Extract pure Hook::ContextPresenter from ContextInjector
codenamev Jul 9, 2026
9dde790
[Quality] Break up Sweep::Maintenance#dedupe_open_conflicts (58 lines)
codenamev Jul 9, 2026
1ddcc2d
[Docs] Mark Medium-tier quality items done after /quality-update
codenamev Jul 9, 2026
787236f
[Quality] Extract Sweep::HistoricalCleanup from Maintenance
codenamev Jul 9, 2026
7241644
[Docs] Mark Q6 complete (Sweep::HistoricalCleanup extracted)
codenamev Jul 9, 2026
5790876
[Quality] Wrap VectorIndex#insert_embedding in a transaction
codenamev Jul 9, 2026
aeab517
[Quality] Route observation token estimates through Core::TokenEstimator
codenamev Jul 9, 2026
415135a
[Quality] Add OTel::Status spec
codenamev Jul 9, 2026
51cd0e2
[Quality] Extract Observe::Promotion shared service
codenamev Jul 9, 2026
b43b93f
[Docs] Mark Low-tier quality items (5 done, 4 consciously deferred)
codenamev Jul 9, 2026
ed7d64e
[Docs] Influencer re-study 2026-07-09 (3 repos moved, 4 items #92-95)
codenamev Jul 9, 2026
fdbe1d3
[Feature] Recognize server_tool_use (advisor) blocks in ToolExtractor
codenamev Jul 9, 2026
8044f17
[Feature] Emit valid SessionStart hookSpecificOutput on no-op/error p…
codenamev Jul 9, 2026
ab89924
[Feature] Truncation-aware ingest gate (Distill::TruncationDetector +…
codenamev Jul 9, 2026
1a32d3c
[Docs] Mark #92, #94, #95 implemented; defer #93 (AST scanner)
codenamev Jul 9, 2026
9ce831c
[Feature] Add error-handling anti-pattern scanner + rake gate
codenamev Jul 10, 2026
572f1ba
[Docs] Mark #93 (error-handling anti-pattern scanner) implemented
codenamev Jul 10, 2026
2be5242
[Fix] Bound empty-rescue override span by next clause; detect ::Stand…
codenamev Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,50 @@ namespace :plugin do
end
end

task default: %i[spec standard]
namespace :audit do
desc "Scan lib/ for error-handling anti-patterns (swallowed errors); exits non-zero on unignored blocking findings"
task :error_handling do
require_relative "lib/claude_memory/audit/error_handling_scanner"

scanner = ClaudeMemory::Audit::ErrorHandlingScanner.new
offenses = Dir["lib/**/*.rb"].sort.flat_map do |path|
scanner.scan(File.read(path), path: path)
end

active = offenses.reject(&:overridden?)
overridden = offenses.select(&:overridden?)
blocking = active.select(&:blocking?)

puts "Error-handling anti-pattern scan (lib/)"
puts

if active.empty?
puts " No active findings."
else
active.sort_by { |o| [o.path, o.line] }.each do |o|
puts format(" %-45s %-18s [%s] %s", "#{o.path}:#{o.line}", o.pattern, o.severity, o.message)
end
end

unless overridden.empty?
puts
puts "Documented swallows (overridden):"
overridden.sort_by { |o| [o.path, o.line] }.each do |o|
puts format(" %-45s %-18s — %s", "#{o.path}:#{o.line}", o.pattern, o.override_reason)
end
end

puts
puts format("Summary: %d blocking, %d advisory, %d overridden",
blocking.size, active.size - blocking.size, overridden.size)

if blocking.any?
puts
puts "Blocking findings must re-raise, log, return a real value, or be annotated with"
puts " # [ANTI-PATTERN IGNORED]: <reason>"
abort "audit:error_handling failed with #{blocking.size} blocking finding(s)"
end
end
end

task default: %i[spec standard audit:error_handling]
49 changes: 49 additions & 0 deletions docs/audit_runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,55 @@ Exit code is `0` when `ok: true`, `1` otherwise. `--no-exit` always returns `0`.
- For a bad `corroboration_count`, re-derive sighting counts via the Reflector's dedup pass.
- For an unknown status, find the writer that bypassed `insert_observation` (the only sanctioned insert path).

### C015 — Truncated source content

**Severity:** info

**Scope:** both DBs.

**Triggered when:** a `content_items` row's `raw_text` carries a host-truncation marker — e.g. `[Read output capped at N lines]`, `[Truncated: N chars]`, "output was truncated", or an "N lines omitted / N characters truncated" count form (see `Distill::TruncationDetector`).

**Why it matters:** Claude Code caps large tool output (notably Read) and leaves a marker in the transcript. Facts the distiller extracts from such a fragment were drawn from incomplete content, not complete ground truth — the same false-positive class as documentation example text the distiller takes literally. The detector deliberately does not recover the full file from disk: ingest runs after the fact and the file may have changed since, so disk recovery at ingest would be temporally unsound.

**Remediation:**
- Trace facts back with `claude-memory explain <fact_id>`.
- Reject any fact that asserts something the truncated fragment could not actually confirm.
- If you need the full content, re-run the relevant tool without truncation so the complete text is re-ingested.

## Source-level gate: `rake audit:error_handling`

Separate from the `C###` checks above. Those inspect **memory-corpus state** (the DBs); this inspects **Ruby source** and enforces the "swallowed errors stay visible" convention as a deterministic, non-LLM gate. It runs as part of `rake default` and can be invoked directly:

```bash
bundle exec rake audit:error_handling
```

Backed by `ClaudeMemory::Audit::ErrorHandlingScanner` — a Prism AST walker (not regex), so the checks are structural facts, not pattern guesses.

**What it flags:**

| Pattern | Severity | Meaning |
| --- | --- | --- |
| `EMPTY_RESCUE` | error (blocking) | A **broad** rescue (bare, `StandardError`, or `Exception`) with an empty or comment-only body. |
| `RESCUE_NIL` | error (blocking) | A **broad** rescue whose body is only `nil`, or a `expr rescue nil` modifier. |
| `RESCUE_EXCEPTION` | warn | Rescues `Exception` — also catches signals and system errors. |
| `ERROR_STRING_MATCH` | info | Control flow branches on the exception's `.message` text — brittle across library/Ruby versions. |

**Breadth is the discriminator.** A *narrow, typed* rescue that returns `nil` — `rescue JSON::ParserError; nil` — is a documented "unparseable/unavailable → nil" contract, the idiom used throughout `lib/`, and is intentionally **not** flagged. Only broad swallows (which discard the whole `StandardError` hierarchy invisibly) block.

**Override — documenting a deliberate swallow.** A genuine best-effort/background swallow stays green by annotating it inside or directly above the rescue:

```ruby
rescue
# [ANTI-PATTERN IGNORED]: background async process must never propagate errors to the caller
nil
end
```

Overridden offenses are still reported (under "Documented swallows") but never block. The point is not to permit silent swallows but to convert them into *documented* ones. `lib/` currently carries three: the hook background-close, the stats FTS-metadata read, and the sweep FTS-GC removal.

**Remediation for a new blocking finding:** re-raise, log, return a real value, narrow the rescue to a specific exception class, or — if the swallow is genuinely intended — add the `[ANTI-PATTERN IGNORED]: <reason>` marker.

## Adding a new check

The audit is extensible by design.
Expand Down
Loading
Loading