flurryx-code-memory 0.7.5__tar.gz → 0.7.6__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.7.5 → flurryx_code_memory-0.7.6}/CHANGELOG.md +38 -0
  2. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/PKG-INFO +2 -2
  3. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/pyproject.toml +2 -2
  4. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/scripts/install.ps1 +98 -14
  5. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/scripts/install.sh +98 -14
  6. flurryx_code_memory-0.7.6/src/code_memory/_console.py +32 -0
  7. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/cli.py +14 -1
  8. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/mcp_server.py +3 -0
  9. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/updater.py +146 -1
  10. flurryx_code_memory-0.7.6/tests/test_console_encoding.py +63 -0
  11. flurryx_code_memory-0.7.6/tests/test_extras_offer.py +327 -0
  12. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/uv.lock +10 -7
  13. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/.claude-plugin/marketplace.json +0 -0
  14. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/.env.example +0 -0
  15. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/.gitignore +0 -0
  16. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/README.md +0 -0
  17. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/docker/docker-compose.yml +0 -0
  18. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/docs/BENCHMARK.md +0 -0
  19. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/docs/BENCHMARK_VS_BASELINE.json +0 -0
  20. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/docs/BENCHMARK_VS_BASELINE.md +0 -0
  21. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/docs/architecture.png +0 -0
  22. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/docs/benchmark-raw.json +0 -0
  23. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/docs/hero.png +0 -0
  24. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/install.ps1 +0 -0
  25. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/install.sh +0 -0
  26. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  27. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/README.md +0 -0
  28. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/commands/code-memory.md +0 -0
  29. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/hooks/hooks.json +0 -0
  30. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/install.sh +0 -0
  31. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/scripts/lib/claim-intent.js +0 -0
  32. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/scripts/lib/claim-intent.test.js +0 -0
  33. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/scripts/lib/io.js +0 -0
  34. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/scripts/lib/memory.js +0 -0
  35. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/scripts/lib/state.js +0 -0
  36. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/scripts/on-post-tool.js +0 -0
  37. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/scripts/on-pre-tool.js +0 -0
  38. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/scripts/on-retrieve-seen.js +0 -0
  39. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/scripts/on-session-start.js +0 -0
  40. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/scripts/on-stop.js +0 -0
  41. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/scripts/on-user-prompt.js +0 -0
  42. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/scripts/resolver-debounce.js +0 -0
  43. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/claude-code/skills/code-memory/SKILL.md +0 -0
  44. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/README.md +0 -0
  45. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/hooks/hooks.json.template +0 -0
  46. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/install.sh +0 -0
  47. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/rules/code-memory.mdc +0 -0
  48. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/scripts/lib/claim-intent.js +0 -0
  49. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/scripts/lib/claim-intent.test.js +0 -0
  50. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/scripts/lib/io.js +0 -0
  51. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/scripts/lib/memory.js +0 -0
  52. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/scripts/lib/state.js +0 -0
  53. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/scripts/on-after-file-edit.js +0 -0
  54. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/scripts/on-before-mcp-execution.js +0 -0
  55. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/scripts/on-before-submit-prompt.js +0 -0
  56. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/scripts/on-post-tool-use.js +0 -0
  57. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/scripts/on-pre-compact.js +0 -0
  58. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/scripts/on-pre-tool-use.js +0 -0
  59. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/scripts/on-session-end.js +0 -0
  60. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/scripts/on-session-start.js +0 -0
  61. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/scripts/on-stop.js +0 -0
  62. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/cursor/scripts/resolver-debounce.js +0 -0
  63. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/opencode/README.md +0 -0
  64. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/opencode/install.sh +0 -0
  65. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/opencode/package-lock.json +0 -0
  66. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/opencode/package.json +0 -0
  67. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/opencode/scripts/add-mcp.py +0 -0
  68. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/opencode/scripts/install.mjs +0 -0
  69. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/opencode/scripts/uninstall.mjs +0 -0
  70. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/opencode/skills/code-memory/SKILL.md +0 -0
  71. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/opencode/src/code-memory-lib/claim-intent.test.mts +0 -0
  72. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/opencode/src/code-memory-lib/claim-intent.ts +0 -0
  73. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/opencode/src/code-memory-lib/memory-client.ts +0 -0
  74. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/opencode/src/code-memory.ts +0 -0
  75. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/opencode/tsconfig.json +0 -0
  76. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/vibe/README.md +0 -0
  77. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/vibe/install.sh +0 -0
  78. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/plugins/vibe/skills/code-memory/SKILL.md +0 -0
  79. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/scripts/benchmark.py +0 -0
  80. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/scripts/benchmark_queries.json +0 -0
  81. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/scripts/benchmark_vs_baseline.py +0 -0
  82. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/scripts/benchmark_vs_grep.sh +0 -0
  83. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/scripts/ingest.py +0 -0
  84. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/__init__.py +0 -0
  85. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/claims/__init__.py +0 -0
  86. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/claims/extractor.py +0 -0
  87. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/claims/indexer.py +0 -0
  88. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/claims/resolver.py +0 -0
  89. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/claims/store.py +0 -0
  90. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/config.py +0 -0
  91. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/embed/__init__.py +0 -0
  92. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/embed/cache.py +0 -0
  93. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/embed/m3.py +0 -0
  94. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/embed/ollama.py +0 -0
  95. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/embed/tei.py +0 -0
  96. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/episodic/__init__.py +0 -0
  97. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/episodic/sqlite_store.py +0 -0
  98. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/extractor/__init__.py +0 -0
  99. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/extractor/csproj.py +0 -0
  100. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/extractor/dll.py +0 -0
  101. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/extractor/gitignore.py +0 -0
  102. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/extractor/nuget.py +0 -0
  103. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/extractor/sanity.py +0 -0
  104. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/extractor/sln.py +0 -0
  105. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/extractor/treesitter.py +0 -0
  106. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/graph/__init__.py +0 -0
  107. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/graph/falkor_store.py +0 -0
  108. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/metrics.py +0 -0
  109. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/orchestrator/__init__.py +0 -0
  110. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/orchestrator/git_delta.py +0 -0
  111. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/orchestrator/ingest_state.py +0 -0
  112. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/orchestrator/pipeline.py +0 -0
  113. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/orchestrator/reset.py +0 -0
  114. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/orchestrator/resolver.py +0 -0
  115. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/orchestrator/retrieve.py +0 -0
  116. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/resilience.py +0 -0
  117. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/sync/__init__.py +0 -0
  118. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/sync/autostart/__init__.py +0 -0
  119. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/sync/autostart/base.py +0 -0
  120. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/sync/autostart/launchd.py +0 -0
  121. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/sync/autostart/schtasks.py +0 -0
  122. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/sync/autostart/systemd.py +0 -0
  123. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/sync/hooks.py +0 -0
  124. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/sync/safety.py +0 -0
  125. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/sync/single_flight.py +0 -0
  126. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/sync/snapshot.py +0 -0
  127. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/sync/store.py +0 -0
  128. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/sync/sync.py +0 -0
  129. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/sync/watcher.py +0 -0
  130. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/vector/__init__.py +0 -0
  131. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/src/code_memory/vector/qdrant_store.py +0 -0
  132. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_autostart_adapters.py +0 -0
  133. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_chunk_text.py +0 -0
  134. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_claim_extractor.py +0 -0
  135. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_claim_indexer.py +0 -0
  136. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_claim_resolver.py +0 -0
  137. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_claim_store.py +0 -0
  138. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_config_embed_dim.py +0 -0
  139. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_config_ipv4_defaults.py +0 -0
  140. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_config_sentinel.py +0 -0
  141. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_csproj.py +0 -0
  142. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_dll_members.py +0 -0
  143. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_dll_parser.py +0 -0
  144. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_embed_backend.py +0 -0
  145. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_embed_cache.py +0 -0
  146. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_embed_m3.py +0 -0
  147. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_embed_ollama_timeout.py +0 -0
  148. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_embed_tei.py +0 -0
  149. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_ensure_fresh_no_blocking.py +0 -0
  150. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_episode_dedup.py +0 -0
  151. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_episode_head_sha.py +0 -0
  152. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_extractor_csharp.py +0 -0
  153. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_extractor_dart.py +0 -0
  154. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_extractor_filters.py +0 -0
  155. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_extractor_parser_compat.py +0 -0
  156. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_extractor_php.py +0 -0
  157. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_extractor_python_imports.py +0 -0
  158. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_extractor_receiver_type.py +0 -0
  159. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_extractor_references.py +0 -0
  160. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_extractor_sanity.py +0 -0
  161. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_extractor_ts_abstract.py +0 -0
  162. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_extractor_ts_inject.py +0 -0
  163. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_extractor_utf8.py +0 -0
  164. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_file_containment.py +0 -0
  165. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_git_delta.py +0 -0
  166. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_graph_queries.py +0 -0
  167. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_graph_shadow_swap.py +0 -0
  168. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_graph_temporal.py +0 -0
  169. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_graph_vacuum_at_sha.py +0 -0
  170. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_hooks_installer.py +0 -0
  171. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_ingest_safety_and_lock.py +0 -0
  172. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_ingest_state.py +0 -0
  173. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_mcp_assert_claim.py +0 -0
  174. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_mcp_server_descriptions.py +0 -0
  175. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_mcp_shutdown.py +0 -0
  176. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_mcp_strict_project.py +0 -0
  177. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_metrics.py +0 -0
  178. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_nuget_resolver.py +0 -0
  179. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_overload_resolution.py +0 -0
  180. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_partial_class.py +0 -0
  181. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_pipeline_health_check.py +0 -0
  182. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_pipeline_references.py +0 -0
  183. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_pipeline_temporal_wiring.py +0 -0
  184. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_qdrant_legacy_guard.py +0 -0
  185. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_razor_inject.py +0 -0
  186. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_resilience.py +0 -0
  187. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_resolver.py +0 -0
  188. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_resolver_assembly.py +0 -0
  189. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_retrieve_claims_surfacing.py +0 -0
  190. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_retrieve_rerank.py +0 -0
  191. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_sln.py +0 -0
  192. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_smoke.py +0 -0
  193. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_snapshot_e2e.py +0 -0
  194. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_snapshot_format.py +0 -0
  195. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_snapshot_store.py +0 -0
  196. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_sync_decision_tree.py +0 -0
  197. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_watch_safety.py +0 -0
  198. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_watcher_debouncer.py +0 -0
  199. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_watcher_exclude.py +0 -0
  200. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_watcher_ref_events.py +0 -0
  201. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_worktree_autostart_guard.py +0 -0
  202. {flurryx_code_memory-0.7.5 → flurryx_code_memory-0.7.6}/tests/test_worktree_slug.py +0 -0
