flurryx-code-memory 0.6.2__tar.gz → 0.7.5__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 (202) hide show
  1. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/CHANGELOG.md +220 -0
  2. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/PKG-INFO +2 -2
  3. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/README.md +7 -2
  4. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/install.ps1 +43 -19
  5. {flurryx_code_memory-0.6.2/plugins/cursor → flurryx_code_memory-0.7.5/plugins/claude-code}/scripts/lib/memory.js +85 -0
  6. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/scripts/on-session-start.js +12 -2
  7. {flurryx_code_memory-0.6.2/plugins/claude-code → flurryx_code_memory-0.7.5/plugins/cursor}/scripts/lib/memory.js +85 -0
  8. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/scripts/on-session-start.js +10 -0
  9. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/pyproject.toml +2 -2
  10. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/scripts/install.ps1 +44 -20
  11. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/cli.py +50 -8
  12. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/config.py +170 -5
  13. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/embed/ollama.py +18 -1
  14. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/extractor/treesitter.py +13 -3
  15. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/graph/falkor_store.py +72 -1
  16. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/mcp_server.py +140 -8
  17. flurryx_code_memory-0.7.5/src/code_memory/orchestrator/ingest_state.py +117 -0
  18. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/orchestrator/pipeline.py +305 -26
  19. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/sync/autostart/base.py +5 -4
  20. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/sync/autostart/launchd.py +2 -2
  21. flurryx_code_memory-0.7.5/src/code_memory/sync/safety.py +182 -0
  22. flurryx_code_memory-0.7.5/src/code_memory/sync/single_flight.py +291 -0
  23. flurryx_code_memory-0.7.5/tests/test_config_ipv4_defaults.py +117 -0
  24. flurryx_code_memory-0.7.5/tests/test_embed_ollama_timeout.py +152 -0
  25. flurryx_code_memory-0.7.5/tests/test_ensure_fresh_no_blocking.py +408 -0
  26. flurryx_code_memory-0.7.5/tests/test_extractor_parser_compat.py +27 -0
  27. flurryx_code_memory-0.7.5/tests/test_graph_shadow_swap.py +304 -0
  28. flurryx_code_memory-0.7.5/tests/test_ingest_safety_and_lock.py +215 -0
  29. flurryx_code_memory-0.7.5/tests/test_ingest_state.py +131 -0
  30. flurryx_code_memory-0.7.5/tests/test_pipeline_health_check.py +192 -0
  31. flurryx_code_memory-0.7.5/tests/test_worktree_autostart_guard.py +293 -0
  32. flurryx_code_memory-0.7.5/tests/test_worktree_slug.py +225 -0
  33. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/uv.lock +7 -10
  34. flurryx_code_memory-0.6.2/src/code_memory/orchestrator/ingest_state.py +0 -71
  35. flurryx_code_memory-0.6.2/src/code_memory/sync/safety.py +0 -93
  36. flurryx_code_memory-0.6.2/tests/test_ingest_state.py +0 -62
  37. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/.claude-plugin/marketplace.json +0 -0
  38. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/.env.example +0 -0
  39. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/.gitignore +0 -0
  40. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/docker/docker-compose.yml +0 -0
  41. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/docs/BENCHMARK.md +0 -0
  42. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/docs/BENCHMARK_VS_BASELINE.json +0 -0
  43. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/docs/BENCHMARK_VS_BASELINE.md +0 -0
  44. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/docs/architecture.png +0 -0
  45. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/docs/benchmark-raw.json +0 -0
  46. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/docs/hero.png +0 -0
  47. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/install.sh +0 -0
  48. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  49. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/README.md +0 -0
  50. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/commands/code-memory.md +0 -0
  51. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/hooks/hooks.json +0 -0
  52. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/install.sh +0 -0
  53. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/scripts/lib/claim-intent.js +0 -0
  54. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/scripts/lib/claim-intent.test.js +0 -0
  55. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/scripts/lib/io.js +0 -0
  56. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/scripts/lib/state.js +0 -0
  57. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/scripts/on-post-tool.js +0 -0
  58. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/scripts/on-pre-tool.js +0 -0
  59. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/scripts/on-retrieve-seen.js +0 -0
  60. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/scripts/on-stop.js +0 -0
  61. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/scripts/on-user-prompt.js +0 -0
  62. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/scripts/resolver-debounce.js +0 -0
  63. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/claude-code/skills/code-memory/SKILL.md +0 -0
  64. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/README.md +0 -0
  65. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/hooks/hooks.json.template +0 -0
  66. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/install.sh +0 -0
  67. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/rules/code-memory.mdc +0 -0
  68. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/scripts/lib/claim-intent.js +0 -0
  69. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/scripts/lib/claim-intent.test.js +0 -0
  70. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/scripts/lib/io.js +0 -0
  71. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/scripts/lib/state.js +0 -0
  72. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/scripts/on-after-file-edit.js +0 -0
  73. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/scripts/on-before-mcp-execution.js +0 -0
  74. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/scripts/on-before-submit-prompt.js +0 -0
  75. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/scripts/on-post-tool-use.js +0 -0
  76. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/scripts/on-pre-compact.js +0 -0
  77. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/scripts/on-pre-tool-use.js +0 -0
  78. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/scripts/on-session-end.js +0 -0
  79. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/scripts/on-stop.js +0 -0
  80. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/cursor/scripts/resolver-debounce.js +0 -0
  81. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/opencode/README.md +0 -0
  82. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/opencode/install.sh +0 -0
  83. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/opencode/package-lock.json +0 -0
  84. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/opencode/package.json +0 -0
  85. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/opencode/scripts/add-mcp.py +0 -0
  86. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/opencode/scripts/install.mjs +0 -0
  87. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/opencode/scripts/uninstall.mjs +0 -0
  88. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/opencode/skills/code-memory/SKILL.md +0 -0
  89. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/opencode/src/code-memory-lib/claim-intent.test.mts +0 -0
  90. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/opencode/src/code-memory-lib/claim-intent.ts +0 -0
  91. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/opencode/src/code-memory-lib/memory-client.ts +0 -0
  92. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/opencode/src/code-memory.ts +0 -0
  93. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/opencode/tsconfig.json +0 -0
  94. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/vibe/README.md +0 -0
  95. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/vibe/install.sh +0 -0
  96. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/plugins/vibe/skills/code-memory/SKILL.md +0 -0
  97. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/scripts/benchmark.py +0 -0
  98. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/scripts/benchmark_queries.json +0 -0
  99. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/scripts/benchmark_vs_baseline.py +0 -0
  100. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/scripts/benchmark_vs_grep.sh +0 -0
  101. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/scripts/ingest.py +0 -0
  102. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/scripts/install.sh +0 -0
  103. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/__init__.py +0 -0
  104. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/claims/__init__.py +0 -0
  105. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/claims/extractor.py +0 -0
  106. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/claims/indexer.py +0 -0
  107. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/claims/resolver.py +0 -0
  108. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/claims/store.py +0 -0
  109. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/embed/__init__.py +0 -0
  110. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/embed/cache.py +0 -0
  111. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/embed/m3.py +0 -0
  112. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/embed/tei.py +0 -0
  113. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/episodic/__init__.py +0 -0
  114. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/episodic/sqlite_store.py +0 -0
  115. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/extractor/__init__.py +0 -0
  116. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/extractor/csproj.py +0 -0
  117. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/extractor/dll.py +0 -0
  118. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/extractor/gitignore.py +0 -0
  119. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/extractor/nuget.py +0 -0
  120. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/extractor/sanity.py +0 -0
  121. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/extractor/sln.py +0 -0
  122. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/graph/__init__.py +0 -0
  123. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/metrics.py +0 -0
  124. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/orchestrator/__init__.py +0 -0
  125. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/orchestrator/git_delta.py +0 -0
  126. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/orchestrator/reset.py +0 -0
  127. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/orchestrator/resolver.py +0 -0
  128. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/orchestrator/retrieve.py +0 -0
  129. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/resilience.py +0 -0
  130. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/sync/__init__.py +0 -0
  131. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/sync/autostart/__init__.py +0 -0
  132. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/sync/autostart/schtasks.py +0 -0
  133. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/sync/autostart/systemd.py +0 -0
  134. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/sync/hooks.py +0 -0
  135. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/sync/snapshot.py +0 -0
  136. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/sync/store.py +0 -0
  137. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/sync/sync.py +0 -0
  138. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/sync/watcher.py +0 -0
  139. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/updater.py +0 -0
  140. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/vector/__init__.py +0 -0
  141. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/src/code_memory/vector/qdrant_store.py +0 -0
  142. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_autostart_adapters.py +0 -0
  143. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_chunk_text.py +0 -0
  144. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_claim_extractor.py +0 -0
  145. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_claim_indexer.py +0 -0
  146. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_claim_resolver.py +0 -0
  147. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_claim_store.py +0 -0
  148. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_config_embed_dim.py +0 -0
  149. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_config_sentinel.py +0 -0
  150. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_csproj.py +0 -0
  151. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_dll_members.py +0 -0
  152. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_dll_parser.py +0 -0
  153. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_embed_backend.py +0 -0
  154. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_embed_cache.py +0 -0
  155. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_embed_m3.py +0 -0
  156. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_embed_tei.py +0 -0
  157. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_episode_dedup.py +0 -0
  158. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_episode_head_sha.py +0 -0
  159. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_extractor_csharp.py +0 -0
  160. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_extractor_dart.py +0 -0
  161. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_extractor_filters.py +0 -0
  162. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_extractor_php.py +0 -0
  163. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_extractor_python_imports.py +0 -0
  164. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_extractor_receiver_type.py +0 -0
  165. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_extractor_references.py +0 -0
  166. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_extractor_sanity.py +0 -0
  167. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_extractor_ts_abstract.py +0 -0
  168. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_extractor_ts_inject.py +0 -0
  169. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_extractor_utf8.py +0 -0
  170. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_file_containment.py +0 -0
  171. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_git_delta.py +0 -0
  172. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_graph_queries.py +0 -0
  173. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_graph_temporal.py +0 -0
  174. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_graph_vacuum_at_sha.py +0 -0
  175. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_hooks_installer.py +0 -0
  176. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_mcp_assert_claim.py +0 -0
  177. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_mcp_server_descriptions.py +0 -0
  178. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_mcp_shutdown.py +0 -0
  179. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_mcp_strict_project.py +0 -0
  180. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_metrics.py +0 -0
  181. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_nuget_resolver.py +0 -0
  182. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_overload_resolution.py +0 -0
  183. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_partial_class.py +0 -0
  184. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_pipeline_references.py +0 -0
  185. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_pipeline_temporal_wiring.py +0 -0
  186. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_qdrant_legacy_guard.py +0 -0
  187. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_razor_inject.py +0 -0
  188. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_resilience.py +0 -0
  189. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_resolver.py +0 -0
  190. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_resolver_assembly.py +0 -0
  191. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_retrieve_claims_surfacing.py +0 -0
  192. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_retrieve_rerank.py +0 -0
  193. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_sln.py +0 -0
  194. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_smoke.py +0 -0
  195. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_snapshot_e2e.py +0 -0
  196. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_snapshot_format.py +0 -0
  197. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_snapshot_store.py +0 -0
  198. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_sync_decision_tree.py +0 -0
  199. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_watch_safety.py +0 -0
  200. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_watcher_debouncer.py +0 -0
  201. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_watcher_exclude.py +0 -0
  202. {flurryx_code_memory-0.6.2 → flurryx_code_memory-0.7.5}/tests/test_watcher_ref_events.py +0 -0
