Skip to content

fix: audit fixes — atomic decisions, shell quoting, dead config cleanup, docs (v0.8.1)#151

Merged
wcatz merged 8 commits intomainfrom
fix/audit-fixes-v0.8.1
Apr 18, 2026
Merged

fix: audit fixes — atomic decisions, shell quoting, dead config cleanup, docs (v0.8.1)#151
wcatz merged 8 commits intomainfrom
fix/audit-fixes-v0.8.1

Conversation

@wcatz
Copy link
Copy Markdown
Owner

@wcatz wcatz commented Apr 18, 2026

Changes

  • fix(memory): Wrap RecordDecision dual-INSERT in a single transaction — prevents zombie decisions if the memory row fails (return `("", err)` not `(id, err)` on second insert failure)
  • fix(mcpinit): Use POSIX single-quote escaping for hook command path — old double-quote approach was insufficient for shell metacharacters (`$`, backticks, semicolons); idempotency guard updated to match new quoted form
  • chore(config): Remove 7 dead struct types from pre-v0.8.0 daemon era (Voice, GitHub, Telegram, Calendar, Google, Briefing, CostReport) — none referenced in MCP-only codebase; cleaned defaults map and envOverrides
  • fix(memory): Log `slog.Warn` when `sanitizeFTS` truncates a query beyond 10 terms
  • docs: Document `learned_context` resource, task state machine transitions, hook output format in README
  • chore: Code review fixes — hasHook idempotency substring, README task status wording, config whitespace

Test plan

  • `go test ./...` passes (126 tests)
  • `go vet ./...` clean
  • Spec compliance reviewed per task
  • Final code review passed (critical idempotency regression caught and fixed)

Summary by CodeRabbit

  • New Features

    • Transitioned to MCP Memory Server architecture with streamlined CLI subcommands
    • Hybrid memory search combining vector similarity and full-text search
    • Enhanced session context injection via hook-based workflow
    • Atomic task and decision persistence
  • Documentation

    • Updated architecture documentation for new MCP-based design
    • Removed TUI and VSCode extension documentation
  • Chores

    • Simplified configuration by removing voice, calendar, and notification integrations
    • Improved shell command escaping for better cross-platform compatibility

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 18, 2026

Warning

Rate limit exceeded

@wcatz has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 54 minutes and 50 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 54 minutes and 50 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3f3475f6-5fed-4116-b041-726016511fe8

📥 Commits

Reviewing files that changed from the base of the PR and between d5e5773 and c2133be.

📒 Files selected for processing (16)
  • .gitignore
  • AUDIT_REPORT.md
  • AUDIT_SUMMARY.md
  • CLAUDE.md
  • README.md
  • docs/architecture.md
  • docs/tui.md
  • docs/vscode.md
  • internal/config/config.example.yaml
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/mcpinit/init.go
  • internal/mcpinit/init_test.go
  • internal/memory/decisions.go
  • internal/memory/store.go
  • internal/memory/store_test.go
📝 Walkthrough

Walkthrough

This PR removes TUI and VSCode extension documentation, eliminates configuration options for voice, GitHub, Telegram, calendar, Google, briefing, and cost reporting, and updates architecture documentation to reflect an MCP server-centric design. It also includes internal improvements: transactional decision recording, shell-safe hook initialization, and FTS term truncation logging.

Changes

Cohort / File(s) Summary
Documentation Deletions
AUDIT_REPORT.md, AUDIT_SUMMARY.md, docs/tui.md, docs/vscode.md
Removed audit reports and TUI/VSCode extension documentation no longer applicable to MCP server architecture.
Documentation Updates
CLAUDE.md, README.md, docs/architecture.md
Updated architecture overview from multi-mode application to MCP server focus; added task lifecycle semantics, learned context flow, and SessionStart hook contract; revised package map and memory consolidation sections.
Configuration Removal
internal/config/config.example.yaml, internal/config/config.go, internal/config/config_test.go
Removed voice, GitHub, Telegram, calendar, Google, briefing, and cost report configuration sections and their corresponding exported struct types; removed associated test case.
MCP Init Improvements
internal/mcpinit/init.go, internal/mcpinit/init_test.go
Added shellQuote helper for POSIX-safe shell quoting; updated hook deduplication check; added comprehensive shell-quote test coverage.
Memory Layer Enhancements
internal/memory/decisions.go, internal/memory/store.go, internal/memory/store_test.go
Wrapped RecordDecision inserts in SQL transaction for atomicity; added FTS truncation warning log; added integration test validating memory persistence.
Dotfiles
.gitignore
Added .worktrees/ to ignore patterns.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Possibly related PRs

Poem