@@ -8,10 +8,48 @@ 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.6] — 2026-06-21
12
+
13
+ Release theme: **Correct tree-sitter pin + Windows console & extras fixes**.
14
+
15
+ ### Fixed
16
+
17
+ **tree-sitter-language-pack pinned to `==1.8.1`** (was `==1.0.0` in the broken 0.7.5).
18
+ Version 1.0.0 has no bundled language grammars (it uses a download model), so
19
+ `get_language('csharp')` raised `LanguageNotFoundError` and extraction produced
20
+ zero symbols on every platform. Version 1.9.1 is also broken (raises `'bytes'
21
+ object is not an instance of 'str'` even on macOS — a 1.9.x regression). 1.8.1
22
+ is the last known-good release with bundled grammars; the original Windows
23
+ failure was caused by the loose `>=0.7` floor letting fresh installs float to
24
+ 1.9.1.
25
+
26
+ Reason: stop floating into broken 1.9.x and restore symbol extraction everywhere.
27
+
28
+ **Windows console crash in `code-memory update`**: stdout/stderr are now forced
29
+ to UTF-8 (`errors="replace"`) at the CLI and MCP entry points, so Unicode
30
+ status glyphs (→, •, ✓) no longer raise `UnicodeEncodeError` on cp1252
31
+ PowerShell consoles.
32
+
33
+ Reason: the updater crashed before doing any work on non-UTF-8 Windows terminals.
34
+
35
+ ### Added
36
+
37
+ **Optional-extras selection prompt** in `code-memory update` and the install
38
+ one-liner (`install.sh` / `install.ps1`): users are now asked which optional
39
+ extras to enable (`dotnet` for .NET assembly indexing, `hybrid` for in-process
40
+ BGE-M3). TTY-gated with a `CODEMEMORY_EXTRAS` env override and an `--extras`
41
+ flag; non-interactive runs install nothing new. Also fixes the bash installer
42
+ gating the prompt on `[ -t 0 ]`, which was always false under `curl | bash` so
43
+ the prompt never appeared.
44
+
45
+ Reason: previously there was no way to opt into .NET indexing during update/install.
46
+
11
47
  ## [0.7.5] — 2026-06-21