@@ -8,6 +8,226 @@ 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.5] — 2026-06-21
12
+
13
+ Release theme: **Windows ingest fix — pin tree-sitter-language-pack**.
14
+
15
+ ### Fixed
16
+
17
+ **What:** pinned `tree-sitter-language-pack` to `==1.0.0` (was `>=0.7`).
18
+ Version 1.9.1 (and other releases > 1.0.0) ship a broken Windows wheel:
19
+ `get_language()` returns a `builtins.Language` that core `tree-sitter`
20
+ rejects (`TypeError: __init__() argument 1 must be tree_sitter.Language,
21
+ not builtins.Language`), surfacing during ingest as `'bytes' object is
22
+ not an instance of 'str'`. This broke `code-memory` CLI ingest on Windows
23
+ across all install environments (uv-tool, global Python, watch daemon).
24
+
25
+ **Reason:** the loose `>=0.7` floor allowed the broken 1.9.1 wheel to
26
+ resolve on fresh Windows installs; pinning to the known-good 1.0.0
27
+ restores ingest. The pin lives in wheel metadata so every install path
28
+ resolves to the working version.
29
+
30
+ ## [0.7.0] — 2026-06-12
31
+
32
+ Release theme: **Worktree resilience, atomic graph rebuilds, and fresh
33
+ indexes**. The index now survives interrupted full ingests; linked git
34
+ worktrees reuse the main repo's index instead of forcing a cold re-ingest;
35
+ and queries trigger non-blocking background rebuilds when the index drifts
36
+ from HEAD.
37
+
38
+ ### Added — Linked git worktree awareness
39
+
40
+ **What:** a new `_git_toplevel()` helper resolves linked git worktrees
41
+ to the main repo's root by running `git rev-parse --git-common-dir`
42
+ after the baseline `--show-toplevel` call. In a linked worktree, the
43
+ two paths differ; in the main worktree, they're the same. The project
44
+ slug is now derived from the main repo's directory name, so all worktrees
45
+ of the same project reuse the same Qdrant / Falkor namespace without
46
+ requiring a cold re-ingest.
47
+
48
+ **Reason:** developers working in linked worktrees (e.g. `git worktree add
49
+ ../feature-branch`) saw no code-memory functionality because the ingest
50
+ system minted a separate Qdrant collection and Falkor graph for each
51
+ worktree. A single repo with 3–5 active worktrees accumulated 3–5 cold
52
+ ingests. The main repo and each worktree now share the same index.
53
+
54
+ ### Added — Non-persistent autostart for linked worktrees
55
+
56
+ **What:** two new gates in the autostart system:
57
+
58
+ - `is_linked_git_worktree(path)` — checks whether *path* is inside a
59
+ linked worktree via git CLI.
60
+ - `is_non_persistent_watch_dir(path)` — returns True for ephemeral
61
+ session dirs OR linked worktrees; used by `ensure_autostart()` to skip
62
+ registering a persistent OS agent for directories that are temporary or
63
+ share the main repo's watcher.
64
+ - `LaunchdAdapter.prune_stale()` (run on every MCP bootstrap) removes
65
+ launchd agents whose `WorkingDirectory` is gone or is a linked worktree.
66
+
67
+ **Reason:** the prior release fixed the watcher's per-session accumulation;
68
+ this release prevents the same bleed on linked worktrees. A developer with
69
+ 2 linked worktrees no longer gets 3 persistent `code-memory watch` units.
70
+
71
+ ### Added — Shadow-graph atomic promotion (graph durability fix)
72
+
73
+ **What:** the full ingest pipeline now builds into a shadow FalkorDB
74
+ graph named `<project_graph>__shadow` and atomically promotes it only
75
+ after the rebuild succeeds:
76
+
77
+ 1. At ingest start, drop any leftover shadow from a prior interrupted
78
+ rebuild.
79
+ 2. Redirect all graph writes to the shadow FalkorStore instance.
80
+ 3. On successful completion, execute `GRAPH.DELETE <live>`, then
81
+ `GRAPH.COPY <shadow> <live>`, then `GRAPH.DELETE <shadow>`.
82
+ 4. If the copy fails, the live graph is cleared but the shadow stays
83
+ intact — the caller can retry without losing data.
84
+
85
+ **Reason:** before this, an interrupted full ingest (network loss,
86
+ Ollama timeout, Falkor down, user kills the process) left the live
87
+ graph empty so subsequent `callers` / `definitions` / `callees` queries
88
+ returned nothing until a manual full re-ingest. The graph now survives
89
+ interruption — the shadow is cleaned up on the next rebuild attempt.
90
+
91
+ ### Added — Health-check guard for stale rebuilds
92
+
93
+ **What:** the ingest state now records `file_count` and `symbol_count`
94
+ from each successful full rebuild. Before starting an incremental ingest,
95
+ `_health_check_ok()` compares the current ingestable file count against
96
+ the stored baseline; if it grew more than 20% and the graph symbol count
97
+ is suspiciously low (below a ratio threshold), a full rebuild is forced
98
+ with a diagnostic message to stderr.
99
+
100
+ Config:
101
+ - `CODE_MEMORY_INGEST_HEALTH_CHECK_ENABLED` (default `true`)
102
+ - `CODE_MEMORY_INGEST_HEALTH_CHECK_MIN_RATIO` (default `0.3` = expect ≥30%
103
+ of file count as symbol count)
104
+
105
+ **Reason:** a transient FalkorDB outage or an incomplete prior ingest
106
+ could leave the graph permanently empty while incremental ingests reported
107
+ success. The health check detects this silently-failed state and forces a
108
+ rebuild, with no user intervention needed.
109
+
110
+ ### Added — Single-flight ingest lock
111
+
112
+ **What:** new `src/code_memory/sync/single_flight.py` module provides
113
+ in-process (`asyncio.Lock`) + cross-process (PID file) guards to prevent
114
+ concurrent full ingests for the same (root, project) pair.
115
+
116
+ - `try_acquire(root, project)` — returns True if no rebuild is running,
117
+ False if the slot is taken.
118
+ - `release(root, project)` — releases the slot unconditionally.
119
+ - Stale PID files (dead process or age > 30 min) are silently removed.
120
+
121
+ The Claude Code and Cursor plugins' `on-session-start.js` fast-path-skip a
122
+ spawn when a live ingest is detected, preventing thundering-herd `code-memory
123
+ ingest` calls on boot.
124
+
125
+ **Reason:** on slow machines or large repos, overlapping `code-memory ingest`
126
+ calls could queue up and queue up (especially if the embedder is I/O-bound),
127
+ causing a session to take 5+ minutes to boot. The lock ensures at most one
128
+ rebuild runs; fast-path skips spare the overhead.
129
+
130
+ ### Added — Ingest safety guards
131
+
132
+ **What:** new `assert_safe_ingest_root()` function in `sync/safety.py`
133
+ refuses to ingest:
134
+
135
+ 1. System/HOME roots (same set as the watcher guard: HOME, /, /tmp,
136
+ /var, /etc, /usr, /System, /Library, /opt, /Applications, C:/, etc.)
137
+ 2. Non-git directories (checked via `is_inside_git_worktree()`).
138
+
139
+ Bypass via `CODE_MEMORY_UNSAFE_INGEST=1` env var (env-only, not a CLI
140
+ flag, to prevent accidental use).
141
+
142
+ **Reason:** `code-memory ingest ~` could walk every IDE cache, checkout,
143
+ and node_modules on disk. `ingest` is more dangerous than `watch` because
144
+ it stores results; a non-git directory ingest mints an arbitrary project
145
+ slug. The guard is invoked by the CLI `ingest` entry point and by hooks.
146
+
147
+ ### Added — IPv4-default service URLs
148
+
149
+ **What:** the default `OLLAMA_URL`, `QDRANT_URL`, and `FALKOR_URL` now
150
+ use `127.0.0.1` instead of `localhost`. This works around a Windows
151
+ quirk where `localhost` may resolve to `::1` (IPv6) and hang on socket
152
+ connect.
153
+
154
+ Config example (all in `.code-memoryrc` or env):
155
+ ```
156
+ OLLAMA_URL=http://127.0.0.1:11434
157
+ QDRANT_URL=http://127.0.0.1:6333
158
+ FALKOR_URL=redis://127.0.0.1:6379
159
+ ```
160
+
161
+ **Reason:** on some Windows setups, the TCP stack prefers IPv6 and
162
+ binds localhost to `::1`, while the service listens on `127.0.0.1`.
163
+ Callers then hang waiting for a timeout (seconds to minutes). Using
164
+ the explicit IPv4 address is more reliable.
165
+
166
+ ### Added — Non-blocking `_ensure_fresh` for MCP queries
167
+
168
+ **What:** the pre-query guard `_ensure_fresh()` no longer blocks on a
169
+ sync. Instead:
170
+
171
+ 1. Spawn a quick freshness check (`_is_index_stale()`) in a bounded
172
+ daemon thread (`_FRESHNESS_PROBE_TIMEOUT`, default 2.0 s).
173
+ 2. If the index is stale AND the check finished in time, fire a
174
+ background `_background_rebuild()` in a detached daemon thread
175
+ protected by the single-flight lock.
176
+ 3. Return immediately so the query gets the current (possibly stale)
177
+ index while the rebuild runs in the background.
178
+
179
+ **Reason:** MCP queries were blocking on a full ingest in the worst case,
180
+ causing Claude Code / OpenCode / Cursor to hang for minutes. Now the agent
181
+ gets an answer immediately while background sync keeps the index fresh.
182
+
183
+ ### Fixed — Ollama embed connect timeout
184
+
185
+ **What:** `OllamaEmbedder` now uses split connect/read timeouts:
186
+
187
+ - `_DEFAULT_CONNECT_TIMEOUT = 5.0 s` — fail fast on wrong stack (IPv6
188
+ vs IPv4) or misconfigured host.
189
+ - `_DEFAULT_READ_TIMEOUT = 300.0 s` — Ollama's cold-load model phase
190
+ happens during read, not connect.
191
+
192
+ Configurable via `OLLAMA_CONNECT_TIMEOUT` and `OLLAMA_READ_TIMEOUT`
193
+ env vars.
194
+
195
+ **Reason:** the old single `timeout=300` param applied to connect, which
196
+ could hang for 300 s waiting on a misconfigured IPv6 address. A 5 s
197
+ connect timeout with 3 retries fails fast (~15 s worst case) instead.
198
+
199
+ ### Added — Vibe plugin
200
+
201
+ **What:** new `plugins/vibe/` brings code-memory to Mistral Vibe. Vibe
202
+ lacks lifecycle hooks (unlike Claude Code, Cursor, OpenCode), so the
203
+ plugin delivers:
204
+
205
+ - **Skill** (`/code-memory`) with orientation guidance and manual command
206
+ runner.
207
+ - **MCP server** registration in `config.toml`.
208
+ - **OS autostart watcher** for file-edit → auto-reingest (since hooks
209
+ aren't available).
210
+
211
+ Install: `./plugins/vibe/install.sh` (default user scope, with flags for
212
+ project scope, no-mcp, no-watch, uninstall).
213
+
214
+ **Reason:** Vibe is a code-aware LLM editor with a different extension
215
+ model. Bundling the same code-memory integration surfaces our topology
216
+ queries to Vibe users without reimplementation.
217
+
218
+ ### Added — Ingest state enhancements
219
+
220
+ **What:** the ingest state now stores:
221
+
222
+ - `file_count` / `symbol_count` from each full rebuild (used by the
223
+ health check).
224
+ - `file_count` and `symbol_count` are populated by the pipeline during
225
+ ingest and read by the health-check predicate before deciding to
226
+ rebuild.
227
+
228
+ **Reason:** enables the health-check guard to detect silently-failed
229
+ ingests.
230
+
11
231
  ## [0.6.0] — 2026-06-04
12
232
 
13
233
  Release theme: **Dart joins the graph, and `this.field.method()` resolves
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flurryx-code-memory
3
- Version: 0.6.2
3
+ Version: 0.7.5
4
4
  Summary: Local lightweight memory layer for coding agents: FalkorDB + Qdrant + Ollama (BGE-M3) + tree-sitter
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: anyio>=4.4
@@ -10,7 +10,7 @@ Requires-Dist: mcp>=1.0
10
10
  Requires-Dist: pydantic>=2.8
11
11
  Requires-Dist: qdrant-client>=1.12
12
12
  Requires-Dist: rich>=13.7
13
- Requires-Dist: tree-sitter-language-pack>=0.7
13
+ Requires-Dist: tree-sitter-language-pack==1.0.0
14
14
  Requires-Dist: tree-sitter>=0.23
15
15
  Requires-Dist: typer>=0.12
16
16
  Requires-Dist: watchdog>=4.0
@@ -99,9 +99,14 @@ curl -fsSL https://raw.githubusercontent.com/fmflurry/code-memory/main/install.s
99
99
  **🪟 Windows**
100
100
 
101
101
  ```powershell
102
- irm https://raw.githubusercontent.com/fmflurry/code-memory/main/install.ps1 | iex
102
+ irm https://raw.githubusercontent.com/fmflurry/code-memory/main/install.ps1 -Headers @{ 'Cache-Control' = 'no-cache' } | iex
103
103
  ```
104
104
 
105
+ If PowerShell reports a parser error at `install.ps1:392` showing the old
106
+ here-string terminator piped to `Write-Host`, you are running a stale
107
+ release/checkout. Update to `main`, or delete and re-download the installer
108
+ from the `main` URL above before running it.
109
+
105
110
  The installer fetches the CLI, drops `docker-compose.yml` into `~/.code-memory/`, starts **FalkorDB** + **Qdrant**, pulls **`bge-m3`**, and wires the **Claude Code** + **OpenCode** plugins. Idempotent — safe to re-run.
106
111
 
107
112
  ### ✅ Verify
@@ -152,7 +157,7 @@ curl -fsSL https://raw.githubusercontent.com/fmflurry/code-memory/main/install.s
152
157
  **Windows** — `iex` doesn't accept args, so download then run:
153
158
 
154
159
  ```powershell
155
- iwr https://raw.githubusercontent.com/fmflurry/code-memory/main/install.ps1 -OutFile install.ps1
160
+ iwr https://raw.githubusercontent.com/fmflurry/code-memory/main/install.ps1 -Headers @{ 'Cache-Control' = 'no-cache' } -OutFile install.ps1
156
161
  ./install.ps1 -NoDocker -NoOllama -NoClaude -NoOpencode -NoMcp
157
162
  ```
158
163
 
@@ -68,6 +68,29 @@ param(
68
68
  )
69
69
 
70
70
  $ErrorActionPreference = 'Stop'
71
+ # PowerShell 7.3+ promotes native-command stderr to a terminating error when
72
+ # $ErrorActionPreference='Stop'. Docker CLI writes benign WARNINGs (e.g. the
73
+ # credential-plugin naming check) to stderr; we gate on $LASTEXITCODE instead,
74
+ # so do not let stderr alone abort the script.
75
+ $PSNativeCommandUseErrorActionPreference = $false
76
+
77
+ # Run a native command whose benign stderr (e.g. Docker CLI credential-plugin
78
+ # warnings) must NOT abort the script. Windows PowerShell 5.1 turns redirected
79
+ # native stderr into terminating NativeCommandError records under
80
+ # $ErrorActionPreference='Stop'; relaxing EAP for the call fixes it on 5.1 and
81
+ # 7+. Callers gate on $LASTEXITCODE. Returns nothing; sets $LASTEXITCODE.
82
+ function Invoke-NativeQuiet {
83
+ param([Parameter(Mandatory)][scriptblock] $Command)
84
+ $prevEAP = $ErrorActionPreference
85
+ $ErrorActionPreference = 'SilentlyContinue'
86
+ try { & $Command 2>&1 | Out-Null } finally { $ErrorActionPreference = $prevEAP }
87
+ }
88
+ function Invoke-NativeVisible {
89
+ param([Parameter(Mandatory)][scriptblock] $Command)
90
+ $prevEAP = $ErrorActionPreference
91
+ $ErrorActionPreference = 'Continue'
92
+ try { & $Command 2>&1 | ForEach-Object { Write-Host $_ } } finally { $ErrorActionPreference = $prevEAP }
93
+ }
71
94
 
72
95
  $RepoUrl = if ($env:CODEMEMORY_REPO_URL) { $env:CODEMEMORY_REPO_URL } else { 'https://github.com/fmflurry/code-memory' }
73
96
  $RawUrl = if ($env:CODEMEMORY_RAW_URL) { $env:CODEMEMORY_RAW_URL } else { 'https://raw.githubusercontent.com/fmflurry/code-memory/main' }
@@ -185,7 +208,7 @@ if ($doDocker) {
185
208
  Step "Starting FalkorDB + Qdrant"
186
209
  if (Wait-ForCmd 'docker' 'Docker Desktop' 'https://www.docker.com/products/docker-desktop') {
187
210
  # ensure daemon up
188
- & docker info *>$null
211
+ Invoke-NativeQuiet { docker info }
189
212
  if ($LASTEXITCODE -ne 0) {
190
213
  Warn "Docker CLI present but daemon not running. Start Docker Desktop."
191
214
  if (Test-Interactive) {
@@ -195,7 +218,7 @@ if ($doDocker) {
195
218
  }
196
219
  }
197
220
  if ($doDocker) {
198
- & docker compose -f (Join-Path $HomeDir 'docker/docker-compose.yml') --project-directory $HomeDir up -d
221
+ Invoke-NativeVisible { docker compose -f (Join-Path $HomeDir 'docker/docker-compose.yml') --project-directory $HomeDir up -d }
199
222
  if ($LASTEXITCODE -ne 0) {
200
223
  Warn "docker compose up failed"
201
224
  } else {
@@ -350,20 +373,21 @@ if ($doOpencode) {
350
373
 
351
374
  # ---------- done ----------
352
375
  Step "Done"
353
- @"
354
-
355
- Side files: $HomeDir\
356
- CLI: $(if ($cliPath) { $cliPath.Source } else { 'code-memory (not on PATH)' })
357
-
358
- Ingest a repo:
359
- code-memory ingest C:\path\to\repo
360
-
361
- Query:
362
- code-memory retrieve "where is the auth middleware?"
363
-
364
- Browse:
365
- FalkorDB http://localhost:3000
366
- Qdrant http://localhost:6333/dashboard
367
-
368
- Edit defaults: $HomeDir\.env
369
- "@ | Write-Host
376
+ $doneLines = @(
377
+ ''
378
+ " Side files: $HomeDir\"
379
+ " CLI: $(if ($cliPath) { $cliPath.Source } else { 'code-memory (not on PATH)' })"
380
+ ''
381
+ ' Ingest a repo:'
382
+ ' code-memory ingest C:\path\to\repo'
383
+ ''
384
+ ' Query:'
385
+ ' code-memory retrieve "where is the auth middleware?"'
386
+ ''
387
+ ' Browse:'
388
+ ' FalkorDB http://localhost:3000'
389
+ ' Qdrant http://localhost:6333/dashboard'
390
+ ''
391
+ " Edit defaults: $HomeDir\.env"
392
+ )
393
+ Write-Host ($doneLines -join [Environment]::NewLine)
@@ -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)],
@@ -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
- // safety guard inside the CLI refuses home/root / non-VCS dirs.
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)],
@@ -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
  }
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "flurryx-code-memory"
7
- version = "0.6.2"
7
+ version = "0.7.5"
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 = [
@@ -12,7 +12,7 @@ dependencies = [
12
12
  "qdrant-client>=1.12",
13
13
  "falkordb>=1.0.10",
14
14
  "tree-sitter>=0.23",
15
- "tree-sitter-language-pack>=0.7",
15
+ "tree-sitter-language-pack==1.0.0",
16
16
  "pydantic>=2.8",
17
17
  "typer>=0.12",
18
18
  "rich>=13.7",