# Code Review: Utilities and CLI

**Scope:** `qc_trace/utils/`, `qc_trace/cli/`, `qc_trace/__init__.py`
**Reviewer:** Claude Opus 4.6
**Date:** 2026-02-07

---

## Summary

The utilities layer (`qc_trace/utils/`) contains four modules: `cursor_parser.py`, `sqlite.py`, `schema_extractor.py`, and `repo_resolver.py`. The CLI layer (`qc_trace/cli/traced.py`) implements a daemon management tool. This review examines dead code, redundancies, code quality, security, and CLI correctness.

**Key findings:**
- `sqlite.py` is entirely dead code -- exported via `__init__.py` but never imported by any consumer
- `schema_extractor.py` is entirely dead code -- same situation, only self-referenced in its own docstring
- `cursor_parser.py` has a duplicate slug decoder that is worse than the one in `repo_resolver.py`
- `cursor_parser.py` has three functions (`parse_mcp_tool_definition`, `parse_mcp_server_metadata`, `discover_cursor_projects`) only used by tests
- `traced.py` has a file descriptor leak and platform-specific assumptions
- `sqlite.py` has a SQL injection vector via un-parameterized `table` and `columns` arguments

---

## Critical

### C1. SQL Injection in `read_sqlite_table()` via `table` and `columns` parameters

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/utils/sqlite.py:192-193`

```python
col_str = ", ".join(columns) if columns else "*"
query = f"SELECT {col_str} FROM {table}"  # noqa: S608
```

The `table` name and `columns` list are interpolated directly into the SQL string without sanitization. The `# noqa: S608` comment acknowledges the Bandit warning but suppresses it rather than fixing it. While the `where` clause supports parameterized queries (`:195-201`), the table name and column names do not.

If any upstream caller ever passes user-controlled input for `table` or `columns`, this enables SQL injection. The fact that the database is opened read-only (`?mode=ro`) limits the blast radius but does not eliminate it -- data exfiltration from other tables is still possible.

**Recommendation:** Validate `table` and `columns` against the actual schema (`SELECT name FROM pragma_table_info(?)`) or at minimum apply an allowlist regex like `^[a-zA-Z_][a-zA-Z0-9_]*$`.

### C2. `sqlite.py` is entirely dead code -- no consumer imports any function

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/utils/sqlite.py` (390 lines)

The module is re-exported via `qc_trace/utils/__init__.py:9-17`, but a project-wide search for actual usage (`from qc_trace.utils import read_vscdb`, `from qc_trace.utils import read_sqlite_table`, etc.) returns **zero results** outside of `__init__.py` itself. No daemon code, no CLI code, no test file imports any sqlite utility.

This is 390 lines of untested, unused code carrying a SQL injection vulnerability.

**Recommendation:** Either integrate these utilities into the daemon's Cursor data pipeline (where they could replace file-based parsing for SQLite-stored composer data), or remove the module entirely.

---

## High

### H1. `schema_extractor.py` is entirely dead code (466 lines)

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/utils/schema_extractor.py` (466 lines)

Re-exported in `__init__.py:3-7` but never imported by any consumer in the project. The only references are:
- Its own docstring (line 13-15) showing example usage
- `__init__.py` re-exports
- Its own `if __name__ == "__main__"` block

No test file exists for this module. It can be run as `python -m qc_trace.utils.schema_extractor` but this is a developer exploration tool, not part of the product pipeline.

**Recommendation:** If kept as a dev tool, move to a `scripts/` or `tools/` directory and remove from `__init__.py` exports. Otherwise delete.

### H2. Duplicate slug decoding: `cursor_parser._decode_workspace_slug` vs `repo_resolver.decode_cursor_slug`

**Files:**
- `/Users/sagar/work/all-things-quickcall/trace/qc_trace/utils/cursor_parser.py:406-428` (`_decode_workspace_slug`)
- `/Users/sagar/work/all-things-quickcall/trace/qc_trace/utils/repo_resolver.py:75-120` (`decode_cursor_slug`)

Both functions solve the same problem: decode a Cursor project slug (e.g., `Users-sagar-work-myproject`) back to a filesystem path. However, they use completely different algorithms:

| Aspect | `_decode_workspace_slug` (cursor_parser) | `decode_cursor_slug` (repo_resolver) |
|--------|------------------------------------------|--------------------------------------|
| Algorithm | Naive: replace all dashes with `/` | Backtracking search: tries all dash-to-slash splits, validates with `os.path.isdir()` |
| Correctness | Broken for paths containing dashes (e.g., `all-things-quickcall` becomes `all/things/quickcall`) | Correct: validates against filesystem |
| Used by | `discover_cursor_projects()` (which is itself only test-used) | `collector.py` (production code) |

The cursor_parser version is provably wrong for any path containing literal dashes, which is extremely common.