12
48
 
13
49
  Release theme: **Windows ingest fix — pin tree-sitter-language-pack**.
14
50
 
51
+ **⚠️ Superseded by 0.7.6 — this release pinned tree-sitter-language-pack==1.0.0 which broke extraction (no bundled grammars). Do not use.**
52
+
15
53
  ### Fixed
16
54
 
17
55
  **What:** pinned `tree-sitter-language-pack` to `==1.0.0` (was `>=0.7`).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flurryx-code-memory
3
- Version: 0.7.5
3
+ Version: 0.7.6
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==1.0.0
13
+ Requires-Dist: tree-sitter-language-pack==1.8.1
14
14
  Requires-Dist: tree-sitter>=0.23
15
15
  Requires-Dist: typer>=0.12
16
16
  Requires-Dist: watchdog>=4.0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "flurryx-code-memory"
7
- version = "0.7.5"
7
+ version = "0.7.6"
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==1.0.0",
15
+ "tree-sitter-language-pack==1.8.1",
16
16
  "pydantic>=2.8",
17
17
  "typer>=0.12",
18
18
  "rich>=13.7",
@@ -25,6 +25,15 @@
25
25
  metadata indexing (Assembly + Type graph nodes from .dll referenced
26
26
  via .csproj). Skip if no .NET source in repos you ingest.
27
27
 
28
+ .PARAMETER WithHybrid
29
+ Install the [hybrid] extra (FlagEmbedding, ~2 GB torch). Enables in-process
30
+ BGE-M3 dense+sparse hybrid reranking. Heavy — skip unless you need it.
31
+
32
+ .PARAMETER Extras
33
+ Comma list of optional extras to install (e.g. 'dotnet,hybrid'), or 'none'
34
+ to bypass the interactive prompt. Overrides -WithDotnet/-WithHybrid and the
35
+ CODEMEMORY_EXTRAS environment variable.
36
+
28
37
  .PARAMETER Plugins
29
38
  Comma-separated whitelist of harness plugins to install:
30
39
  'opencode', 'claudecode', 'cursor', 'all', or 'none'. Bypasses the
@@ -37,14 +46,22 @@
37
46
  ./.cursor.
38
47
 
39
48
  .PARAMETER ExtrasInteractive
40
- Set to `$false` to skip the interactive extras prompt. The -WithDotnet
41
- switch still applies.
49
+ Set to `$false` to skip the interactive extras prompt entirely. Overridden
50
+ by -Extras, -WithDotnet, -WithHybrid, and the CODEMEMORY_EXTRAS env var.
42
51
 
43
52
  .EXAMPLE
44
53
  ./scripts/install.ps1
45
54
  ./scripts/install.ps1 -NoOllama
46
55
  ./scripts/install.ps1 -WithDotnet -Plugins all
56
+ ./scripts/install.ps1 -WithHybrid
57
+ ./scripts/install.ps1 -Extras dotnet,hybrid
58
+ ./scripts/install.ps1 -Extras none
47
59
  ./scripts/install.ps1 -Plugins claudecode -PluginsScope project
60
+
61
+ .NOTES
62
+ Environment variables:
63
+ CODEMEMORY_EXTRAS=dotnet,hybrid same as -Extras (overrides interactive prompt)
64
+ CODEMEMORY_EXTRAS=none skip extras entirely
48
65
  #>
49
66
 
50
67
  [CmdletBinding()]