🐰 Hop away, TUI and VSCode's gleam,
Voice and briefings fade like dream,
MCP shines with focused care,
Transactions keep decisions fair,
Shell-quoted hooks and logs so true—
Our codebase hops to something new! 🌿

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main changes: atomic decisions, shell quoting, config cleanup, and documentation fixes for v0.8.1, matching the core objectives of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/audit-fixes-v0.8.1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
internal/mcpinit/init_test.go (1)

171-191: Nice coverage — consider adding an empty-string case.

The cases cover the tricky ones (spaces, $, backticks, embedded single quotes). One small addition worth considering: an empty input, since shellQuote("") should yield "''" (a valid empty POSIX word) and that path is otherwise untested.

 {"/path/it's/ghost", "'/path/it'\\''s/ghost'"},
+{"", "''"},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/mcpinit/init_test.go` around lines 171 - 191, Add an empty-string
test to TestShellQuote to cover shellQuote("") returning "''": update the tests
slice in TestShellQuote (in internal/mcpinit/init_test.go) to include {input:
"", want: "''"} so the empty POSIX-word case is asserted alongside the existing
cases for shellQuote.
internal/memory/store.go (1)

984-986: Use the store's configured logger rather than the global slog default.

Every other log call in Store uses s.logger (which is injected via NewStore), but this new warning calls the package-level slog.Warn. That bypasses the handler/level the caller configured (e.g., the io.Discard handler used in mcpinit.importMemories), so truncation will show up in stderr even when callers explicitly silence the store. Since sanitizeFTS is a free function, consider making it a method on *Store or threading the logger through.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/memory/store.go` around lines 984 - 986, The warn call in
sanitizeFTS uses the package-level slog.Warn which bypasses the store's
configured logger; make sanitizeFTS either a method on *Store (e.g., func (s
*Store) sanitizeFTS(...)) or add a logger param so it can call s.logger.Warn (or
logger.Warn) instead of slog.Warn; update callers (where sanitizeFTS is invoked)
to use the new method/signature and ensure the Store created via NewStore
continues to supply the proper s.logger.
internal/config/config.go (1)

147-156: Nit: single-entry envOverrides map.

Now that only GHOST_SERVER_AUTH_TOKEN remains, the map + loop is a bit ceremonious. A direct check reads cleaner:

-envOverrides := map[string]string{
-    "GHOST_SERVER_AUTH_TOKEN": "server.auth_token",
-}
-for envKey, koanfKey := range envOverrides {
-    if val := os.Getenv(envKey); val != "" {
-        _ = k.Load(confmap.Provider(map[string]interface{}{
-            koanfKey: val,
-        }, "."), nil)
-    }
-}
+if val := os.Getenv("GHOST_SERVER_AUTH_TOKEN"); val != "" {
+    _ = k.Load(confmap.Provider(map[string]interface{}{
+        "server.auth_token": val,
+    }, "."), nil)
+}

Fine to keep the map form if you expect to re-add underscore-in-tag keys soon.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/config/config.go` around lines 147 - 156, The envOverrides map and
loop are now overkill for a single key; replace the map+for loop with a direct
check for os.Getenv("GHOST_SERVER_AUTH_TOKEN") and, if non-empty, call k.Load
with confmap.Provider(map[string]interface{}{"server.auth_token": val}, ".")
(same as current call site), removing the envOverrides variable and loop; update
the code that uses envOverrides, keeping the call to k.Load, confmap.Provider,
and the key "server.auth_token" intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/architecture.md`:
- Around line 7-15: Multiple fenced code blocks in docs/architecture.md lack
language identifiers causing markdownlint MD040 warnings; update each unlabeled
triple-backtick fence (the blocks listing CLI commands, file listings like
cmd/ghost/main.go, architecture diagrams/text flows, and example command
snippets such as the ghost mcp list and ghost reflect examples) to include an
appropriate language tag (e.g., bash for shell/CLI samples, text for plain
text/diagrams) so all code fences are labeled and the linter warnings are
resolved.
- Around line 84-85: The documentation incorrectly states the SessionStart hook
receives a JSON field named projectPath; update the doc so the hook's stdin JSON
is described as containing cwd (required) and an optional source, and clarify
that lookupProject(db, cwd) uses cwd for path-prefix matching (with name
fallback) — ensure SessionStart, cwd, source, and lookupProject are referenced
precisely and remove or replace any mention of projectPath.
- Around line 67-70: The docs use shorthand like
"ghost_task_create/update/complete" which can be misinterpreted; update the
listing to use the exact MCP tool identifiers (e.g., replace
"ghost_task_create/update/complete" with the full, separate identifiers such as
"ghost_tasks_create", "ghost_tasks_update", "ghost_tasks_complete"), ensure
other entries use their exact names (e.g., "ghost_decision_record" matches the
implemented "ghost_decision_record" that calls store.RecordDecision(), and
"ghost_health" remains as the exact tool id used for store metadata queries),
and expand the ellipses so all 16 tools are enumerated by their exact MCP
identifiers to avoid failed calls.