**Recommendation:** Delete `_decode_workspace_slug` and either import `decode_cursor_slug` from `repo_resolver` or accept that `discover_cursor_projects` has a fundamentally flawed approach to workspace path recovery.

### H3. File descriptor leak in `cmd_start()`

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/cli/traced.py:136-142`

```python
log_fp = open(LOG_FILE, "a")  # noqa: SIM115
proc = subprocess.Popen(
    [sys.executable, "-m", "qc_trace.daemon"],
    stdout=log_fp,
    stderr=log_fp,
    start_new_session=True,
)
```

`log_fp` is never closed. The `# noqa: SIM115` suppresses the "use context manager" warning. After `Popen` returns, the parent process holds an open file descriptor to the log file indefinitely (until the CLI process exits). While the CLI is short-lived, this is still a resource leak and bad practice.

**Recommendation:** Close `log_fp` after `Popen` returns, since the child process inherits the fd:
```python
log_fp = open(LOG_FILE, "a")
proc = subprocess.Popen(..., stdout=log_fp, stderr=log_fp, ...)
log_fp.close()
```

### H4. Three functions in `cursor_parser.py` are only used by tests

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/utils/cursor_parser.py`

| Function | Lines | Used outside tests? |
|----------|-------|---------------------|
| `parse_mcp_tool_definition` | 292-316 | No |
| `parse_mcp_server_metadata` | 319-341 | No |
| `discover_cursor_projects` | 344-403 | No |

These three functions (112 lines, ~26% of the file) are only exercised by `tests/test_cursor_parser.py`. No daemon code, CLI code, or transform code imports them. They appear to be forward-looking infrastructure that was never integrated.

**Recommendation:** Mark with a comment indicating they are unused pending integration, or move to a separate discovery module.

---

## Medium

### M1. `setup_logging()` in `traced.py` is defined but never called

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/cli/traced.py:35-59`

The `setup_logging()` function creates a logger with file and stderr handlers, but the `main()` function never calls it. The CLI commands use `print()` for output instead of logging.

**Recommendation:** Either call `setup_logging()` in `main()` or remove the dead function.

### M2. macOS-only hardcoded paths in `sqlite.py`

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/utils/sqlite.py`

Multiple functions default to macOS paths:

- Line 252: `Path.home() / "Library" / "Application Support" / "Cursor"`
- Line 313: Same path
- Line 355: Same path

On Linux, Cursor stores data in `~/.config/Cursor/`. On Windows, `%APPDATA%/Cursor/`. These functions will silently return `None` / empty lists on non-macOS platforms with no warning or error.

**Recommendation:** Add platform detection or at minimum document the macOS-only assumption. Consider a `get_cursor_support_path()` utility that handles all platforms.

### M3. macOS-only `tail -f` in `cmd_logs()`

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/cli/traced.py:228-229`

```python
proc = subprocess.run(
    ["tail", "-n", str(num_lines), "-f", str(LOG_FILE)],
)
```

`tail` is not available on Windows. The non-follow path (line 236-238) uses pure Python and would work cross-platform.

**Recommendation:** Implement follow mode in pure Python (polling with `time.sleep`) or document the Unix requirement.

### M4. `cursor_parser.py` macOS-only default path

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/utils/cursor_parser.py:26`

```python
DEFAULT_CURSOR_HOME = os.path.expanduser("~/.cursor")
```

This is actually cross-platform correct -- `~/.cursor` is the standard location on all platforms for Cursor's project-local data. No issue here, but noting for completeness.

### M5. `repo_resolver.decode_cursor_slug` has exponential worst-case complexity

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/utils/repo_resolver.py:98-119`

The `_search` function is a backtracking recursive search that tries two options at each dash position. For a slug with N dashes, this is O(2^N) in the worst case. While filesystem validation (`os.path.isdir` and `_has_prefix`) prunes most branches early, a slug like `a-b-c-d-e-f-g-h-i-j-k-l-m-n-o` with many non-existent prefix paths could be slow.

Typical Cursor slugs have ~4-6 dashes, so this is not an immediate problem, but there is no depth limit or memoization.

**Recommendation:** Add a maximum recursion depth or convert to dynamic programming.

### M6. `_http_post` not used in production path -- only `db init`

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/cli/traced.py:107-120`

`_http_post` is only called from `cmd_db_init` (line 244). The `db init` subcommand sends a POST to `http://localhost:7777/db/init` with no authentication. If the daemon is running, any local process can trigger database re-initialization.

**Recommendation:** Consider whether `db init` should be an HTTP endpoint at all, or whether it should be a direct database operation.