@@ -54,6 +71,8 @@ param(
54
71
  [switch]$NoTests,
55
72
  [switch]$NoMcp,
56
73
  [switch]$WithDotnet,
74
+ [switch]$WithHybrid,
75
+ [string]$Extras = '',
57
76
  [string]$Plugins = '',
58
77
  [ValidateSet('global', 'project')]
59
78
  [string]$PluginsScope = 'global',
@@ -195,24 +214,89 @@ Ok "pip upgraded"
195
214
 
196
215
  # ---------- 3. package install ----------
197
216
  # Resolve optional extras up front so we do one pip resolve.
198
- if ($ExtrasInteractive -and -not $WithDotnet) {
199
- $isTty = [Environment]::UserInteractive -and [Console]::IsInputRedirected -eq $false
200
- if ($isTty) {
201
- Step "Optional extras"
202
- Write-Host " [dotnet] .NET DLL metadata indexing (dnfile, ~200 KB)."
203
- Write-Host " Skip if no .csproj / .NET source in repos you ingest."
204
- Write-Host ""
205
- if (Prompt-YesNo "Install [dotnet] extra?" $false) { $WithDotnet = $true }
217
+ #
218
+ # Optional extras keep in sync with EXTRAS registry in updater.py.
219
+ # dotnet: .NET assembly metadata indexing (dnfile, ~200 KB).
220
+ # hybrid: in-process BGE-M3 dense+sparse via FlagEmbedding (~2 GB torch).
221
+ #
222
+ # Priority: -Extras / -WithDotnet / -WithHybrid (CLI) >
223
+ # CODEMEMORY_EXTRAS env var > interactive prompt > skip.
224
+ #
225
+ # $isTty: true when running interactively (not under irm|iex which redirects stdin).
226
+ $isTty = [Environment]::UserInteractive -and -not [Console]::IsInputRedirected
227
+
228
+ $installDotnet = [bool]$WithDotnet
229
+ $installHybrid = [bool]$WithHybrid
230
+
231
+ # Fold legacy switches into -Extras when -Extras is not already set.
232
+ if ([string]::IsNullOrWhiteSpace($Extras)) {
233
+ $legacyParts = @()
234
+ if ($WithDotnet) { $legacyParts += 'dotnet' }
235
+ if ($WithHybrid) { $legacyParts += 'hybrid' }
236
+ if ($legacyParts.Count -gt 0) { $Extras = $legacyParts -join ',' }
237
+ }
238
+
239
+ # Determine effective source.
240
+ $effectiveExtras = $Extras
241
+ if ([string]::IsNullOrWhiteSpace($effectiveExtras)) {
242
+ $envExtras = [System.Environment]::GetEnvironmentVariable('CODEMEMORY_EXTRAS')
243
+ if (-not [string]::IsNullOrWhiteSpace($envExtras)) {
244
+ $effectiveExtras = $envExtras.Trim()
206
245
  }
207
246
  }
208
247
 
209
- $extras = @('dev')
210
- if ($WithDotnet) { $extras += 'dotnet' }
211
- $extrasJoined = ($extras -join ',')
248
+ if (-not [string]::IsNullOrWhiteSpace($effectiveExtras)) {
249
+ # Flag/env path parse comma list.
250
+ if ($effectiveExtras -ieq 'none') {
251
+ $installDotnet = $false
252
+ $installHybrid = $false
253
+ } else {
254
+ foreach ($ep in $effectiveExtras.Split(',')) {
255
+ switch ($ep.Trim().ToLower()) {
256
+ 'dotnet' { $installDotnet = $true }
257
+ 'hybrid' { $installHybrid = $true }
258
+ '' { }
259
+ default { Warn "unknown extra '$($ep.Trim())' (known: dotnet, hybrid) — skipped" }
260
+ }
261
+ }
262
+ }
263
+ } elseif ($ExtrasInteractive -and $isTty) {
264
+ # Interactive TTY path.
265
+ Step "Optional extras"
266
+ # Keep descriptions in sync with updater.py EXTRAS registry.
267
+ Write-Host " [dotnet] .NET assembly metadata indexing (dnfile, ~200 KB)."
268
+ Write-Host " Skip if no .csproj / .NET source in repos you ingest."
269
+ Write-Host ""
270
+ Write-Host " [hybrid] In-process BGE-M3 dense+sparse via FlagEmbedding."
271
+ Write-Host " Heavy — pulls torch (~2 GB). Skip unless you need hybrid reranking."
272
+ Write-Host ""
273
+ if (Prompt-YesNo "Install [dotnet] extra?" $false) { $installDotnet = $true }
274
+ if (Prompt-YesNo "Install [hybrid] extra?" $false) { $installHybrid = $true }
275
+ } else {
276
+ # Non-interactive, no env override — dim hint only, no install.
277
+ Dim "hint: optional extras not installed. Re-run with -Extras dotnet,hybrid or set CODEMEMORY_EXTRAS=dotnet,hybrid."
278
+ }
279
+
280
+ $extrasBase = @('dev')
281
+ if ($installDotnet) { $extrasBase += 'dotnet' }
282
+ if ($installHybrid) { $extrasBase += 'hybrid' }
283
+ $extrasJoined = ($extrasBase -join ',')
212
284
 
213
285
  Step "Installing code-memory (editable, extras: $extrasJoined)"
214
- & pip install -e ".[$extrasJoined]"
286
+ # Base [dev] is hard-fail. Optional extras are non-fatal — a missing build
287
+ # dependency (e.g. torch wheel unavailable) must not abort the whole install.
288
+ & pip install -e ".[dev]"
215
289
  if ($LASTEXITCODE -ne 0) { Die "pip install failed" }
290
+ if ($installDotnet -or $installHybrid) {
291
+ $optionalParts = @()
292
+ if ($installDotnet) { $optionalParts += 'dotnet' }
293
+ if ($installHybrid) { $optionalParts += 'hybrid' }
294
+ $optionalJoined = $optionalParts -join ','
295
+ & pip install -e ".[dev,$optionalJoined]"
296
+ if ($LASTEXITCODE -ne 0) {
297
+ Warn "optional extras install failed (dev install succeeded; re-run: pip install -e '.[$optionalJoined]')"
298
+ }
299
+ }
216
300
  Ok "code-memory installed"
217
301
 
218
302
  # ---------- 4. .env ----------
@@ -10,6 +10,9 @@
10
10
  # ./scripts/install.sh --with-claims # also pull gemma2:9b for Graphiti-style
11
11
  # # user-claim extraction (~5.4 GB)
12
12
  # ./scripts/install.sh --with-dotnet # install [dotnet] extra (dnfile, ~200 KB; .NET DLL metadata indexing)
13
+ # ./scripts/install.sh --with-hybrid # install [hybrid] extra (FlagEmbedding; ~2 GB torch)
14
+ # ./scripts/install.sh --extras=dotnet,hybrid
15
+ # # install named extras (comma list)
13
16
  # ./scripts/install.sh --extras=none # bypass interactive prompt; install only [dev]
14
17
  # ./scripts/install.sh --plugins=opencode,claudecode,cursor
15
18
  # # install named harness plugins (non-interactive)
@@ -21,6 +24,10 @@
21
24
  # # default scope is global (~/.config/opencode,
22
25
  # # ~/.claude, ~/.cursor)
23
26
  #
27
+ # Environment variables:
28
+ # CODEMEMORY_EXTRAS=dotnet,hybrid # same as --extras=... (overrides interactive prompt)
29
+ # CODEMEMORY_EXTRAS=none # skip extras entirely
30
+ #
24
31
  set -euo pipefail
25
32
 
26
33
  # ---------- helpers ----------
@@ -48,8 +55,11 @@ SKIP_OLLAMA=0
48
55
  SKIP_TESTS=0
49
56
  SKIP_MCP=0
50
57
  WITH_DOTNET=0
58
+ WITH_HYBRID=0
51
59
  WITH_CLAIMS=0
52
- EXTRAS_ARG="" # empty = interactive; "none" = skip extras prompt; other ignored
60
+ # EXTRAS_ARG: CLI value of --extras=... (overrides CODEMEMORY_EXTRAS env and interactive).
61
+ # Empty means "not set by CLI"; "none" means skip; comma list means install those extras.
62
+ EXTRAS_ARG=""
53
63
  PLUGINS_ARG="" # empty = interactive; explicit value bypasses prompt
54
64
  PLUGINS_SCOPE="global" # global | project
55
65
  for arg in "$@"; do
@@ -58,13 +68,14 @@ for arg in "$@"; do
58
68
  --no-ollama) SKIP_OLLAMA=1 ;;
59
69
  --no-tests) SKIP_TESTS=1 ;;
60
70
  --no-mcp) SKIP_MCP=1 ;;
