flurryx-code-memory 0.6.1__tar.gz → 0.7.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.
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/CHANGELOG.md +201 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/PKG-INFO +1 -1
- {flurryx_code_memory-0.6.1/plugins/cursor → flurryx_code_memory-0.7.0/plugins/claude-code}/scripts/lib/memory.js +85 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/scripts/on-post-tool.js +11 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/scripts/on-session-start.js +12 -2
- {flurryx_code_memory-0.6.1/plugins/claude-code → flurryx_code_memory-0.7.0/plugins/cursor}/scripts/lib/memory.js +85 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/scripts/on-after-file-edit.js +11 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/scripts/on-session-start.js +10 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/opencode/src/code-memory.ts +11 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/pyproject.toml +1 -1
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/cli.py +50 -8
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/config.py +170 -5
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/embed/ollama.py +18 -1
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/graph/falkor_store.py +72 -1
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/mcp_server.py +140 -8
- flurryx_code_memory-0.7.0/src/code_memory/orchestrator/ingest_state.py +117 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/orchestrator/pipeline.py +305 -26
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/sync/autostart/base.py +5 -4
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/sync/autostart/launchd.py +2 -2
- flurryx_code_memory-0.7.0/src/code_memory/sync/safety.py +182 -0
- flurryx_code_memory-0.7.0/src/code_memory/sync/single_flight.py +291 -0
- flurryx_code_memory-0.7.0/tests/test_config_ipv4_defaults.py +117 -0
- flurryx_code_memory-0.7.0/tests/test_embed_ollama_timeout.py +152 -0
- flurryx_code_memory-0.7.0/tests/test_ensure_fresh_no_blocking.py +408 -0
- flurryx_code_memory-0.7.0/tests/test_graph_shadow_swap.py +304 -0
- flurryx_code_memory-0.7.0/tests/test_ingest_safety_and_lock.py +215 -0
- flurryx_code_memory-0.7.0/tests/test_ingest_state.py +131 -0
- flurryx_code_memory-0.7.0/tests/test_pipeline_health_check.py +192 -0
- flurryx_code_memory-0.7.0/tests/test_worktree_autostart_guard.py +293 -0
- flurryx_code_memory-0.7.0/tests/test_worktree_slug.py +225 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/uv.lock +1 -1
- flurryx_code_memory-0.6.1/src/code_memory/orchestrator/ingest_state.py +0 -71
- flurryx_code_memory-0.6.1/src/code_memory/sync/safety.py +0 -93
- flurryx_code_memory-0.6.1/tests/test_ingest_state.py +0 -62
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/.claude-plugin/marketplace.json +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/.env.example +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/.gitignore +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/README.md +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/docker/docker-compose.yml +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/docs/BENCHMARK.md +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/docs/BENCHMARK_VS_BASELINE.json +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/docs/BENCHMARK_VS_BASELINE.md +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/docs/architecture.png +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/docs/benchmark-raw.json +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/docs/hero.png +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/install.ps1 +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/install.sh +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/README.md +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/commands/code-memory.md +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/hooks/hooks.json +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/install.sh +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/scripts/lib/claim-intent.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/scripts/lib/claim-intent.test.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/scripts/lib/io.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/scripts/lib/state.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/scripts/on-pre-tool.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/scripts/on-retrieve-seen.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/scripts/on-stop.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/scripts/on-user-prompt.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/scripts/resolver-debounce.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/skills/code-memory/SKILL.md +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/README.md +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/hooks/hooks.json.template +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/install.sh +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/rules/code-memory.mdc +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/scripts/lib/claim-intent.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/scripts/lib/claim-intent.test.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/scripts/lib/io.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/scripts/lib/state.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/scripts/on-before-mcp-execution.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/scripts/on-before-submit-prompt.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/scripts/on-post-tool-use.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/scripts/on-pre-compact.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/scripts/on-pre-tool-use.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/scripts/on-session-end.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/scripts/on-stop.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/scripts/resolver-debounce.js +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/opencode/README.md +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/opencode/install.sh +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/opencode/package-lock.json +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/opencode/package.json +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/opencode/scripts/add-mcp.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/opencode/scripts/install.mjs +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/opencode/scripts/uninstall.mjs +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/opencode/skills/code-memory/SKILL.md +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/opencode/src/code-memory-lib/claim-intent.test.mts +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/opencode/src/code-memory-lib/claim-intent.ts +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/opencode/src/code-memory-lib/memory-client.ts +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/opencode/tsconfig.json +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/vibe/README.md +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/vibe/install.sh +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/vibe/skills/code-memory/SKILL.md +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/scripts/benchmark.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/scripts/benchmark_queries.json +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/scripts/benchmark_vs_baseline.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/scripts/benchmark_vs_grep.sh +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/scripts/ingest.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/scripts/install.ps1 +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/scripts/install.sh +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/__init__.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/claims/__init__.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/claims/extractor.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/claims/indexer.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/claims/resolver.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/claims/store.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/embed/__init__.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/embed/cache.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/embed/m3.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/embed/tei.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/episodic/__init__.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/episodic/sqlite_store.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/extractor/__init__.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/extractor/csproj.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/extractor/dll.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/extractor/gitignore.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/extractor/nuget.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/extractor/sanity.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/extractor/sln.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/extractor/treesitter.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/graph/__init__.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/metrics.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/orchestrator/__init__.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/orchestrator/git_delta.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/orchestrator/reset.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/orchestrator/resolver.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/orchestrator/retrieve.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/resilience.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/sync/__init__.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/sync/autostart/__init__.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/sync/autostart/schtasks.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/sync/autostart/systemd.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/sync/hooks.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/sync/snapshot.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/sync/store.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/sync/sync.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/sync/watcher.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/updater.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/vector/__init__.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/src/code_memory/vector/qdrant_store.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_autostart_adapters.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_chunk_text.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_claim_extractor.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_claim_indexer.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_claim_resolver.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_claim_store.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_config_embed_dim.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_config_sentinel.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_csproj.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_dll_members.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_dll_parser.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_embed_backend.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_embed_cache.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_embed_m3.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_embed_tei.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_episode_dedup.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_episode_head_sha.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_extractor_csharp.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_extractor_dart.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_extractor_filters.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_extractor_php.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_extractor_python_imports.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_extractor_receiver_type.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_extractor_references.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_extractor_sanity.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_extractor_ts_abstract.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_extractor_ts_inject.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_extractor_utf8.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_file_containment.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_git_delta.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_graph_queries.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_graph_temporal.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_graph_vacuum_at_sha.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_hooks_installer.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_mcp_assert_claim.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_mcp_server_descriptions.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_mcp_shutdown.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_mcp_strict_project.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_metrics.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_nuget_resolver.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_overload_resolution.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_partial_class.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_pipeline_references.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_pipeline_temporal_wiring.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_qdrant_legacy_guard.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_razor_inject.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_resilience.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_resolver.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_resolver_assembly.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_retrieve_claims_surfacing.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_retrieve_rerank.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_sln.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_smoke.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_snapshot_e2e.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_snapshot_format.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_snapshot_store.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_sync_decision_tree.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_watch_safety.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_watcher_debouncer.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_watcher_exclude.py +0 -0
- {flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/tests/test_watcher_ref_events.py +0 -0
|
@@ -8,6 +8,207 @@ when the repo grows.
|
|
|
8
8
|
This file complements `git log`: commits explain mechanics, this file
|
|
9
9
|
explains intent.
|
|
10
10
|
|
|
11
|
+
## [0.7.0] — 2026-06-12
|
|
12
|
+
|
|
13
|
+
Release theme: **Worktree resilience, atomic graph rebuilds, and fresh
|
|
14
|
+
indexes**. The index now survives interrupted full ingests; linked git
|
|
15
|
+
worktrees reuse the main repo's index instead of forcing a cold re-ingest;
|
|
16
|
+
and queries trigger non-blocking background rebuilds when the index drifts
|
|
17
|
+
from HEAD.
|
|
18
|
+
|
|
19
|
+
### Added — Linked git worktree awareness
|
|
20
|
+
|
|
21
|
+
**What:** a new `_git_toplevel()` helper resolves linked git worktrees
|
|
22
|
+
to the main repo's root by running `git rev-parse --git-common-dir`
|
|
23
|
+
after the baseline `--show-toplevel` call. In a linked worktree, the
|
|
24
|
+
two paths differ; in the main worktree, they're the same. The project
|
|
25
|
+
slug is now derived from the main repo's directory name, so all worktrees
|
|
26
|
+
of the same project reuse the same Qdrant / Falkor namespace without
|
|
27
|
+
requiring a cold re-ingest.
|
|
28
|
+
|
|
29
|
+
**Reason:** developers working in linked worktrees (e.g. `git worktree add
|
|
30
|
+
../feature-branch`) saw no code-memory functionality because the ingest
|
|
31
|
+
system minted a separate Qdrant collection and Falkor graph for each
|
|
32
|
+
worktree. A single repo with 3–5 active worktrees accumulated 3–5 cold
|
|
33
|
+
ingests. The main repo and each worktree now share the same index.
|
|
34
|
+
|
|
35
|
+
### Added — Non-persistent autostart for linked worktrees
|
|
36
|
+
|
|
37
|
+
**What:** two new gates in the autostart system:
|
|
38
|
+
|
|
39
|
+
- `is_linked_git_worktree(path)` — checks whether *path* is inside a
|
|
40
|
+
linked worktree via git CLI.
|
|
41
|
+
- `is_non_persistent_watch_dir(path)` — returns True for ephemeral
|
|
42
|
+
session dirs OR linked worktrees; used by `ensure_autostart()` to skip
|
|
43
|
+
registering a persistent OS agent for directories that are temporary or
|
|
44
|
+
share the main repo's watcher.
|
|
45
|
+
- `LaunchdAdapter.prune_stale()` (run on every MCP bootstrap) removes
|
|
46
|
+
launchd agents whose `WorkingDirectory` is gone or is a linked worktree.
|
|
47
|
+
|
|
48
|
+
**Reason:** the prior release fixed the watcher's per-session accumulation;
|
|
49
|
+
this release prevents the same bleed on linked worktrees. A developer with
|
|
50
|
+
2 linked worktrees no longer gets 3 persistent `code-memory watch` units.
|
|
51
|
+
|
|
52
|
+
### Added — Shadow-graph atomic promotion (graph durability fix)
|
|
53
|
+
|
|
54
|
+
**What:** the full ingest pipeline now builds into a shadow FalkorDB
|
|
55
|
+
graph named `<project_graph>__shadow` and atomically promotes it only
|
|
56
|
+
after the rebuild succeeds:
|
|
57
|
+
|
|
58
|
+
1. At ingest start, drop any leftover shadow from a prior interrupted
|
|
59
|
+
rebuild.
|
|
60
|
+
2. Redirect all graph writes to the shadow FalkorStore instance.
|
|
61
|
+
3. On successful completion, execute `GRAPH.DELETE <live>`, then
|
|
62
|
+
`GRAPH.COPY <shadow> <live>`, then `GRAPH.DELETE <shadow>`.
|
|
63
|
+
4. If the copy fails, the live graph is cleared but the shadow stays
|
|
64
|
+
intact — the caller can retry without losing data.
|
|
65
|
+
|
|
66
|
+
**Reason:** before this, an interrupted full ingest (network loss,
|
|
67
|
+
Ollama timeout, Falkor down, user kills the process) left the live
|
|
68
|
+
graph empty so subsequent `callers` / `definitions` / `callees` queries
|
|
69
|
+
returned nothing until a manual full re-ingest. The graph now survives
|
|
70
|
+
interruption — the shadow is cleaned up on the next rebuild attempt.
|
|
71
|
+
|
|
72
|
+
### Added — Health-check guard for stale rebuilds
|
|
73
|
+
|
|
74
|
+
**What:** the ingest state now records `file_count` and `symbol_count`
|
|
75
|
+
from each successful full rebuild. Before starting an incremental ingest,
|
|
76
|
+
`_health_check_ok()` compares the current ingestable file count against
|
|
77
|
+
the stored baseline; if it grew more than 20% and the graph symbol count
|
|
78
|
+
is suspiciously low (below a ratio threshold), a full rebuild is forced
|
|
79
|
+
with a diagnostic message to stderr.
|
|
80
|
+
|
|
81
|
+
Config:
|
|
82
|
+
- `CODE_MEMORY_INGEST_HEALTH_CHECK_ENABLED` (default `true`)
|
|
83
|
+
- `CODE_MEMORY_INGEST_HEALTH_CHECK_MIN_RATIO` (default `0.3` = expect ≥30%
|
|
84
|
+
of file count as symbol count)
|
|
85
|
+
|
|
86
|
+
**Reason:** a transient FalkorDB outage or an incomplete prior ingest
|
|
87
|
+
could leave the graph permanently empty while incremental ingests reported
|
|
88
|
+
success. The health check detects this silently-failed state and forces a
|
|
89
|
+
rebuild, with no user intervention needed.
|
|
90
|
+
|
|
91
|
+
### Added — Single-flight ingest lock
|
|
92
|
+
|
|
93
|
+
**What:** new `src/code_memory/sync/single_flight.py` module provides
|
|
94
|
+
in-process (`asyncio.Lock`) + cross-process (PID file) guards to prevent
|
|
95
|
+
concurrent full ingests for the same (root, project) pair.
|
|
96
|
+
|
|
97
|
+
- `try_acquire(root, project)` — returns True if no rebuild is running,
|
|
98
|
+
False if the slot is taken.
|
|
99
|
+
- `release(root, project)` — releases the slot unconditionally.
|
|
100
|
+
- Stale PID files (dead process or age > 30 min) are silently removed.
|
|
101
|
+
|
|
102
|
+
The Claude Code and Cursor plugins' `on-session-start.js` fast-path-skip a
|
|
103
|
+
spawn when a live ingest is detected, preventing thundering-herd `code-memory
|
|
104
|
+
ingest` calls on boot.
|
|
105
|
+
|
|
106
|
+
**Reason:** on slow machines or large repos, overlapping `code-memory ingest`
|
|
107
|
+
calls could queue up and queue up (especially if the embedder is I/O-bound),
|
|
108
|
+
causing a session to take 5+ minutes to boot. The lock ensures at most one
|
|
109
|
+
rebuild runs; fast-path skips spare the overhead.
|
|
110
|
+
|
|
111
|
+
### Added — Ingest safety guards
|
|
112
|
+
|
|
113
|
+
**What:** new `assert_safe_ingest_root()` function in `sync/safety.py`
|
|
114
|
+
refuses to ingest:
|
|
115
|
+
|
|
116
|
+
1. System/HOME roots (same set as the watcher guard: HOME, /, /tmp,
|
|
117
|
+
/var, /etc, /usr, /System, /Library, /opt, /Applications, C:/, etc.)
|
|
118
|
+
2. Non-git directories (checked via `is_inside_git_worktree()`).
|
|
119
|
+
|
|
120
|
+
Bypass via `CODE_MEMORY_UNSAFE_INGEST=1` env var (env-only, not a CLI
|
|
121
|
+
flag, to prevent accidental use).
|
|
122
|
+
|
|
123
|
+
**Reason:** `code-memory ingest ~` could walk every IDE cache, checkout,
|
|
124
|
+
and node_modules on disk. `ingest` is more dangerous than `watch` because
|
|
125
|
+
it stores results; a non-git directory ingest mints an arbitrary project
|
|
126
|
+
slug. The guard is invoked by the CLI `ingest` entry point and by hooks.
|
|
127
|
+
|
|
128
|
+
### Added — IPv4-default service URLs
|
|
129
|
+
|
|
130
|
+
**What:** the default `OLLAMA_URL`, `QDRANT_URL`, and `FALKOR_URL` now
|
|
131
|
+
use `127.0.0.1` instead of `localhost`. This works around a Windows
|
|
132
|
+
quirk where `localhost` may resolve to `::1` (IPv6) and hang on socket
|
|
133
|
+
connect.
|
|
134
|
+
|
|
135
|
+
Config example (all in `.code-memoryrc` or env):
|
|
136
|
+
```
|
|
137
|
+
OLLAMA_URL=http://127.0.0.1:11434
|
|
138
|
+
QDRANT_URL=http://127.0.0.1:6333
|
|
139
|
+
FALKOR_URL=redis://127.0.0.1:6379
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Reason:** on some Windows setups, the TCP stack prefers IPv6 and
|
|
143
|
+
binds localhost to `::1`, while the service listens on `127.0.0.1`.
|
|
144
|
+
Callers then hang waiting for a timeout (seconds to minutes). Using
|
|
145
|
+
the explicit IPv4 address is more reliable.
|
|
146
|
+
|
|
147
|
+
### Added — Non-blocking `_ensure_fresh` for MCP queries
|
|
148
|
+
|
|
149
|
+
**What:** the pre-query guard `_ensure_fresh()` no longer blocks on a
|
|
150
|
+
sync. Instead:
|
|
151
|
+
|
|
152
|
+
1. Spawn a quick freshness check (`_is_index_stale()`) in a bounded
|
|
153
|
+
daemon thread (`_FRESHNESS_PROBE_TIMEOUT`, default 2.0 s).
|
|
154
|
+
2. If the index is stale AND the check finished in time, fire a
|
|
155
|
+
background `_background_rebuild()` in a detached daemon thread
|
|
156
|
+
protected by the single-flight lock.
|
|
157
|
+
3. Return immediately so the query gets the current (possibly stale)
|
|
158
|
+
index while the rebuild runs in the background.
|
|
159
|
+
|
|
160
|
+
**Reason:** MCP queries were blocking on a full ingest in the worst case,
|
|
161
|
+
causing Claude Code / OpenCode / Cursor to hang for minutes. Now the agent
|
|
162
|
+
gets an answer immediately while background sync keeps the index fresh.
|
|
163
|
+
|
|
164
|
+
### Fixed — Ollama embed connect timeout
|
|
165
|
+
|
|
166
|
+
**What:** `OllamaEmbedder` now uses split connect/read timeouts:
|
|
167
|
+
|
|
168
|
+
- `_DEFAULT_CONNECT_TIMEOUT = 5.0 s` — fail fast on wrong stack (IPv6
|
|
169
|
+
vs IPv4) or misconfigured host.
|
|
170
|
+
- `_DEFAULT_READ_TIMEOUT = 300.0 s` — Ollama's cold-load model phase
|
|
171
|
+
happens during read, not connect.
|
|
172
|
+
|
|
173
|
+
Configurable via `OLLAMA_CONNECT_TIMEOUT` and `OLLAMA_READ_TIMEOUT`
|
|
174
|
+
env vars.
|
|
175
|
+
|
|
176
|
+
**Reason:** the old single `timeout=300` param applied to connect, which
|
|
177
|
+
could hang for 300 s waiting on a misconfigured IPv6 address. A 5 s
|
|
178
|
+
connect timeout with 3 retries fails fast (~15 s worst case) instead.
|
|
179
|
+
|
|
180
|
+
### Added — Vibe plugin
|
|
181
|
+
|
|
182
|
+
**What:** new `plugins/vibe/` brings code-memory to Mistral Vibe. Vibe
|
|
183
|
+
lacks lifecycle hooks (unlike Claude Code, Cursor, OpenCode), so the
|
|
184
|
+
plugin delivers:
|
|
185
|
+
|
|
186
|
+
- **Skill** (`/code-memory`) with orientation guidance and manual command
|
|
187
|
+
runner.
|
|
188
|
+
- **MCP server** registration in `config.toml`.
|
|
189
|
+
- **OS autostart watcher** for file-edit → auto-reingest (since hooks
|
|
190
|
+
aren't available).
|
|
191
|
+
|
|
192
|
+
Install: `./plugins/vibe/install.sh` (default user scope, with flags for
|
|
193
|
+
project scope, no-mcp, no-watch, uninstall).
|
|
194
|
+
|
|
195
|
+
**Reason:** Vibe is a code-aware LLM editor with a different extension
|
|
196
|
+
model. Bundling the same code-memory integration surfaces our topology
|
|
197
|
+
queries to Vibe users without reimplementation.
|
|
198
|
+
|
|
199
|
+
### Added — Ingest state enhancements
|
|
200
|
+
|
|
201
|
+
**What:** the ingest state now stores:
|
|
202
|
+
|
|
203
|
+
- `file_count` / `symbol_count` from each full rebuild (used by the
|
|
204
|
+
health check).
|
|
205
|
+
- `file_count` and `symbol_count` are populated by the pipeline during
|
|
206
|
+
ingest and read by the health-check predicate before deciding to
|
|
207
|
+
rebuild.
|
|
208
|
+
|
|
209
|
+
**Reason:** enables the health-check guard to detect silently-failed
|
|
210
|
+
ingests.
|
|
211
|
+
|
|
11
212
|
## [0.6.0] — 2026-06-04
|
|
12
213
|
|
|
13
214
|
Release theme: **Dart joins the graph, and `this.field.method()` resolves
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const { execFile } = require("node:child_process");
|
|
10
|
+
const fs = require("node:fs");
|
|
11
|
+
const nodePath = require("node:path");
|
|
10
12
|
|
|
11
13
|
const DEFAULT_BINARY = process.env.CODE_MEMORY_BIN || "code-memory";
|
|
12
14
|
const DEFAULT_PROJECT = process.env.CODE_MEMORY_PROJECT || null;
|
|
@@ -39,6 +41,82 @@ async function detectAvailable(binary, log) {
|
|
|
39
41
|
return ok;
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Derive the lock directory used by code-memory's single_flight module.
|
|
46
|
+
* Mirrors the Python logic in sync/single_flight.py:_lock_dir().
|
|
47
|
+
* Returns null if the directory cannot be determined.
|
|
48
|
+
*/
|
|
49
|
+
function _lockDir() {
|
|
50
|
+
const override = process.env.CODE_MEMORY_LOCK_DIR;
|
|
51
|
+
if (override) return override;
|
|
52
|
+
const stateHome =
|
|
53
|
+
process.env.XDG_STATE_HOME ||
|
|
54
|
+
nodePath.join(
|
|
55
|
+
process.env.HOME || process.env.USERPROFILE || "",
|
|
56
|
+
".local",
|
|
57
|
+
"state",
|
|
58
|
+
);
|
|
59
|
+
return nodePath.join(stateHome, "code-memory", "locks");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Fast-path check: return true if a live ingest is already running for
|
|
64
|
+
* (resolvedRoot, slug). Mirrors Python single_flight._is_stale() logic:
|
|
65
|
+
* a lockfile is considered live when it exists, is not older than the TTL,
|
|
66
|
+
* and its PID is still running.
|
|
67
|
+
*
|
|
68
|
+
* This is JS-side best-effort only — the Python ingest entry point is the
|
|
69
|
+
* authoritative single-flight guard. We check here solely to avoid
|
|
70
|
+
* spawning a new process that would immediately lose the race.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} resolvedRoot - Absolute resolved path of the repo root.
|
|
73
|
+
* @param {string} slug - Project slug.
|
|
74
|
+
* @returns {boolean} true when a live ingest appears to be running.
|
|
75
|
+
*/
|
|
76
|
+
function _ingestLockLive(resolvedRoot, slug) {
|
|
77
|
+
try {
|
|
78
|
+
const lockDir = _lockDir();
|
|
79
|
+
if (!lockDir) return false;
|
|
80
|
+
|
|
81
|
+
// Replicate the Python filename derivation:
|
|
82
|
+
// name = f"{root_part[:64]}__{project_part[:32]}.lock"
|
|
83
|
+
const rootPart = resolvedRoot.replace(/[/\\]/g, "_").replace(/ /g, "_").slice(0, 64);
|
|
84
|
+
const slugPart = slug.replace(/\//g, "_").replace(/ /g, "_").slice(0, 32);
|
|
85
|
+
const lockFile = nodePath.join(lockDir, `${rootPart}__${slugPart}.lock`);
|
|
86
|
+
|
|
87
|
+
let stat;
|
|
88
|
+
try {
|
|
89
|
+
stat = fs.statSync(lockFile);
|
|
90
|
+
} catch {
|
|
91
|
+
return false; // file does not exist — no live ingest
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const ttl = parseFloat(process.env.CODE_MEMORY_REBUILD_LOCK_TTL || "3600");
|
|
95
|
+
const ageSecs = (Date.now() - stat.mtimeMs) / 1000;
|
|
96
|
+
if (ageSecs > ttl) return false; // stale by age
|
|
97
|
+
|
|
98
|
+
let pid;
|
|
99
|
+
try {
|
|
100
|
+
pid = parseInt(fs.readFileSync(lockFile, "utf8").trim(), 10);
|
|
101
|
+
} catch {
|
|
102
|
+
return false; // unreadable → treat as stale
|
|
103
|
+
}
|
|
104
|
+
if (!pid || isNaN(pid)) return false;
|
|
105
|
+
|
|
106
|
+
// Check if the PID is alive (POSIX: signal 0; Windows: tasklist not used,
|
|
107
|
+
// fall back to optimistic "assume live" to avoid a subprocess spawn).
|
|
108
|
+
try {
|
|
109
|
+
process.kill(pid, 0);
|
|
110
|
+
return true; // PID exists and is alive
|
|
111
|
+
} catch (e) {
|
|
112
|
+
if (e.code === "EPERM") return true; // exists but we lack permission
|
|
113
|
+
return false; // ESRCH — process is dead
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
return false; // any unexpected error → don't block
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
42
120
|
/**
|
|
43
121
|
* Spawn detached fire-and-forget. Parent exits immediately.
|
|
44
122
|
* stdout/stderr ignored. Used when the hook must not block.
|
|
@@ -115,6 +193,13 @@ async function createMemoryClient(opts = {}) {
|
|
|
115
193
|
|
|
116
194
|
ingestDetached({ full = false } = {}) {
|
|
117
195
|
if (!available) return false;
|
|
196
|
+
// Fast-path: skip spawn if the Python single-flight lock shows a live
|
|
197
|
+
// ingest is already running for this root. The CLI is the authoritative
|
|
198
|
+
// guard; this avoids spawning a process that would immediately lose the
|
|
199
|
+
// race and exit with code 0.
|
|
200
|
+
const resolvedCwd = nodePath.resolve(cwd);
|
|
201
|
+
const slug = project || nodePath.basename(resolvedCwd);
|
|
202
|
+
if (_ingestLockLive(resolvedCwd, slug)) return false;
|
|
118
203
|
return spawnDetached(
|
|
119
204
|
binary,
|
|
120
205
|
["ingest", cwd, "--json", ...(full ? ["--full"] : []), ...baseArgs(project)],
|
{flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/claude-code/scripts/on-post-tool.js
RENAMED
|
@@ -51,6 +51,17 @@ function pickPath(obj) {
|
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// Guard: only reingest files that live inside the project root (cwd).
|
|
55
|
+
// Resolving against cwd handles relative paths; the sep-suffix check
|
|
56
|
+
// prevents false positives like /foo/bar matching the prefix of /foo/baz.
|
|
57
|
+
const projectRoot = path.resolve(cwd);
|
|
58
|
+
const absFilePath = path.resolve(cwd, filePath);
|
|
59
|
+
if (absFilePath !== projectRoot && !absFilePath.startsWith(projectRoot + path.sep)) {
|
|
60
|
+
// File is outside the project — silently skip ingestion.
|
|
61
|
+
done();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
54
65
|
const mem = await createMemoryClient({ cwd, log: () => {} });
|
|
55
66
|
if (!mem.available) {
|
|
56
67
|
done();
|
|
@@ -25,8 +25,18 @@ const { pruneExpired } = require("./lib/state");
|
|
|
25
25
|
const mem = await createMemoryClient({ cwd, log });
|
|
26
26
|
if (mem.available) {
|
|
27
27
|
// Ensure a launchd/systemd watcher unit exists for this repo so file
|
|
28
|
-
// edits between sessions trigger reingest automatically. Idempotent
|
|
29
|
-
//
|
|
28
|
+
// edits between sessions trigger reingest automatically. Idempotent.
|
|
29
|
+
//
|
|
30
|
+
// Both calls delegate to the `code-memory` binary (DEFAULT_BINARY /
|
|
31
|
+
// CODE_MEMORY_BIN — see lib/memory.js). The CLI enforces its own
|
|
32
|
+
// safety guards at the Python entry point:
|
|
33
|
+
// • `autostart install` — rejects HOME / system roots / ephemeral dirs
|
|
34
|
+
// via sync/safety.py:assert_safe_watch_root (wired in cli.py:watch).
|
|
35
|
+
// • `ingest` — rejects HOME / filesystem roots / non-git dirs via
|
|
36
|
+
// sync/safety.py:assert_safe_ingest_root (wired in cli.py:ingest).
|
|
37
|
+
// A single-flight PID lock also prevents concurrent ingests for the
|
|
38
|
+
// same root (sync/single_flight.py).
|
|
39
|
+
// These guards are install-version-independent (PyPI, uv tool, editable).
|
|
30
40
|
mem.autostartInstallDetached();
|
|
31
41
|
mem.ingestDetached();
|
|
32
42
|
}
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const { execFile } = require("node:child_process");
|
|
10
|
+
const fs = require("node:fs");
|
|
11
|
+
const nodePath = require("node:path");
|
|
10
12
|
|
|
11
13
|
const DEFAULT_BINARY = process.env.CODE_MEMORY_BIN || "code-memory";
|
|
12
14
|
const DEFAULT_PROJECT = process.env.CODE_MEMORY_PROJECT || null;
|
|
@@ -39,6 +41,82 @@ async function detectAvailable(binary, log) {
|
|
|
39
41
|
return ok;
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Derive the lock directory used by code-memory's single_flight module.
|
|
46
|
+
* Mirrors the Python logic in sync/single_flight.py:_lock_dir().
|
|
47
|
+
* Returns null if the directory cannot be determined.
|
|
48
|
+
*/
|
|
49
|
+
function _lockDir() {
|
|
50
|
+
const override = process.env.CODE_MEMORY_LOCK_DIR;
|
|
51
|
+
if (override) return override;
|
|
52
|
+
const stateHome =
|
|
53
|
+
process.env.XDG_STATE_HOME ||
|
|
54
|
+
nodePath.join(
|
|
55
|
+
process.env.HOME || process.env.USERPROFILE || "",
|
|
56
|
+
".local",
|
|
57
|
+
"state",
|
|
58
|
+
);
|
|
59
|
+
return nodePath.join(stateHome, "code-memory", "locks");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Fast-path check: return true if a live ingest is already running for
|
|
64
|
+
* (resolvedRoot, slug). Mirrors Python single_flight._is_stale() logic:
|
|
65
|
+
* a lockfile is considered live when it exists, is not older than the TTL,
|
|
66
|
+
* and its PID is still running.
|
|
67
|
+
*
|
|
68
|
+
* This is JS-side best-effort only — the Python ingest entry point is the
|
|
69
|
+
* authoritative single-flight guard. We check here solely to avoid
|
|
70
|
+
* spawning a new process that would immediately lose the race.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} resolvedRoot - Absolute resolved path of the repo root.
|
|
73
|
+
* @param {string} slug - Project slug.
|
|
74
|
+
* @returns {boolean} true when a live ingest appears to be running.
|
|
75
|
+
*/
|
|
76
|
+
function _ingestLockLive(resolvedRoot, slug) {
|
|
77
|
+
try {
|
|
78
|
+
const lockDir = _lockDir();
|
|
79
|
+
if (!lockDir) return false;
|
|
80
|
+
|
|
81
|
+
// Replicate the Python filename derivation:
|
|
82
|
+
// name = f"{root_part[:64]}__{project_part[:32]}.lock"
|
|
83
|
+
const rootPart = resolvedRoot.replace(/[/\\]/g, "_").replace(/ /g, "_").slice(0, 64);
|
|
84
|
+
const slugPart = slug.replace(/\//g, "_").replace(/ /g, "_").slice(0, 32);
|
|
85
|
+
const lockFile = nodePath.join(lockDir, `${rootPart}__${slugPart}.lock`);
|
|
86
|
+
|
|
87
|
+
let stat;
|
|
88
|
+
try {
|
|
89
|
+
stat = fs.statSync(lockFile);
|
|
90
|
+
} catch {
|
|
91
|
+
return false; // file does not exist — no live ingest
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const ttl = parseFloat(process.env.CODE_MEMORY_REBUILD_LOCK_TTL || "3600");
|
|
95
|
+
const ageSecs = (Date.now() - stat.mtimeMs) / 1000;
|
|
96
|
+
if (ageSecs > ttl) return false; // stale by age
|
|
97
|
+
|
|
98
|
+
let pid;
|
|
99
|
+
try {
|
|
100
|
+
pid = parseInt(fs.readFileSync(lockFile, "utf8").trim(), 10);
|
|
101
|
+
} catch {
|
|
102
|
+
return false; // unreadable → treat as stale
|
|
103
|
+
}
|
|
104
|
+
if (!pid || isNaN(pid)) return false;
|
|
105
|
+
|
|
106
|
+
// Check if the PID is alive (POSIX: signal 0; Windows: tasklist not used,
|
|
107
|
+
// fall back to optimistic "assume live" to avoid a subprocess spawn).
|
|
108
|
+
try {
|
|
109
|
+
process.kill(pid, 0);
|
|
110
|
+
return true; // PID exists and is alive
|
|
111
|
+
} catch (e) {
|
|
112
|
+
if (e.code === "EPERM") return true; // exists but we lack permission
|
|
113
|
+
return false; // ESRCH — process is dead
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
return false; // any unexpected error → don't block
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
42
120
|
/**
|
|
43
121
|
* Spawn detached fire-and-forget. Parent exits immediately.
|
|
44
122
|
* stdout/stderr ignored. Used when the hook must not block.
|
|
@@ -115,6 +193,13 @@ async function createMemoryClient(opts = {}) {
|
|
|
115
193
|
|
|
116
194
|
ingestDetached({ full = false } = {}) {
|
|
117
195
|
if (!available) return false;
|
|
196
|
+
// Fast-path: skip spawn if the Python single-flight lock shows a live
|
|
197
|
+
// ingest is already running for this root. The CLI is the authoritative
|
|
198
|
+
// guard; this avoids spawning a process that would immediately lose the
|
|
199
|
+
// race and exit with code 0.
|
|
200
|
+
const resolvedCwd = nodePath.resolve(cwd);
|
|
201
|
+
const slug = project || nodePath.basename(resolvedCwd);
|
|
202
|
+
if (_ingestLockLive(resolvedCwd, slug)) return false;
|
|
118
203
|
return spawnDetached(
|
|
119
204
|
binary,
|
|
120
205
|
["ingest", cwd, "--json", ...(full ? ["--full"] : []), ...baseArgs(project)],
|
{flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/scripts/on-after-file-edit.js
RENAMED
|
@@ -32,6 +32,17 @@ const { touchResolverMarker } = require("./lib/state");
|
|
|
32
32
|
process.env.CURSOR_PROJECT_DIR ||
|
|
33
33
|
process.cwd();
|
|
34
34
|
|
|
35
|
+
// Guard: only reingest files that live inside the project root (cwd).
|
|
36
|
+
// Resolving against cwd handles relative paths; the sep-suffix check
|
|
37
|
+
// prevents false positives like /foo/bar matching the prefix of /foo/baz.
|
|
38
|
+
const projectRoot = path.resolve(cwd);
|
|
39
|
+
const absFilePath = path.resolve(cwd, filePath);
|
|
40
|
+
if (absFilePath !== projectRoot && !absFilePath.startsWith(projectRoot + path.sep)) {
|
|
41
|
+
// File is outside the project — silently skip ingestion.
|
|
42
|
+
done();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
const mem = await createMemoryClient({ cwd, log: () => {} });
|
|
36
47
|
if (!mem.available) {
|
|
37
48
|
done();
|
{flurryx_code_memory-0.6.1 → flurryx_code_memory-0.7.0}/plugins/cursor/scripts/on-session-start.js
RENAMED
|
@@ -21,6 +21,16 @@ const { pruneExpired } = require("./lib/state");
|
|
|
21
21
|
|
|
22
22
|
const mem = await createMemoryClient({ cwd, log: () => {} });
|
|
23
23
|
if (mem.available) {
|
|
24
|
+
// Both calls delegate to the `code-memory` binary (DEFAULT_BINARY /
|
|
25
|
+
// CODE_MEMORY_BIN — see lib/memory.js). The CLI enforces its own
|
|
26
|
+
// safety guards at the Python entry point:
|
|
27
|
+
// • `autostart install` — rejects HOME / system roots / ephemeral dirs
|
|
28
|
+
// via sync/safety.py:assert_safe_watch_root (wired in cli.py:watch).
|
|
29
|
+
// • `ingest` — rejects HOME / filesystem roots / non-git dirs via
|
|
30
|
+
// sync/safety.py:assert_safe_ingest_root (wired in cli.py:ingest).
|
|
31
|
+
// A single-flight PID lock also prevents concurrent ingests for the
|
|
32
|
+
// same root (sync/single_flight.py).
|
|
33
|
+
// These guards are install-version-independent (PyPI, uv tool, editable).
|
|
24
34
|
mem.ingestDetached();
|
|
25
35
|
mem.autostartInstallDetached();
|
|
26
36
|
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { execFile as execFileCb } from "node:child_process";
|
|
17
17
|
import { promisify } from "node:util";
|
|
18
|
+
import * as nodePath from "node:path";
|
|
18
19
|
|
|
19
20
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
20
21
|
|
|
@@ -324,6 +325,16 @@ const CodeMemoryPlugin: Plugin = async ({ client, directory, worktree }) => {
|
|
|
324
325
|
const path = pickToolPath(output.args) ?? pickToolPath(output.metadata);
|
|
325
326
|
if (!path) return;
|
|
326
327
|
|
|
328
|
+
// Guard: only reingest files that live inside the project root (cwd).
|
|
329
|
+
// Resolving against cwd handles relative paths; the sep-suffix check
|
|
330
|
+
// prevents false positives like /foo/bar matching the prefix of /foo/baz.
|
|
331
|
+
const projectRoot = nodePath.resolve(cwd);
|
|
332
|
+
const absPath = nodePath.resolve(cwd, path);
|
|
333
|
+
if (absPath !== projectRoot && !absPath.startsWith(projectRoot + nodePath.sep)) {
|
|
334
|
+
// File is outside the project — silently skip ingestion.
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
327
338
|
// 1. Re-ingest the single file (fast, background).
|
|
328
339
|
void memory.reingest(path);
|
|
329
340
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "flurryx-code-memory"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.0"
|
|
8
8
|
description = "Local lightweight memory layer for coding agents: FalkorDB + Qdrant + Ollama (BGE-M3) + tree-sitter"
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
dependencies = [
|
|
@@ -118,14 +118,37 @@ def ingest(
|
|
|
118
118
|
|
|
119
119
|
Default: git-aware incremental — diff prior state to HEAD.
|
|
120
120
|
"""
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
from .sync.safety import UnsafeIngestRootError, assert_safe_ingest_root
|
|
122
|
+
from .sync.single_flight import release, try_acquire
|
|
123
|
+
|
|
124
|
+
# --- Phase 3a: refuse HOME / filesystem roots / non-git dirs ----------
|
|
125
|
+
try:
|
|
126
|
+
safe_root = assert_safe_ingest_root(root)
|
|
127
|
+
except UnsafeIngestRootError as exc:
|
|
128
|
+
typer.echo(f"error: {exc}", err=True)
|
|
129
|
+
raise typer.Exit(code=2) from exc
|
|
130
|
+
|
|
131
|
+
slug = project or detect_project_slug(safe_root)
|
|
132
|
+
|
|
133
|
+
# --- Phase 3b: single-flight lock — skip if an ingest is already live --
|
|
134
|
+
if not try_acquire(safe_root, slug):
|
|
135
|
+
typer.echo(
|
|
136
|
+
f"skipped: ingest already running for project={slug!r} root={safe_root}",
|
|
137
|
+
err=True,
|
|
138
|
+
)
|
|
139
|
+
raise typer.Exit(code=0)
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
pipe = Pipeline(project=slug, skip_vectors=no_vectors)
|
|
143
|
+
stats = pipe.ingest_repo(
|
|
144
|
+
safe_root,
|
|
145
|
+
mode="full" if full else "auto",
|
|
146
|
+
since=since,
|
|
147
|
+
dry_run=dry_run,
|
|
148
|
+
)
|
|
149
|
+
finally:
|
|
150
|
+
release(safe_root, slug)
|
|
151
|
+
|
|
129
152
|
_emit(
|
|
130
153
|
{"project": slug, "dry_run": dry_run, "ingested": asdict(stats)},
|
|
131
154
|
as_json=as_json,
|
|
@@ -289,6 +312,25 @@ def reingest(
|
|
|
289
312
|
as_json: bool = JsonOpt,
|
|
290
313
|
) -> None:
|
|
291
314
|
"""Re-ingest a single file."""
|
|
315
|
+
from .config import is_inside_git_worktree
|
|
316
|
+
|
|
317
|
+
# --- Phase 4: skip files that are not inside any git worktree ---------
|
|
318
|
+
# This backstop catches edits to files under ~/.claude/..., C:\Users\...,
|
|
319
|
+
# or any other non-project path that the cwd-containment guard in the JS
|
|
320
|
+
# hook can't catch when cwd itself is not a git directory. Without this
|
|
321
|
+
# guard, detect_project_slug falls back to the raw directory name and
|
|
322
|
+
# mints parasitic Qdrant collections like "code_chunks__on-session-start-js".
|
|
323
|
+
if not is_inside_git_worktree(path.resolve().parent):
|
|
324
|
+
_emit(
|
|
325
|
+
{
|
|
326
|
+
"skipped": True,
|
|
327
|
+
"reason": "not inside a git worktree",
|
|
328
|
+
"path": str(path),
|
|
329
|
+
},
|
|
330
|
+
as_json=as_json,
|
|
331
|
+
)
|
|
332
|
+
raise typer.Exit(code=0)
|
|
333
|
+
|
|
292
334
|
slug = project or detect_project_slug(path)
|
|
293
335
|
pipe = Pipeline(project=slug)
|
|
294
336
|
ex = pipe.reingest_file(path)
|