|
8 | 8 | |----------|-------|-------|-----------| |
9 | 9 | | Critical | 11 | 11 | 0 | |
10 | 10 | | High | 89 | 89 | 0 | |
11 | | -| Medium | 209 | 82 | 127 | |
| 11 | +| Medium | 209 | 83 | 126 | |
12 | 12 | | Low | 80 | 0 | 80 | |
13 | | -| **Total**| **389** | **182** | **207** | |
| 13 | +| **Total**| **389** | **183** | **206** | |
14 | 14 |
|
15 | 15 | --- |
16 | 16 |
|
|
121 | 121 | | H88 | `timps-vscode/src/memory.ts:2` — Architecture: the README's 'one shared memory engine' does not exist — VS Code has its own `TIMPsMemory` class (JSONL episodes, separate type, storage in `context.globalStorageUri/timps-memory/`) completely disconnected from the shared `MemoryEngine` in `@timps/memory-core` (JSON array episodes, sha256 dir at `~/.timps/memory/<hash>/`); memories created in VS Code never appear in CLI/MCP/desktop; at least 7 parallel memory implementations exist across the repo | Rewrote `TIMPsMemory` as a thin adapter: computes `projectHash` the same way as `MemoryEngine` (`sha256(path).slice(0,12)`), stores in `~/.timps/memory/<hash>/semantic.json` (JSON array) and `episodes.json` (JSON array) using the same schema; uses `crypto.randomBytes` for IDs instead of `Math.random()`; existing callers unchanged. VS Code memories now visible to CLI/MCP/desktop. Verified: `tsc --noEmit` clean. | |
122 | 122 | | H89 | `timps-vscode/src/memoryView.ts:127` — Dead code: the Memory Layers TreeView reads `TIMPsMemory` but the only writer (`chatPanel.ts:123,162`) is never imported by `extension.ts`; the active sidebar chat (`TIMPSChatViewProvider`) saves to `globalState` + HTTP, never to `TIMPsMemory`; the file watcher also watches stale paths (`episodes.jsonl`, `timps-memory/` subdir) | Wired `TIMPSChatViewProvider` to accept and write to a shared `TIMPsMemory` instance: after each message exchange, stores user message, runs reflection, records episode, tracks active file; created `memoryInstance` in `activate()` using workspace root for correct project hash; fixed file watcher in `memoryView.ts` to watch `episodes.json` (not `.jsonl`) and use `memory.getStorageDir()` for the correct shared directory; added `getStorageDir()` getter to `TIMPsMemory`. Verified: `tsc --noEmit` clean. | |
123 | 123 |
|
124 | | -## ✅ Fixed — 82 Medium |
| 124 | +## ✅ Fixed — 83 Medium |
125 | 125 |
|
126 | 126 | | ID | Issue | Fix | |
127 | 127 | |---|---|---| |
|
205 | 205 | | M85 | `packages/timps-desktop/src-tauri/src/commands.rs:683` — Bug: Timestamp units are inconsistent across writers: store_episode writes milliseconds (line 680-683) while store_memory (line 292-295), passive_store (line 640-643) and save_lens_link (line 1128-1131) write seconds; scoring and display assume seconds. score_entry (line 344-346) computes days_old = (now_secs - entry_ts)/86400, so ms-stamped episodes always clamp to 'now' and get maximum recency in chat context packing; the frontend formatDate/formatRelativeTime (src/utils/index.ts:12,36) multiply by 1000, rendering ms timestamps as dates in year ~57000. Episodes injected into the chat memory context are always ranked as maximally recent regardless of age, and any UI using the shared date formatters on episode timestamps shows absurd dates | Made timestamps seconds everywhere. `store_episode` now writes its `timestamp` in seconds (was `as_millis()`), matching store_memory/passive_store/save_lens_link. Added a `normalize_ts()` helper (divides by 1000 when ≥1e11, i.e. clearly ms) applied in `score_entry` — so legacy ms-stamped episodes no longer clamp to `days_old=0`/max recency in chat context packing — and on read in `load_episodes`. `load_episodes` now reads the live `episodes.jsonl` (one JSON object per line, what store_episode actually writes) with fallback to the legacy `episodes.json` array, so the EpisodicView UI shows real episodes. Frontend `formatDate`/`formatRelativeTime` accept both units via a `toMs()` guard (values ≥1e11 pass through as ms, smaller are scaled) so the shared formatters can never render a ~year-57000 date. Added `src/utils/index.test.ts` (4 tests: formatDate/formatRelativeTime give identical output for the same instant in seconds vs ms). Verification: `cargo test commands::tests` 18/18 (4 new: normalize_ts ms→sec, seconds passthrough, store_episode writes a seconds timestamp via temp HOME, load_episodes normalizes legacy ms data; also serialized the three HOME-mutating tests behind a new `HOME_LOCK` mutex because the new tests exposed a process-global `set_var("HOME")` race under parallel cargo test threads), full `cargo test` 19 passed / 1 pre-existing environmental nexus_bridge failure, desktop `tsc --noEmit` 0 errors, vitest 104 passed + 5 pre-existing TrayChanges ThemeProvider failures unchanged. No changeset (timps-desktop not versioned) | |
206 | 206 | | M86 | `packages/timps-desktop/src-tauri/src/commands.rs:741` — Bug: the clipboard watcher's stop/start lifecycle races. start_clipboard_watcher and stop_clipboard_watcher share ONE global AtomicBool and the watcher thread polls every ~500ms, so a stop→start sequence issued within that window flips the flag back to true before the old thread ever observes false — the old thread never exits, a second thread is spawned, and the stale thread keeps the project_path it captured at launch. Result: every subsequent clip is double-processed, and passive memory from the new project is misfiled into the OLD project's semantic store (passive_store writes to {memory_dir}/semantic.json keyed by the stale path) | Reworked the watcher lifecycle to a mutex-guarded `WatcherState { running, thread }` (OnceLock<Mutex>): the loop body is extracted into a testable `spawn_clipboard_watcher(project_path, poll_interval, read_clipboard, emit)` with injectable clipboard reader/emitter. `stop_clipboard_watcher` now sets the flag AND `join()`s the old thread while holding the lock, so a subsequent `start` is guaranteed to run exactly one thread and the old project_path can never outlive the stop — no stale watcher survives a project switch, no double-processing. Drive-by fix inside the refactored loop: the old `passive_ticks % 6 == 0` throttle only stored a clip if it first appeared exactly on a tick divisible by 6 (with 500ms polls, ~1/6 of new clips were silently dropped); replaced with an explicit ~1-per-3s rate limit whose first new clip is stored immediately. Added 2 unit tests using temp HOME + fake clipboard queue: `clipboard_watcher_restart_uses_new_project_only` (phase-1 clip lands in project A; stop; new clip; start project B; new clip lands in B, never in A, and A holds exactly one copy of the phase-1 clip) and `clipboard_watcher_second_start_is_noop` (second spawn while running returns early — no duplicate watcher writes into the second project). Both serialize behind the existing `HOME_LOCK`. Verification: `cargo test commands::tests` 20/20, full `cargo test` 21 passed / 1 pre-existing environmental nexus_bridge failure, desktop `tsc --noEmit` 0 errors, vitest 104 passed + 5 pre-existing TrayChanges ThemeProvider failures unchanged. No changeset (timps-desktop not versioned) | |
207 | 207 | | M87 | `packages/timps-desktop/src-tauri/src/commands.rs:782` — Bug: the clipboard watcher 'throttle' (passive_ticks % 6 == 0) permanently drops clips instead of delaying them: a new clip arriving on a non-multiple-of-6 tick is recorded into last_clip and the dedup check then skips it forever. The comment claims it 'throttles passive-store to every ~3 seconds', but the implementation makes storage a ~1-in-6 lottery per clip — roughly 83% of copied text is silently never captured. User enables clipboard capture, copies six different snippets over a minute: on average only one is stored; the rest are permanently lost with no indication | Confirmed this was already removed as a drive-by in M86 (the `passive_ticks % 6 == 0` gate is gone from `spawn_clipboard_watcher`), and completed the fix + added a dedicated regression test proving the audit's exact scenario. The throttle is now an explicit time-window rate limit: `spawn_clipboard_watcher` takes a `passive_interval` param (production passes 3s), the first NEW distinct clip is stored immediately, and subsequent stores are gated by `last_store.elapsed() >= passive_interval` — a clip is only ever DELAYED by the throttle, never dropped. The old behavior set `last_clip` on every new clip and only stored at ticks divisible by 6, so ~5/6 of clips were silently lost. Added `clipboard_watcher_stores_all_distinct_clips`: six distinct snippets are pushed one-by-one (each after the throttle window elapses) into a fake clipboard queue under a temp HOME, and the test asserts ALL SIX land in semantic.json exactly once (the old lottery would keep ~5 of 6). Design note: the first version used a rotating reader (every poll advances to the next clip) and flaked under full parallel `cargo test` load (waited 3s for ~600ms of watcher progress); rewrote to drive each clip sequentially with a throttle-gap sleep so only ~10ms of watcher progress is needed per clip — stable across 6 consecutive full-suite runs. Verification: `cargo test commands::tests` 21/21, full `cargo test` 22 passed / 1 pre-existing environmental nexus_bridge failure (stable ×6 runs), desktop `tsc --noEmit` 0 errors, vitest 104 passed + 5 pre-existing TrayChanges ThemeProvider failures unchanged. No changeset (timps-desktop not versioned) | |
| 208 | +| M88 | `packages/timps-desktop/src-tauri/src/commands.rs:783` — Security: the clipboard watcher persists any copied text ≥20 chars verbatim into plaintext semantic.json (via passive_store) with no secret detection or redaction; the same store is later injected into LLM prompts by the chat command. Users routinely copy passwords, API keys, tokens and personal data; these end up permanently in ~/.timps/memory/<hash>/semantic.json and can be replayed into model context on unrelated queries. User copies an AWS secret key from their password manager while capture is enabled: the key is written to disk unencrypted, survives for up to 2000 entries, and may be pasted into an Ollama/TIMPS-server prompt by the memory-context packer | Added credential detection + skip to the clipboard capture path. New pure `looks_like_secret(content)` checks ~20 compiled regex patterns (regex crate added to the Tauri Cargo.toml): AWS access-key IDs, standalone 40-char base64 secrets, PEM private-key blocks, JWTs, provider API keys (OpenAI sk-, Stripe sk_/pk_, GitHub ghp_/github_pat_, Slack xoxb-, Google AIza, HuggingFace hf_, GitLab glpat-, xAI xai-), 64-char hex keys, key=value / key: value assignments (api_key/secret/password/token/access_key/private_key/auth_token/client_secret), Authorization / x-api-key headers, Bearer tokens, and DB connection strings / http(s) URLs with embedded user:pass@ credentials. `passive_store` refuses to persist flagged content, returning `skip:secret` before any disk write (defense in depth — it is also a registered tauri command). The watcher loop checks secrets BEFORE the URL fast-path (so credential-bearing URLs can't reach save_lens_link either) and emits a new `timps:secret-detected` event. Verbatim plaintext of a copied secret is never written to semantic.json and therefore can never be replayed into an LLM prompt by the memory-context packer. Added 3 unit tests: `secret_detection_flags_known_secret_formats` (20 samples incl. AWS key, PEM block, JWT, Stripe, GitHub, Slack, Google, HF, GitLab, AWS_SECRET_ACCESS_KEY=, password:, Bearer header, x-api-key, postgres://user:pass@, https://user:pass@, 64-hex), `secret_detection_ignores_normal_content` (11 benign samples incl. prose, plain GitHub URLs, localhost ports, mongodb:// without creds, emails, code), and `clipboard_watcher_and_passive_store_never_persist_secrets` (direct passive_store returns skip:secret and writes nothing; watcher fires timps:secret-detected and creates no semantic.json for a secret clip; normal text through the same watcher still stores). Verification: `cargo test commands::tests` 24/24, full `cargo test` 25 passed / 1 pre-existing environmental nexus_bridge failure (stable ×3 runs), desktop `tsc --noEmit` 0 errors, vitest 104 passed + 5 pre-existing TrayChanges ThemeProvider failures unchanged. No changeset (timps-desktop not versioned) | |
208 | 209 | | M82 | `packages/timps-desktop/src-tauri/src/commands.rs:247` — Bug: search_memory returns zero results for queries composed only of words ≤2 characters: the word filter w.len() > 2 (line 247) empties the query list, every entry then scores 0.0 and is filtered out (lines 259-263), instead of falling back to substring match or the take-N path. Common developer queries like 'go', 'ai', 'db', 'ui' silently return an empty result set even when matching memories exist; the empty-query fast path (lines 239-242) only triggers on whitespace-only input | Fixed short-word queries in the desktop Tauri search. `search_memory` now delegates to a new pure `rank_semantic(entries, query, limit)` helper. The word filter is conditional: short words (≤2 chars) are only dropped when the query ALSO contains longer words — a query composed entirely of short tokens keeps them, so 'go'/'ai'/'db'/'ui' now match their entries instead of producing an empty word list that scores everything 0.0. Mixed queries ('db postgres') keep the prior noise-filtering behavior, empty/whitespace queries keep the take-N path, and ranking/limit logic is unchanged. Added 7 unit tests in `commands.rs`: short-word query matches, all-short-words query matches, short word does NOT match unrelated entries (no over-matching), mixed query still filters short noise, empty query take-N, no-match empty, limit respected. `cargo test commands::tests` 7/7 pass (the pre-existing `nexus_bridge::tests::test_load_unified_graph` failure is environmental — it loads live `~/.timps/memory` graph files and is unrelated to this change; no changeset needed as timps-desktop is not a versioned package) | |
209 | 210 |
|
210 | | -## 📋 Remaining — 207 Issues |
| 211 | +## 📋 Remaining — 206 Issues |
211 | 212 |
|
212 | 213 | ### High (0) |
213 | 214 | ✅ All High issues fixed. |
214 | 215 |
|
215 | | -### Medium (134) |
| 216 | +### Medium (133) |
216 | 217 | ⏸️ Paused — awaiting user instruction to proceed |
217 | 218 |
|
218 | 219 | ### Low (80) |
|
0 commit comments