61
- --with-dotnet) WITH_DOTNET=1 ;;
62
- --with-claims) WITH_CLAIMS=1 ;;
71
+ --with-dotnet) WITH_DOTNET=1 ;;
72
+ --with-hybrid) WITH_HYBRID=1 ;;
73
+ --with-claims) WITH_CLAIMS=1 ;;
63
74
  --extras=*) EXTRAS_ARG="${arg#--extras=}" ;;
64
75
  --plugins=*) PLUGINS_ARG="${arg#--plugins=}" ;;
65
76
  --plugins-scope=*) PLUGINS_SCOPE="${arg#--plugins-scope=}" ;;
66
77
  -h|--help)
67
- sed -n '1,22p' "$0"; exit 0 ;;
78
+ sed -n '1,29p' "$0"; exit 0 ;;
68
79
  *) die "unknown flag: $arg" ;;
69
80
  esac
70
81
  done
@@ -190,22 +201,95 @@ prompt_yes_no() {
190
201
  [ "$ans" = "y" ] || [ "$ans" = "yes" ]
191
202
  }
192
203
 
193
- if [ -z "$EXTRAS_ARG" ] && [ "$WITH_DOTNET" -eq 0 ]; then
194
- # Only interactive if stdin/stdout are a TTY.
195
- if [ -t 0 ] && [ -t 1 ]; then
196
- step "Optional extras"
197
- echo " [dotnet] .NET DLL metadata indexing (dnfile, ~200 KB)."
198
- echo " Skip if no .csproj / .NET source in repos you ingest."
199
- echo
200
- if prompt_yes_no "Install [dotnet] extra?" "n"; then WITH_DOTNET=1; fi
204
+ # ---------- extras resolution (contract mirrors updater.py / install.ps1) ----------
205
+ # Optional extras in pyproject.toml keep in sync with EXTRAS registry in updater.py.
206
+ # dotnet: .NET assembly metadata indexing (dnfile, ~200 KB).
207
+ # hybrid: in-process BGE-M3 dense+sparse via FlagEmbedding (~2 GB torch).
208
+ #
209
+ # Priority: CLI flag (--extras= / --with-dotnet / --with-hybrid) >
210
+ # CODEMEMORY_EXTRAS env var > interactive prompt > skip.
211
+ #
212
+ # TTY_OK = true when we can safely prompt:
213
+ # - Both stdin+stdout are a real TTY, OR /dev/tty is readable as a fallback
214
+ # (handles curl|bash where fd 0 is the pipe but /dev/tty is the terminal).
215
+ TTY_OK=0
216
+ if [ -t 0 ] && [ -t 1 ]; then
217
+ TTY_OK=1
218
+ elif [ -r /dev/tty ]; then
219
+ TTY_OK=1
220
+ fi
221
+
222
+ # Build the effective extras list.
223
+ # We accumulate into INSTALL_DOTNET / INSTALL_HYBRID, then build the pip string.
224
+ INSTALL_DOTNET=0
225
+ INSTALL_HYBRID=0
226
+
227
+ # Fold legacy --with-dotnet / --with-hybrid into the generic flag path.
228
+ [ "$WITH_DOTNET" -eq 1 ] && EXTRAS_ARG="${EXTRAS_ARG:-dotnet}"
229
+ [ "$WITH_HYBRID" -eq 1 ] && {
230
+ if [ -n "$EXTRAS_ARG" ] && [ "$EXTRAS_ARG" != "none" ]; then
231
+ EXTRAS_ARG="${EXTRAS_ARG},hybrid"
232
+ elif [ -z "$EXTRAS_ARG" ]; then
233
+ EXTRAS_ARG="hybrid"
234
+ fi
235
+ }
236
+
237
+ # Determine effective source of extras selection.
238
+ EFFECTIVE_EXTRAS=""
239
+ if [ -n "$EXTRAS_ARG" ]; then
240
+ EFFECTIVE_EXTRAS="$EXTRAS_ARG"
241
+ elif [ -n "${CODEMEMORY_EXTRAS:-}" ]; then
242
+ EFFECTIVE_EXTRAS="$CODEMEMORY_EXTRAS"
243
+ fi
244
+
245
+ if [ -n "$EFFECTIVE_EXTRAS" ]; then
246
+ # env/flag path — parse comma list.
247
+ if [ "$EFFECTIVE_EXTRAS" = "none" ]; then
248
+ : # install nothing extra
249
+ else
250
+ IFS=',' read -r -a _extras_parts <<< "$EFFECTIVE_EXTRAS"
251
+ for _ep in "${_extras_parts[@]}"; do
252
+ _ep="$(printf '%s' "$_ep" | tr -d '[:space:]')"
253
+ case "$_ep" in
254
+ dotnet) INSTALL_DOTNET=1 ;;
255
+ hybrid) INSTALL_HYBRID=1 ;;
256
+ "") ;;
257
+ *) warn "unknown extra '$_ep' (known: dotnet, hybrid) — skipped" ;;
258
+ esac
259
+ done
201
260
  fi