In `@internal/mcpinit/init.go`:
- Around line 192-194: The status check is looking for the wrong literal ("ghost
hook session-start") so it won't match the quoted hookCmd produced by
shellQuote; update the call site that currently uses sf.hasHook("SessionStart",
"ghost hook session-start") to instead check for the same substring used when
creating the hook (sf.hasHook("SessionStart", "hook session-start")) so
idempotency holds; additionally add a regression test that runs ensureHook (with
ghostBin set to a path containing spaces) and then asserts
sf.hasHook("SessionStart", "hook session-start") returns true to prevent
regressions.

In `@README.md`:
- Around line 163-164: Update the "Task statuses" line to clarify these are
recommended conventions, not enforced transitions: change the phrasing around
"Task statuses: `pending` → `active` → `done`" to state these are the
typical/proposed progression and that the current implementation accepts any
valid status value without transition guards; mention that tasks can be set to
`blocked` from any non-done state and unblocked back to `active`, and add a
brief note pointing readers to the code/API (or say "implementation") for
enforcement details so users won't assume strict state-machine guarantees.

---

Nitpick comments:
In `@internal/config/config.go`:
- Around line 147-156: The envOverrides map and loop are now overkill for a
single key; replace the map+for loop with a direct check for
os.Getenv("GHOST_SERVER_AUTH_TOKEN") and, if non-empty, call k.Load with
confmap.Provider(map[string]interface{}{"server.auth_token": val}, ".") (same as
current call site), removing the envOverrides variable and loop; update the code
that uses envOverrides, keeping the call to k.Load, confmap.Provider, and the
key "server.auth_token" intact.

In `@internal/mcpinit/init_test.go`:
- Around line 171-191: Add an empty-string test to TestShellQuote to cover
shellQuote("") returning "''": update the tests slice in TestShellQuote (in
internal/mcpinit/init_test.go) to include {input: "", want: "''"} so the empty
POSIX-word case is asserted alongside the existing cases for shellQuote.

In `@internal/memory/store.go`:
- Around line 984-986: The warn call in sanitizeFTS uses the package-level
slog.Warn which bypasses the store's configured logger; make sanitizeFTS either
a method on *Store (e.g., func (s *Store) sanitizeFTS(...)) or add a logger
param so it can call s.logger.Warn (or logger.Warn) instead of slog.Warn; update
callers (where sanitizeFTS is invoked) to use the new method/signature and
ensure the Store created via NewStore continues to supply the proper s.logger.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b548f962-3160-436a-9516-904b86b83930

📥 Commits

Reviewing files that changed from the base of the PR and between 2d1a31a and d5e5773.

📒 Files selected for processing (16)
  • .gitignore
  • AUDIT_REPORT.md
  • AUDIT_SUMMARY.md
  • CLAUDE.md
  • README.md
  • docs/architecture.md
  • docs/tui.md
  • docs/vscode.md
  • internal/config/config.example.yaml
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/mcpinit/init.go
  • internal/mcpinit/init_test.go
  • internal/memory/decisions.go
  • internal/memory/store.go
  • internal/memory/store_test.go
💤 Files with no reviewable changes (5)
  • docs/vscode.md
  • internal/config/config_test.go
  • AUDIT_SUMMARY.md
  • AUDIT_REPORT.md
  • docs/tui.md

Comment thread docs/architecture.md
Comment on lines 7 to 15
```
ghost Interactive bubbletea TUI (default)
ghost "query" One-shot mode (no TUI)
echo ... | ghost Pipe mode (stdin)
ghost serve HTTP daemon + subsystems
ghost mcp MCP server (stdio)
ghost mcp MCP server on stdio (used by Claude Code, Cursor, Goose)
ghost mcp init Configure Claude Code integration
ghost mcp status Health check
ghost hook session-start SessionStart hook (called by Claude Code)
ghost reflect <project> Manual memory consolidation
ghost upgrade Self-update from GitHub Releases
ghost version Print version
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add language tags to fenced code blocks to satisfy markdownlint MD040.

Current unlabeled fences trigger lint warnings and reduce rendering/tooling quality.

✏️ Suggested doc fix
-```
+```bash
 ghost mcp              MCP server on stdio (used by Claude Code, Cursor, Goose)
 ...
-```
+```

-```
+```text
 cmd/ghost/main.go          CLI entrypoint + subcommand dispatch
 ...
-```
+```

-```
+```text
 Claude Code / Cursor → stdio JSON-RPC → mcpserver
 ...
-```
+```

-```
+```text
 Claude Code session opens
 ...
-```
+```

-```
+```text
 ghost reflect <project> --apply
 ...
-```
+```

-```
+```text
 embedding.Worker goroutine:
 ...
-```
+```

Also applies to: 19-54, 59-79, 82-89, 92-104, 108-119

🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 7-7: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/architecture.md` around lines 7 - 15, Multiple fenced code blocks in
docs/architecture.md lack language identifiers causing markdownlint MD040
warnings; update each unlabeled triple-backtick fence (the blocks listing CLI
commands, file listings like cmd/ghost/main.go, architecture diagrams/text
flows, and example command snippets such as the ghost mcp list and ghost reflect
examples) to include an appropriate language tag (e.g., bash for shell/CLI
samples, text for plain text/diagrams) so all code fences are labeled and the
linter warnings are resolved.

Comment thread docs/architecture.md
Comment on lines +67 to +70
ghost_task_create/update/complete → store.CreateTask()...
ghost_decision_record → store.RecordDecision()
ghost_health → store metadata query
... 16 tools total
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Tool names should use the exact MCP identifiers, not shorthand.

Line 67’s ghost_task_create/update/complete can be interpreted as callable names, but implemented tools use explicit identifiers (e.g., ghost_tasks_update). Please list exact tool names to avoid failed calls.

✏️ Suggested doc fix
-                          ghost_task_create/update/complete → store.CreateTask()...
+                          ghost_tasks_create / ghost_tasks_update / ghost_tasks_complete → store.CreateTask()...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/architecture.md` around lines 67 - 70, The docs use shorthand like
"ghost_task_create/update/complete" which can be misinterpreted; update the
listing to use the exact MCP tool identifiers (e.g., replace
"ghost_task_create/update/complete" with the full, separate identifiers such as
"ghost_tasks_create", "ghost_tasks_update", "ghost_tasks_complete"), ensure
other entries use their exact names (e.g., "ghost_decision_record" matches the
implemented "ghost_decision_record" that calls store.RecordDecision(), and
"ghost_health" remains as the exact tool id used for store metadata queries),
and expand the ellipses so all 16 tools are enumerated by their exact MCP
identifiers to avoid failed calls.

Comment thread docs/architecture.md
Comment on lines +84 to +85
→ ghost hook session-start (stdin: JSON with cwd + projectPath)
→ lookupProject(db, cwd) # path-prefix match OR name fallback
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

SessionStart input fields are documented inaccurately.

Line 84 mentions projectPath, but hook input parsing is based on cwd (and optional source). This should be corrected to keep the contract precise.

✏️ Suggested doc fix
-  → ghost hook session-start (stdin: JSON with cwd + projectPath)
+  → ghost hook session-start (stdin: JSON with cwd; optional source)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
→ ghost hook session-start (stdin: JSON with cwd + projectPath)
→ lookupProject(db, cwd) # path-prefix match OR name fallback
→ ghost hook session-start (stdin: JSON with cwd; optional source)
→ lookupProject(db, cwd) # path-prefix match OR name fallback
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/architecture.md` around lines 84 - 85, The documentation incorrectly
states the SessionStart hook receives a JSON field named projectPath; update the
doc so the hook's stdin JSON is described as containing cwd (required) and an
optional source, and clarify that lookupProject(db, cwd) uses cwd for
path-prefix matching (with name fallback) — ensure SessionStart, cwd, source,
and lookupProject are referenced precisely and remove or replace any mention of
projectPath.

Comment thread internal/mcpinit/init.go
Comment thread README.md
wcatz added 8 commits April 18, 2026 14:39
- Rewrite CLAUDE.md: remove TUI/orchestrator/tool/prompt/mode/project refs
- Rewrite docs/architecture.md: reflect stripped package set and data flows
- Rewrite config.example.yaml: remove telegram/google/github/voice/briefing sections
- Delete stale docs/tui.md, docs/vscode.md, AUDIT_REPORT.md, AUDIT_SUMMARY.md
…, Telegram, Calendar, Google, Briefing, CostReport)
@wcatz wcatz force-pushed the fix/audit-fixes-v0.8.1 branch from d5e5773 to c2133be Compare April 18, 2026 18:39
@wcatz wcatz merged commit add1a1a into main Apr 18, 2026
5 checks passed
@wcatz wcatz deleted the fix/audit-fixes-v0.8.1 branch April 18, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant