codevira 1.7.0__tar.gz → 1.8.0__tar.gz

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.
Files changed (122) hide show
  1. {codevira-1.7.0 → codevira-1.8.0}/CHANGELOG.md +252 -0
  2. {codevira-1.7.0/codevira.egg-info → codevira-1.8.0}/PKG-INFO +22 -2
  3. {codevira-1.7.0 → codevira-1.8.0}/README.md +21 -1
  4. {codevira-1.7.0 → codevira-1.8.0/codevira.egg-info}/PKG-INFO +22 -2
  5. {codevira-1.7.0 → codevira-1.8.0}/codevira.egg-info/SOURCES.txt +3 -0
  6. codevira-1.8.0/docs/local-pypi-https.md +184 -0
  7. {codevira-1.7.0 → codevira-1.8.0}/indexer/global_db.py +43 -2
  8. {codevira-1.7.0 → codevira-1.8.0}/indexer/index_codebase.py +84 -1
  9. {codevira-1.7.0 → codevira-1.8.0}/indexer/sqlite_graph.py +85 -9
  10. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/__init__.py +1 -1
  11. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/auto_init.py +9 -1
  12. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/cli.py +57 -1
  13. codevira-1.8.0/mcp_server/cli_configure.py +805 -0
  14. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/http_server.py +7 -0
  15. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/migrate.py +7 -2
  16. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/server.py +16 -1
  17. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/tools/learning.py +92 -12
  18. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/tools/search.py +79 -0
  19. {codevira-1.7.0 → codevira-1.8.0}/pyproject.toml +1 -1
  20. {codevira-1.7.0 → codevira-1.8.0}/tests/test_auto_init.py +2 -1
  21. {codevira-1.7.0 → codevira-1.8.0}/tests/test_cli.py +20 -0
  22. codevira-1.8.0/tests/test_cli_configure.py +1016 -0
  23. {codevira-1.7.0 → codevira-1.8.0}/tests/test_index_codebase.py +184 -0
  24. {codevira-1.7.0 → codevira-1.8.0}/tests/test_migrate.py +2 -1
  25. {codevira-1.7.0 → codevira-1.8.0}/tests/test_sqlite_graph.py +272 -0
  26. {codevira-1.7.0 → codevira-1.8.0}/tests/test_tools_learning.py +194 -3
  27. {codevira-1.7.0 → codevira-1.8.0}/LICENSE +0 -0
  28. {codevira-1.7.0 → codevira-1.8.0}/MANIFEST.in +0 -0
  29. {codevira-1.7.0 → codevira-1.8.0}/agents/builder.md +0 -0
  30. {codevira-1.7.0 → codevira-1.8.0}/agents/developer.md +0 -0
  31. {codevira-1.7.0 → codevira-1.8.0}/agents/documenter.md +0 -0
  32. {codevira-1.7.0 → codevira-1.8.0}/agents/orchestrator.md +0 -0
  33. {codevira-1.7.0 → codevira-1.8.0}/agents/planner.md +0 -0
  34. {codevira-1.7.0 → codevira-1.8.0}/agents/reviewer.md +0 -0
  35. {codevira-1.7.0 → codevira-1.8.0}/agents/tester.md +0 -0
  36. {codevira-1.7.0 → codevira-1.8.0}/codevira.egg-info/dependency_links.txt +0 -0
  37. {codevira-1.7.0 → codevira-1.8.0}/codevira.egg-info/entry_points.txt +0 -0
  38. {codevira-1.7.0 → codevira-1.8.0}/codevira.egg-info/requires.txt +0 -0
  39. {codevira-1.7.0 → codevira-1.8.0}/codevira.egg-info/top_level.txt +0 -0
  40. {codevira-1.7.0 → codevira-1.8.0}/config.example.yaml +0 -0
  41. {codevira-1.7.0 → codevira-1.8.0}/docs/how-i-built-persistent-memory-for-ai-agents.md +0 -0
  42. {codevira-1.7.0 → codevira-1.8.0}/docs/linkedin-article-ai-agent-memory.md +0 -0
  43. {codevira-1.7.0 → codevira-1.8.0}/docs/linkedin-post-ai-agent-memory.md +0 -0
  44. {codevira-1.7.0 → codevira-1.8.0}/docs/medium-your-ai-coding-agent-has-amnesia.md +0 -0
  45. {codevira-1.7.0 → codevira-1.8.0}/docs/roadmap.md +0 -0
  46. {codevira-1.7.0 → codevira-1.8.0}/graph/_schema.yaml +0 -0
  47. {codevira-1.7.0 → codevira-1.8.0}/indexer/__init__.py +0 -0
  48. {codevira-1.7.0 → codevira-1.8.0}/indexer/chunker.py +0 -0
  49. {codevira-1.7.0 → codevira-1.8.0}/indexer/graph_generator.py +0 -0
  50. {codevira-1.7.0 → codevira-1.8.0}/indexer/outcome_tracker.py +0 -0
  51. {codevira-1.7.0 → codevira-1.8.0}/indexer/rule_learner.py +0 -0
  52. {codevira-1.7.0 → codevira-1.8.0}/indexer/treesitter_parser.py +0 -0
  53. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/__main__.py +0 -0
  54. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/crash_logger.py +0 -0
  55. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/__init__.py +0 -0
  56. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/agents/builder.md +0 -0
  57. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/agents/developer.md +0 -0
  58. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/agents/documenter.md +0 -0
  59. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/agents/orchestrator.md +0 -0
  60. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/agents/planner.md +0 -0
  61. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/agents/reviewer.md +0 -0
  62. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/agents/tester.md +0 -0
  63. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/config.example.yaml +0 -0
  64. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/rules/coding-standards.md +0 -0
  65. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/rules/engineering-excellence.md +0 -0
  66. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/rules/git-cicd-governance.md +0 -0
  67. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/rules/git_commits.md +0 -0
  68. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/rules/incremental-updates.md +0 -0
  69. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/rules/master_rule.md +0 -0
  70. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/rules/multi-language.md +0 -0
  71. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/rules/persistence.md +0 -0
  72. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/rules/resilience-observability.md +0 -0
  73. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/rules/smoke-testing.md +0 -0
  74. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/data/rules/testing-standards.md +0 -0
  75. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/detect.py +0 -0
  76. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/gitignore.py +0 -0
  77. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/global_sync.py +0 -0
  78. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/ide_inject.py +0 -0
  79. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/launchd.py +0 -0
  80. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/log_retention.py +0 -0
  81. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/paths.py +0 -0
  82. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/prompts.py +0 -0
  83. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/tools/__init__.py +0 -0
  84. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/tools/changesets.py +0 -0
  85. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/tools/code_reader.py +0 -0
  86. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/tools/graph.py +0 -0
  87. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/tools/playbook.py +0 -0
  88. {codevira-1.7.0 → codevira-1.8.0}/mcp_server/tools/roadmap.py +0 -0
  89. {codevira-1.7.0 → codevira-1.8.0}/rules/coding-standards.md +0 -0
  90. {codevira-1.7.0 → codevira-1.8.0}/rules/engineering-excellence.md +0 -0
  91. {codevira-1.7.0 → codevira-1.8.0}/rules/git-cicd-governance.md +0 -0
  92. {codevira-1.7.0 → codevira-1.8.0}/rules/git_commits.md +0 -0
  93. {codevira-1.7.0 → codevira-1.8.0}/rules/incremental-updates.md +0 -0
  94. {codevira-1.7.0 → codevira-1.8.0}/rules/master_rule.md +0 -0
  95. {codevira-1.7.0 → codevira-1.8.0}/rules/persistence.md +0 -0
  96. {codevira-1.7.0 → codevira-1.8.0}/rules/resilience-observability.md +0 -0
  97. {codevira-1.7.0 → codevira-1.8.0}/rules/smoke-testing.md +0 -0
  98. {codevira-1.7.0 → codevira-1.8.0}/rules/testing-standards.md +0 -0
  99. {codevira-1.7.0 → codevira-1.8.0}/setup.cfg +0 -0
  100. {codevira-1.7.0 → codevira-1.8.0}/tests/test_chunker.py +0 -0
  101. {codevira-1.7.0 → codevira-1.8.0}/tests/test_crash_logger.py +0 -0
  102. {codevira-1.7.0 → codevira-1.8.0}/tests/test_detect.py +0 -0
  103. {codevira-1.7.0 → codevira-1.8.0}/tests/test_gitignore.py +0 -0
  104. {codevira-1.7.0 → codevira-1.8.0}/tests/test_global_db.py +0 -0
  105. {codevira-1.7.0 → codevira-1.8.0}/tests/test_global_sync.py +0 -0
  106. {codevira-1.7.0 → codevira-1.8.0}/tests/test_graph_generator.py +0 -0
  107. {codevira-1.7.0 → codevira-1.8.0}/tests/test_http_server.py +0 -0
  108. {codevira-1.7.0 → codevira-1.8.0}/tests/test_ide_inject.py +0 -0
  109. {codevira-1.7.0 → codevira-1.8.0}/tests/test_launchd.py +0 -0
  110. {codevira-1.7.0 → codevira-1.8.0}/tests/test_log_retention.py +0 -0
  111. {codevira-1.7.0 → codevira-1.8.0}/tests/test_outcome_tracker.py +0 -0
  112. {codevira-1.7.0 → codevira-1.8.0}/tests/test_paths.py +0 -0
  113. {codevira-1.7.0 → codevira-1.8.0}/tests/test_prompts.py +0 -0
  114. {codevira-1.7.0 → codevira-1.8.0}/tests/test_rule_learner.py +0 -0
  115. {codevira-1.7.0 → codevira-1.8.0}/tests/test_server.py +0 -0
  116. {codevira-1.7.0 → codevira-1.8.0}/tests/test_tools_changesets.py +0 -0
  117. {codevira-1.7.0 → codevira-1.8.0}/tests/test_tools_code_reader.py +0 -0
  118. {codevira-1.7.0 → codevira-1.8.0}/tests/test_tools_graph.py +0 -0
  119. {codevira-1.7.0 → codevira-1.8.0}/tests/test_tools_playbook.py +0 -0
  120. {codevira-1.7.0 → codevira-1.8.0}/tests/test_tools_roadmap.py +0 -0
  121. {codevira-1.7.0 → codevira-1.8.0}/tests/test_tools_search.py +0 -0
  122. {codevira-1.7.0 → codevira-1.8.0}/tests/test_treesitter_parser.py +0 -0