261
+ elif [ "$TTY_OK" -eq 1 ]; then
262
+ # Interactive path.
263
+ step "Optional extras"
264
+ # Keep descriptions in sync with updater.py EXTRAS registry.
265
+ echo " [dotnet] .NET assembly metadata indexing (dnfile, ~200 KB)."
266
+ echo " Skip if no .csproj / .NET source in repos you ingest."
267
+ echo
268
+ echo " [hybrid] In-process BGE-M3 dense+sparse via FlagEmbedding."
269
+ echo " Heavy — pulls torch (~2 GB). Skip unless you need hybrid reranking."
270
+ echo
271
+ if prompt_yes_no "Install [dotnet] extra?" "n"; then INSTALL_DOTNET=1; fi
272
+ if prompt_yes_no "Install [hybrid] extra?" "n"; then INSTALL_HYBRID=1; fi
273
+ else
274
+ # Non-interactive, no env override — skip silently with a dim hint.
275
+ printf "${DIM} hint: optional extras not installed. Re-run with --extras=dotnet,hybrid or set CODEMEMORY_EXTRAS=dotnet,hybrid.${RST}\n"
202
276
  fi
203
277
 
204
278
  EXTRAS="dev"
205
- [ "$WITH_DOTNET" -eq 1 ] && EXTRAS="${EXTRAS},dotnet"
279
+ [ "$INSTALL_DOTNET" -eq 1 ] && EXTRAS="${EXTRAS},dotnet"
280
+ [ "$INSTALL_HYBRID" -eq 1 ] && EXTRAS="${EXTRAS},hybrid"
206
281
 
207
282
  step "Installing code-memory (editable, extras: $EXTRAS)"
208
- pip install -e ".[${EXTRAS}]"
283
+ # The base [dev] install is hard-fail; optional extras are non-fatal so a
284
+ # missing build dependency (e.g. torch wheel not available) does not abort
285
+ # the whole install under set -e.
286
+ pip install -e ".[dev]"
287
+ if [ "$INSTALL_DOTNET" -eq 1 ] || [ "$INSTALL_HYBRID" -eq 1 ]; then
288
+ _optional_extras="${EXTRAS#dev}"
289
+ _optional_extras="${_optional_extras#,}"
290
+ pip install -e ".[dev,${_optional_extras}]" \
291
+ || warn "optional extras install failed (dev install succeeded; re-run: pip install -e '.[${_optional_extras}]')"
292
+ fi
209
293
  ok "code-memory installed"
210
294
 
211
295
  # ---------- 4. .env ----------