### M7. `utils/__init__.py` does not export `cursor_parser` or `repo_resolver`

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/utils/__init__.py`

The `__init__.py` re-exports from `schema_extractor` and `sqlite` but not from `cursor_parser` or `repo_resolver`. All consumers of those modules import directly (`from qc_trace.utils.cursor_parser import ...`). This is inconsistent -- either all utils should be re-exported via `__init__.py` or none should.

Since the `schema_extractor` and `sqlite` re-exports are themselves dead code, the inconsistency is moot in practice, but it suggests the `__init__.py` was written early and never updated.

---

## Low

### L1. `_remove_tool_sections` regex may over-match

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/utils/cursor_parser.py:224-230`

```python
content = re.sub(
    r"\[Tool result\]\s+\w+\s*\n?.*?(?=\[Tool (?:call|result)\]|assistant:|user:|$)",
    "",
    content,
    flags=re.DOTALL,
)
```

The `.*?` with `re.DOTALL` will match newlines. The lookahead `(?=...|$)` means it matches to end-of-string if no boundary is found. In practice this works because `_parse_transcript_content` already splits content by message boundary, but the regex is fragile if the content format changes.

### L2. `extract_schema` reads file twice for format detection

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/utils/schema_extractor.py:283-294`

```python
with open(file_path) as f:
    first_char = f.read(1).strip()
```

Then the subsequent `extract_json_schema` or `extract_jsonl_schema` re-opens the file. For large files this is negligible, but it is a minor inefficiency.

### L3. `parse_agent_transcript` returns dict but type hint says `CursorAgentTranscript`

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/utils/cursor_parser.py:29`

The function signature says it returns `CursorAgentTranscript`, but it actually returns a plain dict. This works because `CursorAgentTranscript` is likely a `TypedDict`, but it means no runtime type checking occurs. The same pattern is used throughout all parse functions in this file.

### L4. `INGEST_URL` is hardcoded to `localhost:7777`

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/cli/traced.py:28`

```python
INGEST_URL = "http://localhost:7777"
```

No environment variable override or configuration option exists for changing the port.

**Recommendation:** Support `QC_TRACE_PORT` environment variable or a `--port` CLI flag.

### L5. `cmd_stop` sends SIGKILL without logging

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/cli/traced.py:174-177`

After the 5-second graceful shutdown window, `SIGKILL` is sent silently. The user is not informed that a forceful kill was necessary. This could mask daemon shutdown bugs.

### L6. No `--version` flag in CLI

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/cli/traced.py:257-289`

The parser does not support `--version` despite `qc_trace/__init__.py` defining `__version__ = "0.1.0"`.

### L7. `db` subcommand help is broken

**File:** `/Users/sagar/work/all-things-quickcall/trace/qc_trace/cli/traced.py:312`

```python
parser.parse_args(["db", "--help"])
```

When `qc-traced db` is run without a subcommand, this calls `parse_args` which prints help and then calls `sys.exit(0)` -- the `return 1` on line 313 is unreachable. The function's return code contract is violated.

---

## Dead Code Summary

| Module | Lines | Status | Consumers |
|--------|-------|--------|-----------|
| `sqlite.py` | 390 | **Entirely dead** | Only `utils/__init__.py` re-export, no actual consumer |
| `schema_extractor.py` | 466 | **Entirely dead** | Only `utils/__init__.py` re-export, no actual consumer |
| `cursor_parser.parse_mcp_tool_definition` | 25 | **Dead** | Tests only |
| `cursor_parser.parse_mcp_server_metadata` | 23 | **Dead** | Tests only |
| `cursor_parser.discover_cursor_projects` | 60 | **Dead** | Tests only |
| `cursor_parser._decode_workspace_slug` | 23 | **Dead** (and broken) | Only `discover_cursor_projects` (also dead) |
| `traced.setup_logging` | 25 | **Dead** | Never called |
| **Total dead lines** | **~1012** | | Out of ~1809 total lines reviewed (~56%) |

---

## Redundancy Summary

| Item | Location A | Location B | Verdict |
|------|-----------|-----------|---------|
| Slug decoding | `cursor_parser._decode_workspace_slug` (naive, broken) | `repo_resolver.decode_cursor_slug` (backtracking, correct) | Delete Location A |
| Cursor home path | `cursor_parser.DEFAULT_CURSOR_HOME` | `sqlite.py` hardcoded defaults | Should be unified constant |
| File reading | `collector._read_file` | Various `Path.read_text()` calls in cursor_parser | Minor duplication, acceptable |

---

## CLI Completeness Check

| Docstring Command | Implemented? | Notes |
|-------------------|-------------|-------|
| `start` | Yes | Has fd leak (H3) |
| `stop` | Yes | SIGKILL not communicated (L5) |
| `status` | Yes | Works correctly |
| `logs` | Yes | Platform-specific (M3) |
| `db init` | Yes | HTTP-only, requires running daemon (M6) |

The CLI is functionally complete relative to its docstring. The `pyproject.toml` correctly registers `qc-traced` as the entry point pointing to `qc_trace.cli.traced:main`.