@@ -13,6 +13,258 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
13
13
 
14
14
  ---
15
15
 
16
+ ## [1.8.0] — 2026-04-23 — Memory Sharpening + Config UX
17
+
18
+ Three internal improvements that make the memory we already capture **sharper**,
19
+ without making it heavier. Zero new MCP tools. Zero new tables. The public API
20
+ shape changes only one thing: `get_session_context()` gains a `focus_source`
21
+ field (~10 tokens, additive, backwards-compatible).
22
+
23
+ The problem this release solves:
24
+ - `get_session_context()` returned the 3 newest decisions by timestamp —
25
+ regardless of whether they had anything to do with the current task.
26
+ - `search_decisions()` ordered purely by recency — a `file_path` match
27
+ was no better than a match buried in an unrelated session summary.
28
+ - `log_session()` inserted every decision unconditionally — a day of
29
+ iterative agent work logged the same intent 5+ times.
30
+
31
+ ### Fixed
32
+
33
+ - **MCP `serverInfo.version` reported the MCP library version, not codevira's**
34
+ (pre-existing bug, surfaced during v1.8 install verification on Python
35
+ 3.13). `Server("codevira")` was constructed without a `version=` argument,
36
+ so the framework defaulted to its own pip-package version (e.g. `1.27.0`)
37
+ in the JSON-RPC `initialize` handshake response. Clients use this field
38
+ for telemetry and version gating, so the wrong value misled them.
39
+ One-line fix: `Server("codevira", version=__version__)`.
40
+ - **`get_session_context()` read the wrong dict key** (pre-existing bug).
41
+ `list_open_changesets()` returns `{"open_changesets": [...], ...}`, but
42
+ `get_session_context` looked for `"changesets"`. The `open_changesets`
43
+ field in the session-context response was **always empty** in production.
44
+ Tests didn't catch it because mocks used the same wrong key.
45
+
46
+ - **`GlobalDB` concurrent-open race condition** (pre-existing bug — latent
47
+ since v1.6's centralized storage introduced shared `~/.codevira/global.db`).
48
+ `PRAGMA journal_mode=WAL` requires an exclusive lock and — unlike normal
49
+ SQL — does NOT honour `sqlite3`'s `busy_timeout`. When multiple processes
50
+ or threads opened the same fresh database concurrently (e.g. several
51
+ projects' first-ever `codevira register` running in parallel, or the
52
+ `global_sync` background export racing the MCP server thread), one or
53
+ more would raise `OperationalError('database is locked')` and silently
54
+ fail to register. The test `test_concurrent_access_from_threads` was
55
+ flaky at 60% failure rate, hinting at the real issue. Fixed with WAL-
56
+ enable retry loop + short-circuit when WAL is already active. Stability
57
+ verified at 20/20 passes across 20 test runs.
58
+
59
+ ### Changed
60
+
61
+ - **Focus-weighted `recent_decisions` in `get_session_context()`**. Instead
62
+ of chronological "newest 3", decisions are now ranked by what the agent
63
+ is currently focused on:
64
+ 1. Open changeset with `files_pending` → focus = first file path of the
65
+ most-recently-created changeset.
66
+ 2. Strong `current_phase.next_action` signal → focus = extracted keywords
67
+ (rejects short or stop-list-only actions like "continue work").
68
+ 3. Otherwise → chronological fallback (unchanged behaviour).
69
+ If focus returns fewer than 3, the list pads with `get_recent_decisions()`.
70
+ New response field `focus_source` (`"open_changeset:<id>"`, `"next_action"`,
71
+ or `null`) lets the agent see *why* it got these decisions.
72
+
73
+ - **Smarter `search_decisions()` ranking**. SQL now adds `file_path` to both
74
+ the WHERE clause and a CASE-based ORDER BY:
75
+ `file_path match (0) > decision text (1) > context (2) > summary-only (3)`,
76
+ then newest first within each tier. Searching for `"src/auth.py"` now
77
+ surfaces file-path matches even when the decision text doesn't mention
78
+ the path.
79
+
80
+ - **Decision dedup on write**. `log_session()` now skips a new decision
81
+ if it has a `file_path` and its token-set overlaps ≥ 80% with any of
82
+ the 5 most recent decisions for that same file. The session row is
83
+ always created; only redundant *decisions* are dropped. Short
84
+ decisions (< 3 tokens) and decisions without `file_path` are always
85
+ inserted.
86
+
87
+ ### Added
88
+
89
+ - `focus_source` field on `get_session_context()` response (≈10 tokens).
90
+ - `mcp_server.tools.learning._infer_focus()` — pure helper, module-private.
91
+ - `indexer.sqlite_graph._is_duplicate()` — pure token-overlap helper,
92
+ module-private, independently testable.
93
+ - **`codevira configure`** — new CLI subcommand. Scans your project
94
+ (gitignore-aware via existing `discover_source_files()`), shows discovered
95
+ directories and file extensions with counts, lets you pick via a numbered-
96
+ list prompt, writes the choices back to `.codevira/config.yaml`, and offers
97
+ to rebuild the index. Non-interactive:
98
+ `codevira configure --dirs src,lib --extensions .py,.ts --no-reindex`.
99
+ Solves the AgentStore-style "0 chunks indexed" case where
100
+ `auto_detect_project()` mis-guesses a monorepo layout.
101
+ When `config.yaml` is missing (normal state after `codevira register` but
102
+ before the first MCP tool call), `configure` auto-bootstraps it in full
103
+ parity with `auto_init`'s first-init path: writes `metadata.json` (rename-
104
+ resilient lookup via `git_remote`) and registers the project in
105
+ `~/.codevira/global.db` for cross-project intelligence. Missing these on
106
+ earlier drafts would have left the project invisible to rename-resilient
107
+ path lookup and absent from global memory until the first session log.
108
+ - **Zero-chunks safety hint at index time.** When `codevira index --full` or
109
+ `codevira index` (incremental, project-wide) matches no files against your
110
+ `watched_dirs` + `file_extensions`, the indexer now prints a one-line
111
+ remedy pointing at `codevira configure`. Output goes to **stderr** (not
112
+ stdout) so the hint never leaks into the MCP JSON-RPC wire when
113
+ `start_background_full_index` runs during auto_init inside the MCP server
114
+ process. Also logged at WARNING level so background invocations
115
+ (auto-init, launchd watcher) leave a trace regardless of terminal
116
+ capture. Does NOT fire for caller-scoped incremental runs (e.g. the
117
+ `refresh_index` MCP tool targeting a specific file) — zero matches there
118
+ is the caller's choice, not a misconfiguration.
119
+ - `codevira register` success banner now nudges toward `codevira configure`.
120
+
121
+ ### Internal
122
+
123
+ - 87 new tests:
124
+ - 34 for v1.8 memory sharpening (focus inference priority rules, ranking
125
+ tier ordering, dedup threshold behaviour, session-row-always-created
126
+ invariant, NULL file_path fallback, session_id filtering + new ranking SQL)
127
+ - 43 for `codevira configure` (scan_project with centralized-mode
128
+ decoupling + skip_dirs honoring, multi-select prompt incl. non-TTY
129
+ fallback + Ctrl+C clean-abort, config writer preserve/dedupe/idempotency,
130
+ orchestrator edge cases incl. bootstrap on missing config, dry-run disk
131
+ safety, corrupt-YAML handling, empty-extensions safety, PermissionError
132
+ friendly wrapper, `--dirs`/`--extensions` normalization)
133
+ - 10 for the zero-chunks hint (unit tests of the helper proving it writes
134
+ to stderr not stdout + integration tests proving it fires ONLY for full
135
+ or project-wide-incremental scans, not caller-scoped or normal "no files
136
+ changed")
137
+ - Full test suite: **1,398 passing, 0 deterministic failures** (up from
138
+ 1,306 at v1.7.1 → +92). The two "pre-existing watchdog failures" that
139
+ haunted earlier drafts of this CHANGELOG turned out to be an environment
140
+ issue in a single dev machine (system Python 3.9 without `watchdog`);
141
+ the pipx-installed v1.8.0 environment has all required deps. The one
142
+ pre-existing flaky test (`test_concurrent_access_from_threads`) is now
143
+ fixed by the `GlobalDB` WAL-enable retry loop described above.
144
+
145
+ ### Verified environments
146
+
147
+ - **macOS (APFS)** + Python 3.9 system + Python 3.11 pipx: full regression
148
+ passes; all interactive + non-interactive flows manually verified on three
149
+ real projects (AgentStore, UDAP, ToolsConnector).
150
+ - **Cross-process + thread concurrency**: stress-tested (12 threads × 20
151
+ writes, 8 subprocesses × 25 writes, 100 concurrent-read/write cycles) —
152
+ 0 errors, 0 data loss.
153
+
154
+ ### Unverified environments / known gaps (candidates for v1.8.1 or v1.9)
155
+
156
+ - **Windows**: `os.replace` atomicity weakens when the destination is open
157
+ by another process. If a Windows user has Claude Code reading
158
+ `config.yaml` at the moment `codevira configure` writes it, the write
159
+ may fail with `PermissionError`. Pre-existing risk; v1.8 does not fix
160
+ and does not regress. Windows smoke-testing is a v1.9 scope item.
161
+ - **Network filesystems (NFS, SMB)**: atomic-replace guarantees are weaker
162
+ on network FS. Unlikely in solo-dev environments (codevira's target);
163
+ possible in enterprise setups.
164
+ - **Python 3.10, 3.12**: The APIs `codevira configure` uses are stable
165
+ across 3.10+. Syntax-verified against 3.10+. **Python 3.13.7
166
+ empirically verified** during v1.8 install validation (full pipx
167
+ install + MCP handshake working). 3.10 and 3.12 are syntax-verified
168
+ only. CI on all Python versions is a v1.8.x task.
169
+ - **Case-insensitive filesystem slugs**: On macOS APFS (default), paths
170
+ differing only in case (`~/Documents` vs `~/documents`) produce
171
+ different slugs for the same physical directory, creating split state.
172
+ Pre-existing since v1.5 — fixing requires a migration step for existing
173
+ users and is scoped to v1.9.
174
+ - **Interactive TTY automated coverage**: The interactive prompt flow is
175
+ tested via mocked stdin + `sys.stdin.isatty`. A real terminal session
176
+ was manually verified during development; automated TTY testing (via
177
+ pexpect or similar) is a v1.8.x nice-to-have.
178
+ - **MCP client post-upgrade reload**: `codevira register` writes config;
179
+ each MCP client (Claude Code, Cursor, Windsurf, Antigravity, Claude
180
+ Desktop) needs to reload to see changes. Verified for Claude Code.
181
+ Other clients may have edge cases that surface post-release.
182
+
183
+ ### Known test flake (NOT v1.8; pre-existing)
184
+
185
+ - `test_chunk_error_continues_to_next_file` fails ~3/10 times in the full
186
+ suite on Python 3.9 (system) due to a chromadb+pydantic version
187
+ incompatibility raising `TypeError` during `import chromadb`, which
188
+ `_check_search_deps()` doesn't catch (it only catches `ImportError`).
189
+ **Not introduced by v1.8 and not touched by v1.8 code paths** — verified
190
+ by measuring the same 3/10 flake rate on clean v1.7.1. v1.8 deliberately
191
+ does not widen the exception catch because it would silently mask real
192
+ dep issues; a proper fix belongs in a targeted follow-up PR with its own
193
+ test coverage. Does not affect production users — the condition requires
194
+ a specific dev environment (Py 3.9 + mismatched chromadb/pydantic).
195
+ - Regression guards added by the binocular review pass:
196
+ - `test_centralized_mode_data_dir_and_project_root_decoupled` — catches
197
+ the production bug where `data_dir.parent` was used where
198
+ `get_project_root()` was required (centralized mode v1.6+).
199
+ - `test_bootstraps_config_when_missing` — catches the workflow where
200
+ `codevira register` was run but config.yaml hasn't been written yet
201
+ (auto_init hadn't fired because no MCP tool call had happened).
202
+ - `test_bootstrap_respects_dry_run` — catches bootstrap writing disk
203
+ during `--dry-run`.
204
+ - `test_fires_on_stderr_when_not_quiet` — catches zero-chunks hint
205
+ leaking to stdout, which would corrupt the MCP JSON-RPC wire in stdio
206
+ mode.
207
+ - `test_empty_extensions_non_interactive_errors_exit_2` — catches
208
+ `--extensions ""` being silently accepted, which would write an empty
209
+ `file_extensions: []` and re-create the zero-chunks bug.
210
+ - `test_ctrl_c_in_prompt_returns_exit_0` — catches KeyboardInterrupt
211
+ propagating a traceback to the user.
212
+ - `test_permission_error_on_write_exits_1` — catches PermissionError /
213
+ OSError propagating a traceback when config.yaml isn't writable.
214
+ - `test_honors_user_skip_dirs_from_config` — catches scan_project
215
+ ignoring the user's explicit skip_dirs in config.yaml.
216
+
217
+ ### Known limitations
218
+
219
+ - A running file-watcher or live MCP server session won't pick up config
220
+ changes until it restarts (the watcher snapshots `watched_dirs` at boot).
221
+ Restart your AI tool after `codevira configure` to apply changes.
222
+ - `yaml.safe_dump` doesn't preserve comments in `config.yaml`. First-time
223
+ configs are auto-generated and have no comments; users who hand-edited
224
+ may see formatting normalized after `codevira configure` rewrites the file.
225
+
226
+ ### Unchanged (intentionally)
227
+
228
+ - No new MCP tools. No new tables. No schema migration.
229
+ - `search_decisions()` method signature unchanged.
230
+ - `log_session()` method signature unchanged.
231
+ - `get_session_context()` keys are additive — no removals.
232
+ - `auto_init.py`, `detect.py`, `gitignore.py`, and `metadata.json` writer
233
+ untouched; `codevira configure` reuses all existing detection machinery.
234
+
235
+ ---
236
+
237
+ ## [1.7.1] — 2026-04-22 — Search Timeout Fix & Version Display
238
+
239
+ Two small but user-visible fixes on top of v1.7.0.
240
+
241
+ ### Fixed
242
+
243
+ - **`search_codebase` timeout on first call** (reported by a user testing
244
+ on Antigravity). The embedding model (`all-MiniLM-L6-v2`) was being
245
+ instantiated fresh on every MCP tool call, which triggered a ~90MB
246
+ download + PyTorch init on first ever use (30-60s on slow networks)
247
+ and 1-3s of re-init overhead on every subsequent call. Antigravity's
248
+ ~30s MCP tool timeout killed the query before the model finished loading.
249
+
250
+ Three-layer fix:
251
+ 1. Module-level cache for the chroma client + embedding function,
252
+ keyed by `db_dir`. Subsequent calls are now instant.
253
+ 2. Background `prewarm_embedding_model()` spawned at server startup
254
+ (both stdio and HTTP transports). Model loads in parallel with
255
+ the MCP handshake window.
256
+ 3. Cold-path timeout guard: if a query arrives while warmup is still
257
+ in progress, returns `{"status": "warming", ...}` within 10 seconds
258
+ instead of blocking until the MCP timeout fires. The agent gets a
259
+ clean retryable response.
260
+
261
+ - **`codevira register` banner showed hardcoded `v1.6`** after upgrading
262
+ to v1.7.0. Now reads `mcp_server.__version__` dynamically. Same fix
263
+ applied to `metadata.json` version field written during auto-init and
264
+ migration.
265
+
266
+ ---
267
+
16
268
  ## [1.7.0] — 2026-04-18 — Token Efficiency & AI-First Tool Design
17
269
 
18
270
  **The biggest release since v1.0.** We realized Codevira was dumping 15k-60k
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: codevira
3
- Version: 1.7.0
3
+ Version: 1.8.0
4
4
  Summary: Persistent adaptive memory for AI coding agents — MCP server with context graph, semantic search, adaptive learning, roadmap tracking, and cross-tool continuity.
5
5
  Author-email: Sachin Shelke <sachin.worldnet@gmail.com>
6
6
  License: MIT
@@ -44,7 +44,7 @@ Provides-Extra: all
44
44
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/)
45
45
  [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
46
46
  [![MCP](https://img.shields.io/badge/protocol-MCP-purple)](https://modelcontextprotocol.io)
47
- [![Version](https://img.shields.io/badge/version-1.7.0-orange)](CHANGELOG.md)
47
+ [![Version](https://img.shields.io/badge/version-1.8.0-orange)](CHANGELOG.md)
48
48
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](CONTRIBUTING.md)
49
49
 
50
50
  **Built for solo developers** working on local projects with AI agents. Codevira gives every AI tool you use access to the same persistent project memory — so you stop re-explaining your codebase every session, stop losing carefully-made decisions, and stop burning tokens on re-discovery.
@@ -142,6 +142,24 @@ Ask your AI agent to call `get_roadmap()` — it should return your current phas
142
142
 
143
143
  > **Note:** Restart your AI tool after running `codevira register` to pick up the new MCP config.
144
144
 
145
+ ### Customizing what's indexed
146
+
147
+ Codevira tries to auto-detect your project's source layout, but monorepo or non-standard layouts sometimes slip through — you'll notice when `codevira index --full` reports `0 chunks indexed` and prints a hint pointing you here.
148
+
149
+ ```bash
150
+ cd your-project
151
+ codevira configure
152
+ ```
153
+
154
+ Scans your project (gitignore-aware), shows discovered directories and extensions with file counts, and lets you pick which to watch via a numbered-list prompt. It writes your choices back to `.codevira/config.yaml` and offers to rebuild the index.
155
+
156
+ **Non-interactive** (useful in scripts or CI):
157
+ ```bash
158
+ codevira configure --dirs src,packages,apps --extensions .py,.ts,.tsx --no-reindex
159
+ ```
160
+
161
+ After changing watched directories, **restart your AI tool** — running watchers snapshot the dir set at boot.
162
+
145
163
  ### Manual config (only if auto-inject didn't detect your tool)
146
164
 
147
165
  Codevira supports two transports. Use the right one for your client:
@@ -570,6 +588,8 @@ Contributions are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md) for the full
570
588
  **Requesting a feature?** [Open a feature request](https://github.com/sachinshelke/codevira/issues/new?template=feature_request.md)
571
589
  **Found a security issue?** Read [SECURITY.md](SECURITY.md) — please don't use public issues for vulnerabilities.
572
590
 
591
+ **Testing a release candidate locally?** See [docs/local-pypi-https.md](docs/local-pypi-https.md) for setting up a Docker-based HTTPS PyPI registry that mirrors the real PyPI install flow without touching public PyPI.
592
+
573
593
  ---
574
594
 
575
595
  ## FAQ
@@ -5,7 +5,7 @@
5
5
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/)
6
6
  [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
7
7
  [![MCP](https://img.shields.io/badge/protocol-MCP-purple)](https://modelcontextprotocol.io)
8
- [![Version](https://img.shields.io/badge/version-1.7.0-orange)](CHANGELOG.md)
8
+ [![Version](https://img.shields.io/badge/version-1.8.0-orange)](CHANGELOG.md)
9
9
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](CONTRIBUTING.md)
10
10
 
11
11
  **Built for solo developers** working on local projects with AI agents. Codevira gives every AI tool you use access to the same persistent project memory — so you stop re-explaining your codebase every session, stop losing carefully-made decisions, and stop burning tokens on re-discovery.
@@ -103,6 +103,24 @@ Ask your AI agent to call `get_roadmap()` — it should return your current phas
103
103
 
104
104
  > **Note:** Restart your AI tool after running `codevira register` to pick up the new MCP config.
105
105
 
106
+ ### Customizing what's indexed
107
+
108
+ Codevira tries to auto-detect your project's source layout, but monorepo or non-standard layouts sometimes slip through — you'll notice when `codevira index --full` reports `0 chunks indexed` and prints a hint pointing you here.
109
+
110
+ ```bash
111
+ cd your-project
112
+ codevira configure
113
+ ```
114
+
115
+ Scans your project (gitignore-aware), shows discovered directories and extensions with file counts, and lets you pick which to watch via a numbered-list prompt. It writes your choices back to `.codevira/config.yaml` and offers to rebuild the index.
116
+
117
+ **Non-interactive** (useful in scripts or CI):
118
+ ```bash
119
+ codevira configure --dirs src,packages,apps --extensions .py,.ts,.tsx --no-reindex
120
+ ```
121
+
122
+ After changing watched directories, **restart your AI tool** — running watchers snapshot the dir set at boot.
123
+
106
124
  ### Manual config (only if auto-inject didn't detect your tool)
107
125
 
108
126
  Codevira supports two transports. Use the right one for your client:
@@ -531,6 +549,8 @@ Contributions are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md) for the full
531
549
  **Requesting a feature?** [Open a feature request](https://github.com/sachinshelke/codevira/issues/new?template=feature_request.md)
532
550
  **Found a security issue?** Read [SECURITY.md](SECURITY.md) — please don't use public issues for vulnerabilities.
533
551
 
552
+ **Testing a release candidate locally?** See [docs/local-pypi-https.md](docs/local-pypi-https.md) for setting up a Docker-based HTTPS PyPI registry that mirrors the real PyPI install flow without touching public PyPI.
553
+
534
554
  ---
535
555
 
536
556
  ## FAQ
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: codevira
3
- Version: 1.7.0
3
+ Version: 1.8.0
4
4
  Summary: Persistent adaptive memory for AI coding agents — MCP server with context graph, semantic search, adaptive learning, roadmap tracking, and cross-tool continuity.
5
5
  Author-email: Sachin Shelke <sachin.worldnet@gmail.com>
6
6
  License: MIT
@@ -44,7 +44,7 @@ Provides-Extra: all
44
44
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/)
45
45
  [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
46
46
  [![MCP](https://img.shields.io/badge/protocol-MCP-purple)](https://modelcontextprotocol.io)
47
- [![Version](https://img.shields.io/badge/version-1.7.0-orange)](CHANGELOG.md)
47
+ [![Version](https://img.shields.io/badge/version-1.8.0-orange)](CHANGELOG.md)
48
48
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](CONTRIBUTING.md)
49
49
 
50
50
  **Built for solo developers** working on local projects with AI agents. Codevira gives every AI tool you use access to the same persistent project memory — so you stop re-explaining your codebase every session, stop losing carefully-made decisions, and stop burning tokens on re-discovery.
@@ -142,6 +142,24 @@ Ask your AI agent to call `get_roadmap()` — it should return your current phas
142
142
 
143
143
  > **Note:** Restart your AI tool after running `codevira register` to pick up the new MCP config.
144
144
 
145
+ ### Customizing what's indexed
146
+
147
+ Codevira tries to auto-detect your project's source layout, but monorepo or non-standard layouts sometimes slip through — you'll notice when `codevira index --full` reports `0 chunks indexed` and prints a hint pointing you here.
148
+
149
+ ```bash
150
+ cd your-project
151
+ codevira configure
152
+ ```
153
+
154
+ Scans your project (gitignore-aware), shows discovered directories and extensions with file counts, and lets you pick which to watch via a numbered-list prompt. It writes your choices back to `.codevira/config.yaml` and offers to rebuild the index.
155
+
156
+ **Non-interactive** (useful in scripts or CI):
157
+ ```bash
158
+ codevira configure --dirs src,packages,apps --extensions .py,.ts,.tsx --no-reindex
159
+ ```
160
+
161
+ After changing watched directories, **restart your AI tool** — running watchers snapshot the dir set at boot.
162
+
145
163
  ### Manual config (only if auto-inject didn't detect your tool)
146
164
 
147
165
  Codevira supports two transports. Use the right one for your client:
@@ -570,6 +588,8 @@ Contributions are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md) for the full
570
588
  **Requesting a feature?** [Open a feature request](https://github.com/sachinshelke/codevira/issues/new?template=feature_request.md)
571
589
  **Found a security issue?** Read [SECURITY.md](SECURITY.md) — please don't use public issues for vulnerabilities.
572
590
 
591
+ **Testing a release candidate locally?** See [docs/local-pypi-https.md](docs/local-pypi-https.md) for setting up a Docker-based HTTPS PyPI registry that mirrors the real PyPI install flow without touching public PyPI.
592
+
573
593
  ---
574
594
 
575
595
  ## FAQ
@@ -20,6 +20,7 @@ codevira.egg-info/top_level.txt
20
20
  docs/how-i-built-persistent-memory-for-ai-agents.md
21
21
  docs/linkedin-article-ai-agent-memory.md
22
22
  docs/linkedin-post-ai-agent-memory.md
23
+ docs/local-pypi-https.md
23
24
  docs/medium-your-ai-coding-agent-has-amnesia.md
24
25
  docs/roadmap.md
25
26
  graph/_schema.yaml
@@ -36,6 +37,7 @@ mcp_server/__init__.py
36
37
  mcp_server/__main__.py
37
38
  mcp_server/auto_init.py
38
39
  mcp_server/cli.py
40
+ mcp_server/cli_configure.py
39
41
  mcp_server/crash_logger.py
40
42
  mcp_server/detect.py
41
43
  mcp_server/gitignore.py
@@ -89,6 +91,7 @@ rules/testing-standards.md
89
91
  tests/test_auto_init.py
90
92
  tests/test_chunker.py
91
93
  tests/test_cli.py
94
+ tests/test_cli_configure.py
92
95
  tests/test_crash_logger.py
93
96
  tests/test_detect.py
94
97
  tests/test_gitignore.py
@@ -0,0 +1,184 @@
1
+ # Local PyPI with HTTPS — for private-registry testing and IDE integration
2
+
3
+ This guide sets up a **local HTTPS-secured PyPI registry** using Docker, so
4
+ you can:
5
+
6
+ - Smoke-test a release candidate by uploading to a private registry and
7
+ reinstalling `pipx install codevira` from it (exactly the flow a real
8
+ user will hit, just scoped to your machine).
9
+ - Serve `codevira` packages to team machines or CI over HTTPS.
10
+ - Test HTTPS-aware install flows before pushing to public PyPI.
11
+
12
+ The stack is two containers on a shared docker network:
13
+
14
+ ```
15
+ ┌──────────────────────────┐ ┌────────────────────────────┐
16
+ │ nginx:alpine │ │ pypiserver/pypiserver │
17
+ │ :8443 (HTTPS, self-sig)│ ───▶ │ :8080 (plain HTTP) │
18
+ │ reverse proxy │ │ htpasswd auth on uploads │
19
+ └──────────────────────────┘ └────────────────────────────┘
20
+ ```
21
+
22
+ The self-signed cert is fine for local testing. For LAN/team use, replace
23
+ it with a real cert from your own CA or Let's Encrypt via DNS-01.
24
+
25
+ ---
26
+
27
+ ## One-time setup
28
+
29
+ ```bash
30
+ BASE=~/.codevira-local-pypi
31
+ mkdir -p "$BASE/packages" "$BASE/certs"
32
+
33
+ # 1. Create test credentials (replace 'testuser/testpass' for real use)
34
+ htpasswd -cb "$BASE/htpasswd" testuser testpass
35
+
36
+ # 2. Generate a self-signed cert for localhost
37
+ openssl req -x509 -newkey rsa:4096 -nodes \
38
+ -keyout "$BASE/certs/key.pem" -out "$BASE/certs/cert.pem" \
39
+ -days 365 \
40
+ -subj "/CN=localhost" \
41
+ -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
42
+
43
+ # 3. Write an nginx config that reverse-proxies HTTPS:8443 → pypi:8080
44
+ cat > "$BASE/nginx.conf" <<'CONF'
45
+ events { worker_connections 1024; }
46
+ http {
47
+ server {
48
+ listen 443 ssl;
49
+ server_name localhost;
50
+ ssl_certificate /etc/nginx/certs/cert.pem;
51
+ ssl_certificate_key /etc/nginx/certs/key.pem;
52
+ client_max_body_size 50M;
53
+
54
+ location / {
55
+ proxy_pass http://pypi:8080;
56
+ proxy_set_header Host $host;
57
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
58
+ proxy_set_header X-Forwarded-Proto https;
59
+ }
60
+ }
61
+ }
62
+ CONF
63
+
64
+ # 4. Create a shared network so nginx can resolve 'pypi' by name
65
+ docker network create codevira-pypi-net
66
+ ```
67
+
68
+ ## Start the stack
69
+
70
+ ```bash
71
+ BASE=~/.codevira-local-pypi
72
+
73
+ # pypiserver (name it 'pypi' so nginx can resolve it)
74
+ docker run -d \
75
+ --name pypi \
76
+ --network codevira-pypi-net \
77
+ -p 8080:8080 \
78
+ -v "$BASE/packages:/data/packages" \
79
+ -v "$BASE/htpasswd:/data/.htpasswd:ro" \
80
+ pypiserver/pypiserver:latest \
81
+ run \
82
+ --passwords /data/.htpasswd \
83
+ --authenticate update \
84
+ --overwrite \
85
+ /data/packages
86
+
87
+ # nginx HTTPS reverse proxy on 8443
88
+ docker run -d \
89
+ --name codevira-pypi-nginx \
90
+ --network codevira-pypi-net \
91
+ -p 8443:443 \
92
+ -v "$BASE/certs:/etc/nginx/certs:ro" \
93
+ -v "$BASE/nginx.conf:/etc/nginx/nginx.conf:ro" \
94
+ nginx:alpine
95
+
96
+ # Health check (the -k is OK — we know the cert is self-signed)
97
+ curl -sk https://localhost:8443/ | head -3
98
+ ```
99
+
100
+ ## Publish a release to your local registry
101
+
102
+ ```bash
103
+ cd /path/to/agent-mcp
104
+
105
+ # 1. Build the distribution artifacts
106
+ rm -rf dist/ build/ *.egg-info
107
+ python3 -m build
108
+
109
+ # 2. Upload via HTTPS
110
+ TWINE_USERNAME=testuser TWINE_PASSWORD=testpass twine upload \
111
+ --repository-url https://localhost:8443 \
112
+ --cert ~/.codevira-local-pypi/certs/cert.pem \
113
+ dist/codevira-1.8.0-py3-none-any.whl \
114
+ dist/codevira-1.8.0.tar.gz
115
+ ```
116
+
117
+ ## Install from the local registry
118
+
119
+ ```bash
120
+ pipx uninstall codevira # remove any previous install
121
+
122
+ pipx install codevira==1.8.0 \
123
+ --python python3.11 \
124
+ --index-url "https://testuser:testpass@localhost:8443/simple/" \
125
+ --pip-args="--trusted-host localhost --cert $HOME/.codevira-local-pypi/certs/cert.pem"
126
+
127
+ # Verify
128
+ codevira --help
129
+ python3 -c "import mcp_server; print(mcp_server.__version__)"
130
+ ```
131
+
132
+ ## Cleanup
133
+
134
+ Stop and remove containers + network when done:
135
+
136
+ ```bash
137
+ docker rm -f codevira-pypi-nginx pypi
138
+ docker network rm codevira-pypi-net
139
+ # Optional — delete everything:
140
+ rm -rf ~/.codevira-local-pypi
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Common gotchas
146
+
147
+ - **`--skip-existing` not supported.** `pypiserver` doesn't implement this
148
+ twine flag. Either delete the old wheel from `~/.codevira-local-pypi/
149
+ packages/` or set `--overwrite` at container start (as above) so
150
+ re-uploads of the same version replace the existing file.
151
+
152
+ - **Self-signed cert trust.** Every client that hits the registry needs to
153
+ trust `~/.codevira-local-pypi/certs/cert.pem`. `twine` takes
154
+ `--cert <path>`, `pip` takes `--cert <path>` plus `--trusted-host
155
+ localhost`. For system-wide trust on macOS you can add it to the
156
+ Keychain, but for per-command flags it's simpler to pass `--cert` each
157
+ time.
158
+
159
+ - **Port 8443 already in use.** Change the host port in the `-p
160
+ 8443:443` mapping. The container-side port stays `443` (inside nginx).
161
+
162
+ - **Adding `codevira` to an IDE/app that requires HTTPS.** Most MCP clients
163
+ (Claude Code, Cursor, Windsurf, Antigravity) use **stdio transport by
164
+ default**, not HTTPS — stdio needs no URL at all, just the `codevira`
165
+ binary path. `codevira register` sets this up for you. HTTPS transport
166
+ is only needed for the preview `codevira serve --https` multi-project
167
+ server mode (see v1.7 HTTPS-preview docs). If your client is asking for
168
+ an HTTPS URL, that's the serve path — different from this local-PyPI
169
+ guide, which covers **package distribution**.
170
+
171
+ ---
172
+
173
+ ## Why HTTP isn't enough for team/CI use
174
+
175
+ Local HTTP works fine for solo smoke-testing on a single machine, but:
176
+
177
+ - `pip` in modern versions warns on `--index-url http://...` and some
178
+ environments block plain-HTTP index URLs entirely.
179
+ - When team members on other machines `pipx install codevira` from your
180
+ registry, they expect HTTPS.
181
+ - Corporate proxies often block plain-HTTP traffic.
182
+
183
+ The self-signed-cert setup above is a one-command step up from HTTP and
184
+ gives you a realistic distribution flow without touching public PyPI.
@@ -21,12 +21,53 @@ class GlobalDB:
21
21
  def __init__(self, db_path: str | Path):
22
22
  self.db_path = Path(db_path)
23
23
  self.db_path.parent.mkdir(parents=True, exist_ok=True)
24
- self.conn = sqlite3.connect(str(self.db_path), timeout=5)
24
+ # 30s timeout (up from 5s): handles later-write contention under load.
25
+ self.conn = sqlite3.connect(str(self.db_path), timeout=30)
25
26
  self.conn.row_factory = sqlite3.Row
26
- self.conn.execute("PRAGMA journal_mode=WAL")
27
+ # Enable WAL with retries. `PRAGMA journal_mode=WAL` requires an
28
+ # exclusive lock and — unlike normal SQL — does NOT honour the
29
+ # `busy_timeout`. When multiple threads/processes open the same fresh
30
+ # database file concurrently, they all race to flip the journal mode
31
+ # and some raise `OperationalError('database is locked')`. Skip the
32
+ # PRAGMA if WAL is already the effective mode, otherwise retry with
33
+ # short backoff. After ~1s we give up and fall through — the DB still
34
+ # works in the default rollback-journal mode.
35
+ self._enable_wal_with_retry()
27
36
  self.conn.execute("PRAGMA foreign_keys=ON")
37
+ # SQLite-level busy timeout for subsequent writes (complements the
38
+ # `timeout=30` on the connect — matters for later transactions).
39
+ self.conn.execute("PRAGMA busy_timeout=30000")
28
40
  self._init_schema()
29
41
 
42
+ def _enable_wal_with_retry(self, attempts: int = 10, initial_delay: float = 0.02) -> None:
43
+ """Best-effort enable of WAL journal mode.
44
+
45
+ Survives concurrent-open races by short-backoff retry and by
46
+ short-circuiting when WAL is already active (in which case every
47
+ other connection sees it too — no PRAGMA needed).
48
+ """
49
+ import time as _time
50
+ try:
51
+ mode = self.conn.execute("PRAGMA journal_mode").fetchone()[0]
52
+ if str(mode).lower() == "wal":
53
+ return # already WAL — nothing to do
54
+ except sqlite3.OperationalError:
55
+ pass # fall through to the retry loop
56
+ delay = initial_delay
57
+ for _ in range(attempts):
58
+ try:
59
+ self.conn.execute("PRAGMA journal_mode=WAL")
60
+ return
61
+ except sqlite3.OperationalError as e:
62
+ if "locked" not in str(e).lower():
63
+ raise
64
+ _time.sleep(delay)
65
+ delay = min(delay * 1.5, 0.2)
66
+ # Last-resort: continue in the default journal mode. Non-fatal —
67
+ # concurrent writers are still serialized, just without WAL's
68
+ # reader-during-writer benefit.
69
+ logger.warning("Could not enable WAL on %s after retries; continuing in default mode", self.db_path)
70
+
30
71
  def _init_schema(self) -> None:
31
72
  self.conn.executescript("""
32
73
  CREATE TABLE IF NOT EXISTS projects (