@@ -0,0 +1,32 @@
1
+ """Console encoding utilities.
2
+
3
+ Ensures stdout/stderr are safe on non-UTF-8 consoles (e.g. Windows cp1252).
4
+ Must be imported with zero heavy dependencies so it can be called at the
5
+ earliest point of every CLI entry point.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+
12
+
13
+ def _force_utf8_console() -> None:
14
+ """Reconfigure stdout and stderr to UTF-8 with errors='replace'.
15
+
16
+ On Windows the console inherits the active code page (often cp1252 / cp850).
17
+ Python's ``TextIOWrapper.reconfigure`` switches to UTF-8 while keeping the
18
+ same underlying buffer, so Unicode decoration chars (→ • ✓ —) render
19
+ correctly on a real Windows console (Python uses the Unicode console API)
20
+ and degrade to ``?`` when redirected to a cp1252 pipe — never a crash.
21
+
22
+ The ``getattr`` guard + bare ``except Exception`` make this a safe no-op
23
+ on stream types that lack ``reconfigure`` (e.g. pytest's ``CaptureFixture``
24
+ or ``io.StringIO``).
25
+ """
26
+ for stream in (sys.stdout, sys.stderr):
27
+ reconfigure = getattr(stream, "reconfigure", None)
28
+ if reconfigure is not None:
29
+ try:
30
+ reconfigure(encoding="utf-8", errors="replace")
31
+ except Exception: # noqa: BLE001
32
+ pass
@@ -12,12 +12,15 @@ from rich import print as rprint
12
12
 
13
13
  from dataclasses import asdict as _asdict
14
14
 
15
+ from ._console import _force_utf8_console
15
16
  from .config import CONFIG, detect_project_slug
16
17
  from .episodic import Episode
17
18
  from .graph import FalkorStore
18
19
  from .orchestrator import Pipeline, Retriever, list_projects, reset_all, reset_project
19
20
  from .orchestrator import git_delta as _git_delta
20
21
 
22
+ _force_utf8_console()
23
+
21
24
 
22
25
  def _graph_for(project: str | None) -> FalkorStore:
23
26
  slug = project or detect_project_slug()
@@ -1269,6 +1272,14 @@ def update(
1269
1272
  "--bleeding",
1270
1273
  help="Install CLI from git+main instead of PyPI.",
1271
1274
  ),
1275
+ extras: str | None = typer.Option(
1276
+ None,
1277
+ "--extras",
1278
+ help=(
1279
+ "Comma list of optional extras to install (e.g. dotnet,hybrid), or 'none'."
1280
+ " Overrides the interactive prompt and CODEMEMORY_EXTRAS env var."
1281
+ ),
1282
+ ),
1272
1283
  ) -> None:
1273
1284
  """Smart-update code-memory: refresh only components already installed locally.
1274
1285
 
