mempalace-code 1.3.0__tar.gz → 1.4.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.
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/CHANGELOG.md +13 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/CLAUDE.md +2 -1
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/PKG-INFO +29 -5
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/README.md +28 -4
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/docs/BACKLOG-archived.yaml +12 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/docs/BACKLOG.yaml +0 -41
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/cli.py +110 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/miner.py +41 -5
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/storage.py +23 -2
- mempalace_code-1.4.0/mempalace/watcher.py +619 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/pyproject.toml +1 -1
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_miner.py +45 -2
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_symbol_extract.py +35 -0
- mempalace_code-1.3.0/mempalace/watcher.py +0 -240
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/prompts/codex-hardening-review.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/prompts/codex-plan-review.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/settings.json +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/_shared/commit-checkpoint.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/_shared/mode-classification.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/_shared/task-state.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/bench/SKILL.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/doc-refresh/INSTRUCTIONS.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/doc-refresh/SKILL.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/entropy-gc/INSTRUCTIONS.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/entropy-gc/SKILL.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/mine/SKILL.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/palace-health/SKILL.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/release/SKILL.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/ship/INSTRUCTIONS.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/ship/SKILL.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/start/INSTRUCTIONS.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/start/SKILL.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/status/SKILL.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/task-hardening/INSTRUCTIONS.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/task-hardening/SKILL.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/task-plan/INSTRUCTIONS.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/task-plan/SKILL.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/verify/INSTRUCTIONS.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.claude/skills/verify/SKILL.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.github/workflows/ci.yml +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.github/workflows/publish.yml +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.gitignore +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/.pre-commit-config.yaml +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/CONTRIBUTING.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/LICENSE +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/NOTICE +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/assets/mempalace_banner.jpg +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/benchmarks/BENCHMARKS.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/benchmarks/HYBRID_MODE.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/benchmarks/README.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/benchmarks/convomem_bench.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/benchmarks/dotnet_bench.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/benchmarks/embed_ab_bench.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/benchmarks/locomo_bench.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/benchmarks/longmemeval_bench.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/benchmarks/membench_bench.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/benchmarks/results_embed_ab_2026-04-09.json +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/benchmarks/results_token_delta_mempalace.json +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/benchmarks/results_token_delta_wh40k.json +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/benchmarks/token_delta_bench.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/docs/AGENT_INSTALL.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/docs/BACKUP_RESTORE.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/docs/BENCH_TOKEN_DELTA.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/docs/COMPARISON_GRAPHIFY.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/docs/HOW_SEARCH_WORKS.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/docs/OFFLINE_USAGE.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/docs/UPSTREAM_HARDENING.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/docs/WHY_THIS_FORK.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/examples/HOOKS_TUTORIAL.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/examples/basic_mining.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/examples/convo_import.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/examples/gemini_cli_setup.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/examples/mcp_setup.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/hooks/README.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/hooks/mempal_precompact_hook.sh +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/hooks/mempal_save_hook.sh +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/README.md +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/__init__.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/__main__.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/_chroma_store.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/backup.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/config.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/convo_miner.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/dialect.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/entity_detector.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/entity_registry.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/export.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/general_extractor.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/knowledge_graph.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/layers.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/mcp_server.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/migrate.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/normalize.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/onboarding.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/palace_graph.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/py.typed +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/room_detector_local.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/searcher.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/spellcheck.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/split_mega_files.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/treesitter.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/mempalace/version.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/scripts/bootstrap.sh +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/scripts/codex-review.sh +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/scripts/nuke_wing.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/conftest.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_backup.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_chroma_compat.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_chunking.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_cli.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_config.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_convo_miner.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_dialect.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_dotnet_config.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_e2e.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_embed_ab_bench.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_export.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_kg_extract.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_knowledge_graph.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_lang_detect.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_mcp_server.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_migrate.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_normalize.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_offline.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_searcher.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_split_mega_files.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_storage.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_storage_lance.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_treesitter.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_version_consistency.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/tests/test_watcher.py +0 -0
- {mempalace_code-1.3.0 → mempalace_code-1.4.0}/uv.lock +0 -0
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.4.0 — 2026-04-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Watcher quiet mode** — re-mines suppress verbose output; only logs a one-line summary when drawers are actually filed; no-op commits produce zero log noise; optimize skipped on empty batches
|
|
7
|
+
- **Per-project `bin/` skip** — `bin/` no longer globally skipped; only excluded when .NET project markers (`.csproj`, `.sln`, `.fsproj`, `.vbproj`) are present (MINE-BIN-SKIP-DIRS)
|
|
8
|
+
- **Kotlin nested generic receiver** — `fun <T> List<Pair<K,V>>.ext()` now parsed correctly (MINE-KOTLIN-GENERIC-RECEIVER-NESTED)
|
|
9
|
+
- `mine()` now returns stats dict (`files_processed`, `drawers_filed`, `elapsed_secs`)
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- **Watcher on-commit detection** — `watchfiles.DefaultFilter` ignores `.git/` by default; on-commit mode now passes `watch_filter=None` so `.git/refs/heads/` changes are detected
|
|
13
|
+
- **Watcher log buffering** — flush Python stdout/stderr before restoring file descriptors to prevent mine() output leaking to real stdout
|
|
14
|
+
- **HuggingFace/safetensors noise** — suppress BertModel LOAD REPORT and progress bars via OS fd-level redirect during model init
|
|
15
|
+
|
|
3
16
|
## v1.3.0 — 2026-04-19
|
|
4
17
|
|
|
5
18
|
First-class C#/.NET support — delivers [rergards/mempalace-code#1](https://github.com/rergards/mempalace-code/issues/1) in full.
|
|
@@ -72,7 +72,8 @@ Line length: 100. Target: py39. Quote style: double.
|
|
|
72
72
|
| `layers.py` | Tiered context loading — L0/L1/L2/L3 wake-up layers for local models |
|
|
73
73
|
| `palace_graph.py` | Graph traversal and tunnel detection across wings/rooms |
|
|
74
74
|
| `mcp_server.py` | MCP server — exposes palace tools to Claude Code and other MCP clients |
|
|
75
|
-
| `
|
|
75
|
+
| `watcher.py` | File watcher — `watch_and_mine`, `watch_all`, launchd/cron schedule rendering |
|
|
76
|
+
| `cli.py` | `mempalace` CLI entry point — init, mine, mine-all, watch, search, health, repair, backup |
|
|
76
77
|
|
|
77
78
|
## Architecture Principles
|
|
78
79
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mempalace-code
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Developer memory tool — mine codebases and conversations into a LanceDB-backed searchable palace. No API key required.
|
|
5
5
|
Project-URL: Homepage, https://github.com/rergards/mempalace-code
|
|
6
6
|
Project-URL: Repository, https://github.com/rergards/mempalace-code
|
|
@@ -77,7 +77,7 @@ No cloud. No API keys. No subscription. Nothing leaves your machine.
|
|
|
77
77
|
<tr>
|
|
78
78
|
<td align="center"><strong>595x Token Savings</strong><br><sub>measured peak · median 80x<br><a href="docs/BENCH_TOKEN_DELTA.md">scales with project size</a></sub></td>
|
|
79
79
|
<td align="center"><strong>Cross-Project Tunnels</strong><br><sub>Search <code>auth</code> in one project<br>find it everywhere</sub></td>
|
|
80
|
-
<td align="center"><strong>
|
|
80
|
+
<td align="center"><strong>1008 Tests · $0 Cost</strong><br><sub>Every feature acceptance-gated<br>fully offline after install</sub></td>
|
|
81
81
|
</tr>
|
|
82
82
|
</table>
|
|
83
83
|
|
|
@@ -175,10 +175,30 @@ Tree-sitter is optional (`pip install "mempalace-code[treesitter]"`). Without it
|
|
|
175
175
|
mempalace mine ~/projects/myapp # all supported file types
|
|
176
176
|
mempalace mine ~/projects/myapp --wing myapp # tag with a specific wing
|
|
177
177
|
mempalace mine ~/chats/ --mode convos # mine conversation exports
|
|
178
|
+
mempalace mine-all ~/projects/ # batch mine all projects in a directory
|
|
178
179
|
```
|
|
179
180
|
|
|
180
181
|
Mining is **incremental** by default — content-hash based, only changed files are re-chunked. Use `--full` to force a rebuild.
|
|
181
182
|
|
|
183
|
+
### Auto-Watch
|
|
184
|
+
|
|
185
|
+
Keep your palace in sync automatically. By default, watches `.git/refs/heads/` and re-mines only on **commit** — no noise from work-in-progress saves. Handles multiple branches and worktrees.
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
mempalace watch ~/projects/ # watch all projects (on commit, default)
|
|
189
|
+
mempalace watch ~/projects/ --on-save # watch all file saves instead (noisier)
|
|
190
|
+
mempalace watch ~/projects/ schedule # print launchd/cron snippet for daemon
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Install as persistent daemon (macOS):**
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
mempalace watch ~/projects/ schedule > ~/Library/LaunchAgents/com.mempalace.watch.plist
|
|
197
|
+
launchctl load ~/Library/LaunchAgents/com.mempalace.watch.plist
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Starts at login, restarts if crashed. Logs to `/tmp/mempalace-watch.log`.
|
|
201
|
+
|
|
182
202
|
---
|
|
183
203
|
|
|
184
204
|
### The Palace
|
|
@@ -421,7 +441,7 @@ This is a code-first fork of [milla-jovovich/mempalace](https://github.com/milla
|
|
|
421
441
|
| ChromaDB — [silently deletes data on version bump](https://github.com/milla-jovovich/mempalace/issues/469) | LanceDB — crash-safe Arrow storage, no version-cliff |
|
|
422
442
|
| "No internet after install" — [false](https://github.com/milla-jovovich/mempalace/issues/524) | `mempalace init` downloads model explicitly; fully offline after |
|
|
423
443
|
| "100% R@5" — [unverifiable](https://github.com/milla-jovovich/mempalace/issues/27) | Number removed. Methodology caveats documented |
|
|
424
|
-
| ~30% test coverage |
|
|
444
|
+
| ~30% test coverage | 1008 tests, every feature acceptance-gated |
|
|
425
445
|
| No backup, no recovery | `backup` / `restore` / `export` / `import` |
|
|
426
446
|
| No incremental mining | Content-hash incremental: only changed files re-chunked |
|
|
427
447
|
| No code-search | `code_search` — filter by language, symbol, glob |
|
|
@@ -496,6 +516,10 @@ mempalace mine <dir> --full # force full rebuild
|
|
|
496
516
|
mempalace mine <dir> --watch # auto-incremental on file changes
|
|
497
517
|
mempalace mine-all <parent-dir> # batch mine all projects in a directory
|
|
498
518
|
|
|
519
|
+
# Watch (multi-project auto-sync)
|
|
520
|
+
mempalace watch <parent-dir> # watch all initialized projects
|
|
521
|
+
mempalace watch <parent-dir> schedule # print launchd/cron daemon snippet
|
|
522
|
+
|
|
499
523
|
# Search
|
|
500
524
|
mempalace search "query" # search everything
|
|
501
525
|
mempalace search "query" --wing myapp # scoped to wing
|
|
@@ -559,7 +583,7 @@ mempalace/
|
|
|
559
583
|
├── benchmarks/ ← reproducible benchmark runners
|
|
560
584
|
├── hooks/ ← Claude Code auto-save hooks
|
|
561
585
|
├── examples/ ← usage examples
|
|
562
|
-
└── tests/ ←
|
|
586
|
+
└── tests/ ← 1008 tests
|
|
563
587
|
```
|
|
564
588
|
|
|
565
589
|
</details>
|
|
@@ -579,7 +603,7 @@ python -m pytest tests/ -x -q # full suite, all local, no network
|
|
|
579
603
|
Apache 2.0 — see [LICENSE](LICENSE) and [NOTICE](NOTICE).
|
|
580
604
|
|
|
581
605
|
<!-- Link Definitions -->
|
|
582
|
-
[version-shield]: https://img.shields.io/badge/version-1.
|
|
606
|
+
[version-shield]: https://img.shields.io/badge/version-1.4.0-4dc9f6?style=flat-square&labelColor=0a0e14
|
|
583
607
|
[release-link]: https://github.com/rergards/mempalace-code/releases
|
|
584
608
|
[python-shield]: https://img.shields.io/badge/python-3.9+-7dd8f8?style=flat-square&labelColor=0a0e14&logo=python&logoColor=7dd8f8
|
|
585
609
|
[python-link]: https://www.python.org/
|
|
@@ -29,7 +29,7 @@ No cloud. No API keys. No subscription. Nothing leaves your machine.
|
|
|
29
29
|
<tr>
|
|
30
30
|
<td align="center"><strong>595x Token Savings</strong><br><sub>measured peak · median 80x<br><a href="docs/BENCH_TOKEN_DELTA.md">scales with project size</a></sub></td>
|
|
31
31
|
<td align="center"><strong>Cross-Project Tunnels</strong><br><sub>Search <code>auth</code> in one project<br>find it everywhere</sub></td>
|
|
32
|
-
<td align="center"><strong>
|
|
32
|
+
<td align="center"><strong>1008 Tests · $0 Cost</strong><br><sub>Every feature acceptance-gated<br>fully offline after install</sub></td>
|
|
33
33
|
</tr>
|
|
34
34
|
</table>
|
|
35
35
|
|
|
@@ -127,10 +127,30 @@ Tree-sitter is optional (`pip install "mempalace-code[treesitter]"`). Without it
|
|
|
127
127
|
mempalace mine ~/projects/myapp # all supported file types
|
|
128
128
|
mempalace mine ~/projects/myapp --wing myapp # tag with a specific wing
|
|
129
129
|
mempalace mine ~/chats/ --mode convos # mine conversation exports
|
|
130
|
+
mempalace mine-all ~/projects/ # batch mine all projects in a directory
|
|
130
131
|
```
|
|
131
132
|
|
|
132
133
|
Mining is **incremental** by default — content-hash based, only changed files are re-chunked. Use `--full` to force a rebuild.
|
|
133
134
|
|
|
135
|
+
### Auto-Watch
|
|
136
|
+
|
|
137
|
+
Keep your palace in sync automatically. By default, watches `.git/refs/heads/` and re-mines only on **commit** — no noise from work-in-progress saves. Handles multiple branches and worktrees.
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
mempalace watch ~/projects/ # watch all projects (on commit, default)
|
|
141
|
+
mempalace watch ~/projects/ --on-save # watch all file saves instead (noisier)
|
|
142
|
+
mempalace watch ~/projects/ schedule # print launchd/cron snippet for daemon
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Install as persistent daemon (macOS):**
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
mempalace watch ~/projects/ schedule > ~/Library/LaunchAgents/com.mempalace.watch.plist
|
|
149
|
+
launchctl load ~/Library/LaunchAgents/com.mempalace.watch.plist
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Starts at login, restarts if crashed. Logs to `/tmp/mempalace-watch.log`.
|
|
153
|
+
|
|
134
154
|
---
|
|
135
155
|
|
|
136
156
|
### The Palace
|
|
@@ -373,7 +393,7 @@ This is a code-first fork of [milla-jovovich/mempalace](https://github.com/milla
|
|
|
373
393
|
| ChromaDB — [silently deletes data on version bump](https://github.com/milla-jovovich/mempalace/issues/469) | LanceDB — crash-safe Arrow storage, no version-cliff |
|
|
374
394
|
| "No internet after install" — [false](https://github.com/milla-jovovich/mempalace/issues/524) | `mempalace init` downloads model explicitly; fully offline after |
|
|
375
395
|
| "100% R@5" — [unverifiable](https://github.com/milla-jovovich/mempalace/issues/27) | Number removed. Methodology caveats documented |
|
|
376
|
-
| ~30% test coverage |
|
|
396
|
+
| ~30% test coverage | 1008 tests, every feature acceptance-gated |
|
|
377
397
|
| No backup, no recovery | `backup` / `restore` / `export` / `import` |
|
|
378
398
|
| No incremental mining | Content-hash incremental: only changed files re-chunked |
|
|
379
399
|
| No code-search | `code_search` — filter by language, symbol, glob |
|
|
@@ -448,6 +468,10 @@ mempalace mine <dir> --full # force full rebuild
|
|
|
448
468
|
mempalace mine <dir> --watch # auto-incremental on file changes
|
|
449
469
|
mempalace mine-all <parent-dir> # batch mine all projects in a directory
|
|
450
470
|
|
|
471
|
+
# Watch (multi-project auto-sync)
|
|
472
|
+
mempalace watch <parent-dir> # watch all initialized projects
|
|
473
|
+
mempalace watch <parent-dir> schedule # print launchd/cron daemon snippet
|
|
474
|
+
|
|
451
475
|
# Search
|
|
452
476
|
mempalace search "query" # search everything
|
|
453
477
|
mempalace search "query" --wing myapp # scoped to wing
|
|
@@ -511,7 +535,7 @@ mempalace/
|
|
|
511
535
|
├── benchmarks/ ← reproducible benchmark runners
|
|
512
536
|
├── hooks/ ← Claude Code auto-save hooks
|
|
513
537
|
├── examples/ ← usage examples
|
|
514
|
-
└── tests/ ←
|
|
538
|
+
└── tests/ ← 1008 tests
|
|
515
539
|
```
|
|
516
540
|
|
|
517
541
|
</details>
|
|
@@ -531,7 +555,7 @@ python -m pytest tests/ -x -q # full suite, all local, no network
|
|
|
531
555
|
Apache 2.0 — see [LICENSE](LICENSE) and [NOTICE](NOTICE).
|
|
532
556
|
|
|
533
557
|
<!-- Link Definitions -->
|
|
534
|
-
[version-shield]: https://img.shields.io/badge/version-1.
|
|
558
|
+
[version-shield]: https://img.shields.io/badge/version-1.4.0-4dc9f6?style=flat-square&labelColor=0a0e14
|
|
535
559
|
[release-link]: https://github.com/rergards/mempalace-code/releases
|
|
536
560
|
[python-shield]: https://img.shields.io/badge/python-3.9+-7dd8f8?style=flat-square&labelColor=0a0e14&logo=python&logoColor=7dd8f8
|
|
537
561
|
[python-link]: https://www.python.org/
|
|
@@ -148,3 +148,15 @@ items:
|
|
|
148
148
|
summary: Track plain Name= attribute as well as x:Name for has_named_control triples
|
|
149
149
|
resolution: '2026-04-19: Updated parse_xaml_file() section 3 to collect both x:Name and plain Name= per element into a set before emitting has_named_control triples; added 2 regression tests (plain Name= and same-value dedup). All 189 tests pass.'
|
|
150
150
|
archived_date: "2026-04-19"
|
|
151
|
+
- key: MINE-KOTLIN-GENERIC-RECEIVER-NESTED
|
|
152
|
+
summary: Extract function names from Kotlin generic functions with deeply nested receiver types
|
|
153
|
+
resolution: '2026-04-19: Replaced [^>]+ with (?:[^<>]|<[^<>]*>)* in fun regex; added 2 tests for depth-2 generic nesting (AC-1, AC-2); all 176 symbol extract tests pass'
|
|
154
|
+
archived_date: "2026-04-19"
|
|
155
|
+
- key: MINE-BIN-SKIP-DIRS
|
|
156
|
+
summary: bin/ in SKIP_DIRS silently excludes non-.NET script directories
|
|
157
|
+
resolution: '2026-04-19: Remove bin from global SKIP_DIRS; add _is_dotnet_project() helper; skip bin/ conditionally in scan_project() when .NET markers present. Hardened round-1: fixed weak .dll test fixture in test_bin_dir_skipped_when_sln_at_root.'
|
|
158
|
+
archived_date: "2026-04-19"
|
|
159
|
+
- key: CLARIFY-MINE-BIN-SKIP-DIRS
|
|
160
|
+
summary: Clarify scope for MINE-BIN-SKIP-DIRS before planning can proceed
|
|
161
|
+
resolution: '2026-04-19: Owner decision: implement per-project detection — skip bin/ only when .NET markers (.csproj, .sln, .fsproj, .vbproj) are present. Add non-.NET regression test.'
|
|
162
|
+
archived_date: "2026-04-19"
|
|
@@ -840,26 +840,6 @@ items:
|
|
|
840
840
|
- [ ] extract_symbol("String getName() {\n}\n", "java") returns ("getName", "method")
|
|
841
841
|
- [ ] extract_symbol("String name;\n", "java") still returns ("", "")
|
|
842
842
|
- [ ] All existing Java symbol-extraction tests pass
|
|
843
|
-
- key: MINE-KOTLIN-GENERIC-RECEIVER-NESTED
|
|
844
|
-
summary: Extract function names from Kotlin generic functions with deeply nested receiver types
|
|
845
|
-
type: feature
|
|
846
|
-
status: open
|
|
847
|
-
priority: low
|
|
848
|
-
size: S
|
|
849
|
-
section_id: immediate
|
|
850
|
-
labels: [feat, miner, kotlin]
|
|
851
|
-
description: |-
|
|
852
|
-
## Problem
|
|
853
|
-
The fun extraction regex uses negated-class [^>] that stops at the first >, so nested generics like Map<String, List<Int>> or bounds like <T : Comparable<T>> cause extract_symbol to return ("", ""). Chunks are stored but symbol metadata is empty, degrading search relevance for generic utility/extension code.
|
|
854
|
-
|
|
855
|
-
## Scope
|
|
856
|
-
- Update fun regex in _KOTLIN_EXTRACT to handle depth-2 nesting
|
|
857
|
-
- Cover: type param bounds, generic receivers
|
|
858
|
-
|
|
859
|
-
## Acceptance criteria
|
|
860
|
-
- [ ] extract_symbol('fun <T : Comparable<T>> List<T>.sorted(): List<T>', 'kotlin') == ('sorted', 'function')
|
|
861
|
-
- [ ] extract_symbol('fun Map<String, List<Int>>.flatten(): List<Int>', 'kotlin') == ('flatten', 'function')
|
|
862
|
-
- [ ] All 23 existing Kotlin symbol tests still pass
|
|
863
843
|
- key: MINE-CSHARP-EXPR-BODY
|
|
864
844
|
summary: C# expression-bodied properties not detected as boundaries or symbols
|
|
865
845
|
type: feature
|
|
@@ -888,27 +868,6 @@ items:
|
|
|
888
868
|
- [ ] extract_symbol('public int Count => _items.Count;\n', 'csharp') returns ('Count', 'property')
|
|
889
869
|
- [ ] chunk_code() creates a boundary at expression-bodied property declarations
|
|
890
870
|
- [ ] Existing tests unaffected
|
|
891
|
-
- key: MINE-BIN-SKIP-DIRS
|
|
892
|
-
summary: bin/ in SKIP_DIRS silently excludes non-.NET script directories
|
|
893
|
-
type: task
|
|
894
|
-
status: open
|
|
895
|
-
priority: low
|
|
896
|
-
size: S
|
|
897
|
-
section_id: immediate
|
|
898
|
-
labels: [miner]
|
|
899
|
-
description: |-
|
|
900
|
-
## Problem
|
|
901
|
-
The MINE-DOTNET feature added 'bin' to SKIP_DIRS globally. In non-.NET projects (e.g. Ruby on Rails, Go), a 'bin/' directory legitimately contains source scripts/executables that should be mined. Currently these files are silently skipped with no warning.
|
|
902
|
-
|
|
903
|
-
## Scope
|
|
904
|
-
- Investigate whether 'bin' should be kept global or made .NET-specific
|
|
905
|
-
- Option A: Replace global 'bin' entry with per-project detection (only skip bin/ when .csproj/.sln is present in the project root)
|
|
906
|
-
- Option B: Document as a known limitation in CLAUDE.md and README
|
|
907
|
-
|
|
908
|
-
## Acceptance criteria
|
|
909
|
-
- A Ruby on Rails or Go project with a 'bin/' directory has those files mined correctly (option A), OR the limitation is clearly documented in user-facing docs (option B)
|
|
910
|
-
- .NET project mining still skips bin/ and obj/ correctly
|
|
911
|
-
- No existing tests regress
|
|
912
871
|
- key: BENCH-DOTNET-CLONE-REPO
|
|
913
872
|
summary: Clone jasontaylordev/CleanArchitecture (pin to a stable tag, e.g. v8.0.x) and record the commit hash before running the benchmark
|
|
914
873
|
type: task
|
|
@@ -14,6 +14,8 @@ Commands:
|
|
|
14
14
|
mempalace mine <dir> Mine project files (default)
|
|
15
15
|
mempalace mine <dir> --mode convos Mine conversation exports
|
|
16
16
|
mempalace mine-all <parent-dir> Mine all projects in a directory
|
|
17
|
+
mempalace watch <parent-dir> Watch all projects for changes, re-mine automatically
|
|
18
|
+
mempalace watch <parent-dir> schedule Print launchd/cron snippet for watch daemon
|
|
17
19
|
mempalace search "query" Find anything, exact words
|
|
18
20
|
mempalace wake-up Show L0 + L1 wake-up context
|
|
19
21
|
mempalace wake-up --wing my_app Wake-up for a specific project
|
|
@@ -770,6 +772,76 @@ def cmd_compress(args):
|
|
|
770
772
|
print(" (dry run -- nothing stored)")
|
|
771
773
|
|
|
772
774
|
|
|
775
|
+
def cmd_watch(args):
|
|
776
|
+
watch_command = getattr(args, "watch_command", None)
|
|
777
|
+
if watch_command == "schedule":
|
|
778
|
+
cmd_watch_schedule(args)
|
|
779
|
+
return
|
|
780
|
+
|
|
781
|
+
# Default: run the watcher
|
|
782
|
+
palace_path = os.path.expanduser(args.palace) if args.palace else MempalaceConfig().palace_path
|
|
783
|
+
|
|
784
|
+
try:
|
|
785
|
+
from .watcher import watch_all
|
|
786
|
+
except ImportError as exc:
|
|
787
|
+
print(f" Error importing watcher: {exc}", file=sys.stderr)
|
|
788
|
+
sys.exit(1)
|
|
789
|
+
|
|
790
|
+
watch_all(
|
|
791
|
+
parent_dir=args.dir,
|
|
792
|
+
palace_path=palace_path,
|
|
793
|
+
agent=args.agent,
|
|
794
|
+
respect_gitignore=not args.no_gitignore,
|
|
795
|
+
on_commit=not getattr(args, "on_save", False),
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def cmd_watch_schedule(args):
|
|
800
|
+
import sys as _sys
|
|
801
|
+
|
|
802
|
+
if getattr(args, "install", False):
|
|
803
|
+
print(
|
|
804
|
+
" owner action required: --install is not supported.\n"
|
|
805
|
+
" Print the snippet with 'mempalace watch <dir> schedule'\n"
|
|
806
|
+
" then install it yourself with: launchctl load <plist> (macOS)\n"
|
|
807
|
+
" or: crontab -e (Linux).",
|
|
808
|
+
file=sys.stderr,
|
|
809
|
+
)
|
|
810
|
+
sys.exit(2)
|
|
811
|
+
|
|
812
|
+
platform = _sys.platform
|
|
813
|
+
if platform.startswith("darwin"):
|
|
814
|
+
platform = "darwin"
|
|
815
|
+
elif platform.startswith("linux"):
|
|
816
|
+
platform = "linux"
|
|
817
|
+
else:
|
|
818
|
+
print(
|
|
819
|
+
f" Error: watch scheduling is not supported on {_sys.platform}.\n"
|
|
820
|
+
" 'mempalace watch schedule' works on macOS (launchd) and Linux (cron) only.",
|
|
821
|
+
file=sys.stderr,
|
|
822
|
+
)
|
|
823
|
+
sys.exit(1)
|
|
824
|
+
|
|
825
|
+
from .watcher import render_watch_schedule
|
|
826
|
+
|
|
827
|
+
try:
|
|
828
|
+
snippet = render_watch_schedule(args.dir, platform)
|
|
829
|
+
except ValueError as exc:
|
|
830
|
+
print(f" Error: {exc}", file=sys.stderr)
|
|
831
|
+
sys.exit(1)
|
|
832
|
+
|
|
833
|
+
print(snippet, end="")
|
|
834
|
+
if platform == "darwin":
|
|
835
|
+
plist_path = "~/Library/LaunchAgents/com.mempalace.watch.plist"
|
|
836
|
+
print("\n # To install:", file=sys.stderr)
|
|
837
|
+
print(f" # mempalace watch {args.dir} schedule > {plist_path}", file=sys.stderr)
|
|
838
|
+
print(f" # launchctl load {plist_path}", file=sys.stderr)
|
|
839
|
+
print(" # To stop:", file=sys.stderr)
|
|
840
|
+
print(f" # launchctl unload {plist_path}", file=sys.stderr)
|
|
841
|
+
else:
|
|
842
|
+
print("\n # To install: crontab -e (paste the line above)", file=sys.stderr)
|
|
843
|
+
|
|
844
|
+
|
|
773
845
|
def cmd_backup_create(args):
|
|
774
846
|
from .backup import create_backup
|
|
775
847
|
|
|
@@ -1190,6 +1262,43 @@ def main():
|
|
|
1190
1262
|
help="Re-download even if already cached",
|
|
1191
1263
|
)
|
|
1192
1264
|
|
|
1265
|
+
# watch
|
|
1266
|
+
p_watch = sub.add_parser(
|
|
1267
|
+
"watch",
|
|
1268
|
+
help="Watch all initialized projects for changes and re-mine automatically",
|
|
1269
|
+
)
|
|
1270
|
+
p_watch.add_argument(
|
|
1271
|
+
"dir",
|
|
1272
|
+
help="Parent directory containing project subdirectories",
|
|
1273
|
+
)
|
|
1274
|
+
p_watch.add_argument(
|
|
1275
|
+
"--no-gitignore",
|
|
1276
|
+
action="store_true",
|
|
1277
|
+
help="Don't respect .gitignore files when scanning project files",
|
|
1278
|
+
)
|
|
1279
|
+
p_watch.add_argument(
|
|
1280
|
+
"--agent",
|
|
1281
|
+
default="mempalace",
|
|
1282
|
+
help="Name recorded on every drawer (default: mempalace)",
|
|
1283
|
+
)
|
|
1284
|
+
p_watch.add_argument(
|
|
1285
|
+
"--on-save",
|
|
1286
|
+
action="store_true",
|
|
1287
|
+
help="Re-mine on every file save instead of only on git commits (noisier)",
|
|
1288
|
+
)
|
|
1289
|
+
watch_sub = p_watch.add_subparsers(dest="watch_command")
|
|
1290
|
+
|
|
1291
|
+
# watch schedule
|
|
1292
|
+
p_watch_schedule = watch_sub.add_parser(
|
|
1293
|
+
"schedule",
|
|
1294
|
+
help="Print a scheduler snippet (launchd plist or cron line) for the watch daemon",
|
|
1295
|
+
)
|
|
1296
|
+
p_watch_schedule.add_argument(
|
|
1297
|
+
"--install",
|
|
1298
|
+
action="store_true",
|
|
1299
|
+
help="(Accepted but rejected with an explanation — owner action required)",
|
|
1300
|
+
)
|
|
1301
|
+
|
|
1193
1302
|
# backup
|
|
1194
1303
|
p_backup = sub.add_parser(
|
|
1195
1304
|
"backup",
|
|
@@ -1324,6 +1433,7 @@ def main():
|
|
|
1324
1433
|
"init": cmd_init,
|
|
1325
1434
|
"mine": cmd_mine,
|
|
1326
1435
|
"mine-all": cmd_mine_all,
|
|
1436
|
+
"watch": cmd_watch,
|
|
1327
1437
|
"split": cmd_split,
|
|
1328
1438
|
"search": cmd_search,
|
|
1329
1439
|
"compress": cmd_compress,
|
|
@@ -158,7 +158,6 @@ SKIP_DIRS = {
|
|
|
158
158
|
".tox",
|
|
159
159
|
".nox",
|
|
160
160
|
".vs",
|
|
161
|
-
"bin",
|
|
162
161
|
"obj",
|
|
163
162
|
".idea",
|
|
164
163
|
".vscode",
|
|
@@ -354,6 +353,27 @@ def is_gitignored(path: Path, matchers: list, is_dir: bool = False) -> bool:
|
|
|
354
353
|
return ignored
|
|
355
354
|
|
|
356
355
|
|
|
356
|
+
_DOTNET_MARKERS = (
|
|
357
|
+
"*.sln",
|
|
358
|
+
"*.csproj",
|
|
359
|
+
"*.fsproj",
|
|
360
|
+
"*.vbproj",
|
|
361
|
+
"*/*.csproj",
|
|
362
|
+
"*/*.fsproj",
|
|
363
|
+
"*/*.vbproj",
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _is_dotnet_project(project_path: Path) -> bool:
|
|
368
|
+
"""Return True if *project_path* looks like a .NET project.
|
|
369
|
+
|
|
370
|
+
Checks for .sln at root level and .csproj/.fsproj/.vbproj at root or one
|
|
371
|
+
level deep (the standard layout: Solution.sln at root, Project/Project.csproj
|
|
372
|
+
in a subdirectory). Uses early-exit to minimise filesystem round-trips.
|
|
373
|
+
"""
|
|
374
|
+
return any(next(project_path.glob(pat), None) is not None for pat in _DOTNET_MARKERS)
|
|
375
|
+
|
|
376
|
+
|
|
357
377
|
def should_skip_dir(dirname: str) -> bool:
|
|
358
378
|
"""Skip known generated/cache directories before gitignore matching."""
|
|
359
379
|
return dirname in SKIP_DIRS or dirname.endswith(".egg-info")
|
|
@@ -863,10 +883,11 @@ _KOTLIN_EXTRACT = [
|
|
|
863
883
|
(re.compile(r"object\s+(\w+)", re.MULTILINE), "object"),
|
|
864
884
|
# fun — optional type params (e.g. `fun <T> identity(…)`) and optional receiver type
|
|
865
885
|
# (e.g. `fun String.isEmpty()` → `isEmpty`, `fun <T> List<T>.map()` → `map`).
|
|
866
|
-
#
|
|
886
|
+
# Uses (?:[^<>]|<[^<>]*>)* instead of [^>]+ to handle depth-2 generic nesting, e.g.
|
|
887
|
+
# `fun <T : Comparable<T>> …` and `fun Map<String, List<Int>>.flatten()`.
|
|
867
888
|
(
|
|
868
889
|
re.compile(
|
|
869
|
-
r"^(?:(?:public|internal|protected|private|abstract|open|final|override|inline|infix|operator|tailrec|suspend|external|expect|actual)\s+)*fun\s+(?:<[
|
|
890
|
+
r"^(?:(?:public|internal|protected|private|abstract|open|final|override|inline|infix|operator|tailrec|suspend|external|expect|actual)\s+)*fun\s+(?:<(?:[^<>]|<[^<>]*>)*>\s+)?(?:\w+(?:<(?:[^<>]|<[^<>]*>)*>)?\.)?(\w+)",
|
|
870
891
|
re.MULTILINE,
|
|
871
892
|
),
|
|
872
893
|
"function",
|
|
@@ -1911,6 +1932,7 @@ def scan_project(
|
|
|
1911
1932
|
active_matchers = []
|
|
1912
1933
|
matcher_cache = {}
|
|
1913
1934
|
include_paths = normalize_include_paths(include_ignored)
|
|
1935
|
+
dotnet_project = _is_dotnet_project(project_path)
|
|
1914
1936
|
|
|
1915
1937
|
for root, dirs, filenames in os.walk(project_path):
|
|
1916
1938
|
root_path = Path(root)
|
|
@@ -1929,7 +1951,7 @@ def scan_project(
|
|
|
1929
1951
|
d
|
|
1930
1952
|
for d in dirs
|
|
1931
1953
|
if is_force_included(root_path / d, project_path, include_paths)
|
|
1932
|
-
or not should_skip_dir(d)
|
|
1954
|
+
or not (should_skip_dir(d) or (dotnet_project and d == "bin"))
|
|
1933
1955
|
]
|
|
1934
1956
|
if respect_gitignore and active_matchers:
|
|
1935
1957
|
dirs[:] = [
|
|
@@ -2623,6 +2645,7 @@ def mine(
|
|
|
2623
2645
|
include_ignored: list = None,
|
|
2624
2646
|
incremental: bool = True,
|
|
2625
2647
|
kg=None,
|
|
2648
|
+
skip_optimize: bool = False,
|
|
2626
2649
|
):
|
|
2627
2650
|
"""Mine a project directory into the palace.
|
|
2628
2651
|
|
|
@@ -2633,6 +2656,10 @@ def mine(
|
|
|
2633
2656
|
*kg* is an optional KnowledgeGraph instance. When provided, .NET project files
|
|
2634
2657
|
(.csproj, .fsproj, .vbproj) and solution files (.sln) are also parsed for
|
|
2635
2658
|
structured dependency triples that are written to the knowledge graph.
|
|
2659
|
+
|
|
2660
|
+
When *skip_optimize* is True, post-mine storage compaction is skipped. Callers
|
|
2661
|
+
(e.g. the watcher) that run many mine() calls in sequence should skip optimize
|
|
2662
|
+
on each call and run a single optimize at the end.
|
|
2636
2663
|
"""
|
|
2637
2664
|
|
|
2638
2665
|
project_path = Path(project_dir).expanduser().resolve()
|
|
@@ -2811,7 +2838,9 @@ def mine(
|
|
|
2811
2838
|
kg.invalidate_by_source_file(stale_path)
|
|
2812
2839
|
|
|
2813
2840
|
config = MempalaceConfig()
|
|
2814
|
-
if
|
|
2841
|
+
if skip_optimize:
|
|
2842
|
+
pass # caller will optimize later
|
|
2843
|
+
elif config.optimize_after_mine:
|
|
2815
2844
|
t0 = time.time()
|
|
2816
2845
|
backup_first = config.backup_before_optimize
|
|
2817
2846
|
if backup_first:
|
|
@@ -2852,6 +2881,13 @@ def mine(
|
|
|
2852
2881
|
print('\n Next: mempalace search "what you\'re looking for"')
|
|
2853
2882
|
print(f"{'=' * 55}\n")
|
|
2854
2883
|
|
|
2884
|
+
return {
|
|
2885
|
+
"files_processed": len(files) - files_skipped,
|
|
2886
|
+
"files_skipped": files_skipped,
|
|
2887
|
+
"drawers_filed": total_drawers,
|
|
2888
|
+
"elapsed_secs": elapsed,
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2855
2891
|
|
|
2856
2892
|
# =============================================================================
|
|
2857
2893
|
# MULTI-PROJECT DETECTION
|
|
@@ -241,11 +241,32 @@ class LanceStore(DrawerStore):
|
|
|
241
241
|
|
|
242
242
|
def __init__(self, palace_path: str, create: bool = True, embed_model: Optional[str] = None):
|
|
243
243
|
import lancedb
|
|
244
|
+
import logging
|
|
244
245
|
|
|
245
246
|
self._model_name = embed_model or DEFAULT_EMBED_MODEL
|
|
246
247
|
self._db = lancedb.connect(os.path.join(palace_path, "lance"))
|
|
247
|
-
|
|
248
|
-
|
|
248
|
+
|
|
249
|
+
# Suppress noisy HF/safetensors output (BertModel LOAD REPORT, tqdm bars,
|
|
250
|
+
# unauthenticated-request warnings). Must redirect at the OS fd level
|
|
251
|
+
# because the noise comes from C/Rust code, not Python.
|
|
252
|
+
hf_logger = logging.getLogger("huggingface_hub")
|
|
253
|
+
prev_level = hf_logger.level
|
|
254
|
+
hf_logger.setLevel(logging.ERROR)
|
|
255
|
+
devnull = os.open(os.devnull, os.O_WRONLY)
|
|
256
|
+
old_stdout = os.dup(1)
|
|
257
|
+
old_stderr = os.dup(2)
|
|
258
|
+
try:
|
|
259
|
+
os.dup2(devnull, 1)
|
|
260
|
+
os.dup2(devnull, 2)
|
|
261
|
+
self._embedder = self._get_embedder()
|
|
262
|
+
self._table = self._open_or_create(create)
|
|
263
|
+
finally:
|
|
264
|
+
os.dup2(old_stdout, 1)
|
|
265
|
+
os.dup2(old_stderr, 2)
|
|
266
|
+
os.close(devnull)
|
|
267
|
+
os.close(old_stdout)
|
|
268
|
+
os.close(old_stderr)
|
|
269
|
+
hf_logger.setLevel(prev_level)
|
|
249
270
|
|
|
250
271
|
def _get_embedder(self):
|
|
251
272
|
"""Load the sentence-transformers embedding model."""
|