@@ -1278,10 +1289,12 @@ def update(
1278
1289
  stay untouched — no prompts, no re-asking.
1279
1290
 
1280
1291
  Use ``--check`` for a dry-run, ``--full`` to behave like a fresh install.
1292
+ Use ``--extras dotnet,hybrid`` to install optional Python extras, or
1293
+ ``--extras none`` to suppress the interactive prompt.
1281
1294
  """
1282
1295
  from .updater import run_update
1283
1296
 
1284
- rc = run_update(check_only=check, full=full, bleeding=bleeding)
1297
+ rc = run_update(check_only=check, full=full, bleeding=bleeding, extras_override=extras)
1285
1298
  raise typer.Exit(code=rc)
1286
1299
 
1287
1300
 
@@ -1984,6 +1984,9 @@ async def _run() -> None:
1984
1984
 
1985
1985
 
1986
1986
  def main() -> None:
1987
+ from ._console import _force_utf8_console
1988
+
1989
+ _force_utf8_console()
1987
1990
  logging.basicConfig(
1988
1991
  level=os.environ.get("CODE_MEMORY_LOG_LEVEL", "INFO"),
1989
1992
  format="%(asctime)s %(levelname)s %(name)s %(message)s",
@@ -464,12 +464,21 @@ def upgrade_npm_pkg(pkg: str = "code-memory-opencode") -> tuple[bool, str]:
464
464
  # ---------- orchestrator ----------
465
465
 
466
466
 
467
- def run_update(*, check_only: bool, full: bool, bleeding: bool) -> int:
467
+ def run_update(
468
+ *,
469
+ check_only: bool,
470
+ full: bool,
471
+ bleeding: bool,
472
+ extras_override: str | None = None,
473
+ ) -> int:
468
474
  """Top-level entry point used by the CLI.
469
475
 
470
476
  ``check_only`` prints the plan and exits 0/1 based on whether anything
471
477
  is behind. ``full`` re-runs the one-liner installer (curl … | bash).
472
478
  Default is the smart path: upgrade-in-place only what is already present.
479
+
480
+ ``extras_override`` is the value of the ``--extras`` CLI flag (highest
481
+ priority over the ``CODEMEMORY_EXTRAS`` env var).
473
482
  """
474
483
  plan = build_plan()
475
484
  _print_plan(plan)
@@ -511,6 +520,7 @@ def run_update(*, check_only: bool, full: bool, bleeding: bool) -> int:
511
520
  ok, detail = upgrade_docker_images()
512
521
  print(f" Docker stack: {'ok' if ok else 'skip'} — {detail}")
513
522
 
523
+ rc |= offer_missing_extras(plan.install_method, extras_override=extras_override)
514
524
  return rc
515
525
 
516
526
 
@@ -525,6 +535,141 @@ def _print_plan(plan: UpdatePlan) -> None:
525
535
  print(f" {mark} {c.name}{suffix}{state}")
526
536
 
527
537
 
538
+ def resolve_extras_selection(
539
+ *,
540
+ env_value: str | None,
541
+ is_tty: bool,
542
+ missing: list[str],
543
+ ) -> tuple[list[str], str]:
544
+ """Pure helper — no I/O — that decides which extras to install.
545
+
546
+ Returns ``(selected_names, mode)`` where ``mode`` is one of
547
+ ``"env"``, ``"interactive"``, or ``"skip"``.
548
+
549
+ Priority:
550
+ 1. ``env_value`` not None → parse comma list; ``"none"`` or empty → nothing.
551
+ 2. ``is_tty`` True → caller must run the interactive loop (mode=``"interactive"``).
552
+ 3. Otherwise → skip (non-interactive, no env override).
553
+ """
554
+ if env_value is not None:
555
+ stripped = env_value.strip()
556
+ if stripped.lower() == "none" or stripped == "":
557
+ return [], "env"
558
+ names = [n.strip() for n in stripped.split(",") if n.strip()]
559
+ valid = [n for n in names if n in EXTRAS and n in missing]
560
+ return valid, "env"
561
+ if is_tty:
562
+ return [], "interactive"
563
+ return [], "skip"
564
+
565
+
566
+ def _install_selected_extras(names: list[str], method: InstallMethod) -> int:
567
+ """Install a list of extras; returns OR of per-extra return codes.
568
+
569
+ A single failed extra does not abort the rest — each failure sets the
570
+ nonzero bit in ``rc`` but installation continues.
571
+ """
572
+ rc = 0
573
+ for name in names:
574
+ print()
575
+ print(f"Installing extra: {name}")
576
+ ok, detail = install_extra(name, method)
577
+ marker = "ok" if ok else "FAILED"
578
+ print(f" {marker} — {detail}")
579
+ rc |= 0 if ok else 1
580
+ return rc
581
+
582
+
583
+ def offer_missing_extras(
584
+ method: InstallMethod,
585
+ *,
586
+ extras_override: str | None = None,
587
+ ) -> int:
588
+ """Offer to install extras that are not yet present.
589
+
590
+ Called automatically at the end of the smart ``run_update`` path.
591
+ Never blocks ``--check`` / ``--full`` (those return before reaching here).
592
+
593
+ Priority: ``extras_override`` (CLI ``--extras`` flag) >
594
+ ``CODEMEMORY_EXTRAS`` env var > interactive prompt > skip silently.
595
+ """
596
+ missing = [n for n in EXTRAS if not _python_module_present(EXTRAS[n]["module"])]
597
+ if not missing:
598
+ return 0
599
+
600
+ if method in ("unknown",):
601
+ print(
602
+ f"\033[2m hint: optional extras not yet installed: {', '.join(missing)}.\n"
603
+ " Run `code-memory extras` or set CODEMEMORY_EXTRAS=dotnet,hybrid.\033[0m"
604
+ )
605
+ return 0
606
+
607
+ # For editable installs, we need a valid pyproject.toml to install extras.
608
+ if method == "editable":
609
+ repo_root = Path(__file__).resolve().parents[2]
610
+ if not (repo_root / "pyproject.toml").exists():
611
+ print(
612
+ f"\033[2m hint: editable install without pyproject.toml at {repo_root};\n"
613
+ " cannot install optional extras automatically.\033[0m"
614
+ )
615
+ return 0
616
+
617
+ # Determine TTY status safely.
618
+ try:
619
+ is_tty = sys.stdin.isatty()
620
+ except Exception: # noqa: BLE001
621
+ is_tty = False
622
+
623
+ # CLI flag takes precedence over env var.
624
+ effective_env = extras_override if extras_override is not None else os.environ.get("CODEMEMORY_EXTRAS")
625
+
626
+ selected, mode = resolve_extras_selection(
627
+ env_value=effective_env,
628
+ is_tty=is_tty,
629
+ missing=missing,
630
+ )
631
+
632
+ if mode == "env":
633
+ if effective_env is not None:
634
+ # Warn about any names that were not in EXTRAS or already installed.
635
+ raw_names = [n.strip() for n in effective_env.split(",") if n.strip() and n.strip().lower() != "none"]
636
+ unknown = [n for n in raw_names if n not in EXTRAS]
637
+ already = [n for n in raw_names if n in EXTRAS and n not in missing]
638
+ for n in unknown:
639
+ print(f"\033[33m [warn]\033[0m unknown extra '{n}' (known: {', '.join(EXTRAS)})")
640
+ for n in already:
641
+ print(f" · {n} already installed — skipped")
642
+ if not selected:
643
+ return 0
644
+ return _install_selected_extras(selected, method)
645
+
646
+ if mode == "interactive":
647
+ todo: list[str] = []
648
+ print()
649
+ print("Optional extras available:")
650
+ for name in missing:
651
+ info = EXTRAS[name]
652
+ print(f" · {name} — not installed")
653
+ print(f" {info['desc']}")
654
+ try:
655
+ answer = input(f" Install `{name}`? [y/N] ").strip().lower()
656
+ except EOFError:
657
+ answer = ""
658
+ if answer in ("y", "yes"):
659
+ todo.append(name)
660
+ if not todo:
661
+ print("Nothing to install.")
662
+ return 0
663
+ return _install_selected_extras(todo, method)
664
+
665
+ # mode == "skip"
666
+ print(
667
+ f"\033[2m hint: optional extras not yet installed: {', '.join(missing)}.\n"
668
+ " Re-run with CODEMEMORY_EXTRAS=dotnet,hybrid or `code-memory extras`.\033[0m"
669
+ )
670
+ return 0
671
+
672
+
528
673
  def install_extra(name: str, method: InstallMethod) -> tuple[bool, str]:
529
674
  """Install a single optional extra using whichever channel owns the CLI.
530
675