agent-notes 2.22.0__tar.gz → 2.23.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. {agent_notes-2.22.0 → agent_notes-2.23.0}/PKG-INFO +1 -1
  2. agent_notes-2.23.0/agent_notes/VERSION +1 -0
  3. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/cli.py +1 -1
  4. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/commands/build.py +4 -4
  5. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/commands/config.py +24 -10
  6. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/commands/doctor.py +4 -6
  7. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/commands/info.py +2 -2
  8. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/commands/install.py +9 -8
  9. agent_notes-2.23.0/agent_notes/commands/memory/__init__.py +107 -0
  10. agent_notes-2.23.0/agent_notes/commands/memory/_common.py +63 -0
  11. agent_notes-2.23.0/agent_notes/commands/memory/migrate.py +152 -0
  12. agent_notes-2.23.0/agent_notes/commands/memory/notes.py +207 -0
  13. agent_notes-2.23.0/agent_notes/commands/memory/reset.py +87 -0
  14. agent_notes-2.23.0/agent_notes/commands/memory/transfer.py +73 -0
  15. agent_notes-2.23.0/agent_notes/commands/memory/vault.py +66 -0
  16. agent_notes-2.23.0/agent_notes/commands/memory/wiki.py +153 -0
  17. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/commands/regenerate.py +6 -7
  18. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/commands/set_role.py +5 -7
  19. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/commands/uninstall.py +2 -2
  20. agent_notes-2.23.0/agent_notes/commands/wizard/__init__.py +421 -0
  21. agent_notes-2.23.0/agent_notes/commands/wizard/_common.py +63 -0
  22. agent_notes-2.23.0/agent_notes/commands/wizard/execute.py +195 -0
  23. agent_notes-2.23.0/agent_notes/commands/wizard/orchestrator.py +87 -0
  24. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/config.py +8 -4
  25. agent_notes-2.23.0/agent_notes/constants.py +24 -0
  26. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/analyst.md +1 -11
  27. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/api-reviewer.md +1 -11
  28. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/architect.md +1 -11
  29. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/coder.md +8 -18
  30. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/database-specialist.md +1 -11
  31. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/debugger.md +1 -11
  32. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/devil.md +1 -11
  33. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/devops.md +8 -18
  34. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/explorer.md +1 -11
  35. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/integrations.md +8 -18
  36. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/performance-profiler.md +1 -11
  37. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/refactorer.md +8 -18
  38. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/reviewer.md +1 -11
  39. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/security-auditor.md +1 -11
  40. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/system-auditor.md +1 -11
  41. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/tech-writer.md +8 -18
  42. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/test-runner.md +8 -18
  43. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/test-writer.md +8 -18
  44. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/global-claude.md +8 -0
  45. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/roles/reasoner.yaml +1 -0
  46. agent_notes-2.23.0/agent_notes/data/rules/safety.md +41 -0
  47. agent_notes-2.23.0/agent_notes/data/skills/ingest/SKILL.md +239 -0
  48. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/obsidian-memory/SKILL.md +4 -85
  49. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/doctor_checks.py +2 -1
  50. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/domain/role.py +2 -1
  51. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/domain/skill.py +2 -1
  52. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/registries/role_registry.py +1 -0
  53. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/registries/skill_registry.py +24 -20
  54. agent_notes-2.23.0/agent_notes/services/_memory_utils.py +47 -0
  55. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/diagnostics/_checks.py +4 -6
  56. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/diagnostics/_fix.py +2 -2
  57. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/installer.py +65 -9
  58. agent_notes-2.23.0/agent_notes/services/local_backend.py +32 -0
  59. agent_notes-2.23.0/agent_notes/services/memory_router.py +32 -0
  60. agent_notes-2.22.0/agent_notes/services/memory_backend.py → agent_notes-2.23.0/agent_notes/services/obsidian_backend.py +36 -82
  61. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/rendering.py +64 -6
  62. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/settings_writer.py +12 -0
  63. agent_notes-2.23.0/agent_notes/services/wiki/__init__.py +25 -0
  64. agent_notes-2.23.0/agent_notes/services/wiki/_wiki_utils.py +182 -0
  65. agent_notes-2.23.0/agent_notes/services/wiki/wiki_index.py +171 -0
  66. agent_notes-2.23.0/agent_notes/services/wiki/wiki_ingest.py +375 -0
  67. agent_notes-2.23.0/agent_notes/services/wiki/wiki_lint.py +101 -0
  68. agent_notes-2.23.0/agent_notes/services/wiki/wiki_query.py +103 -0
  69. agent_notes-2.23.0/agent_notes/services/wiki/wiki_storage.py +122 -0
  70. agent_notes-2.23.0/agent_notes/services/wiki_backend.py +3 -0
  71. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes.egg-info/PKG-INFO +1 -1
  72. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes.egg-info/SOURCES.txt +33 -6
  73. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/functional/commands/test_config_command.py +6 -6
  74. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/functional/commands/test_regenerate_command.py +2 -2
  75. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/functional/commands/test_uninstall_command.py +1 -1
  76. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/functional/memory/test_memory_command.py +1 -1
  77. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/plugins/test_skills.py +17 -0
  78. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/commands/test_info.py +2 -2
  79. agent_notes-2.23.0/tests/unit/commands/test_memory_imports.py +118 -0
  80. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/commands/test_memory_migrate.py +4 -4
  81. agent_notes-2.23.0/tests/unit/commands/test_wizard_imports.py +106 -0
  82. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/registries/test_registries.py +9 -0
  83. agent_notes-2.23.0/tests/unit/services/test_credential_filter.py +132 -0
  84. agent_notes-2.23.0/tests/unit/services/test_local_backend.py +242 -0
  85. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/services/test_memory_backend.py +3 -1
  86. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/services/test_memory_backend_io.py +76 -13
  87. agent_notes-2.23.0/tests/unit/services/test_memory_router.py +100 -0
  88. agent_notes-2.23.0/tests/unit/services/test_skill_filtering.py +65 -0
  89. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/services/test_wiki_backend.py +51 -0
  90. agent_notes-2.23.0/tests/unit/services/test_wiki_imports.py +222 -0
  91. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/test_memory_dir_for_backend.py +14 -12
  92. agent_notes-2.22.0/agent_notes/VERSION +0 -1
  93. agent_notes-2.22.0/agent_notes/commands/memory.py +0 -843
  94. agent_notes-2.22.0/agent_notes/commands/wizard.py +0 -749
  95. agent_notes-2.22.0/agent_notes/data/rules/safety.md +0 -10
  96. agent_notes-2.22.0/agent_notes/install_state.py +0 -11
  97. agent_notes-2.22.0/agent_notes/services/wiki_backend.py +0 -1022
  98. agent_notes-2.22.0/agent_notes/state.py +0 -21
  99. {agent_notes-2.22.0 → agent_notes-2.23.0}/LICENSE +0 -0
  100. {agent_notes-2.22.0 → agent_notes-2.23.0}/README.md +0 -0
  101. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/__init__.py +0 -0
  102. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/__main__.py +0 -0
  103. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/commands/__init__.py +0 -0
  104. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/commands/_install_helpers.py +0 -0
  105. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/commands/list.py +0 -0
  106. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/commands/validate.py +0 -0
  107. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/agents.yaml +0 -0
  108. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/lead.md +0 -0
  109. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/shared/cost_reporting.md +0 -0
  110. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/shared/execution.md +0 -0
  111. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/shared/guardrails.md +0 -0
  112. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/shared/hard_limits.md +0 -0
  113. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/shared/phase0.md +0 -0
  114. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/shared/pipelines.md +0 -0
  115. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/shared/review.md +0 -0
  116. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/shared/verification.md +0 -0
  117. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/shared/wiki_compile.md +0 -0
  118. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/agents/wiki-compiler.md +0 -0
  119. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/cli/claude.yaml +0 -0
  120. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/cli/copilot.yaml +0 -0
  121. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/cli/opencode.yaml +0 -0
  122. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/commands/brainstorm.md +0 -0
  123. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/commands/debug.md +0 -0
  124. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/commands/review.md +0 -0
  125. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/global-copilot.md +0 -0
  126. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/global-opencode.md +0 -0
  127. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/hooks/session-context.md.tpl +0 -0
  128. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/models/claude-haiku-4-5.yaml +0 -0
  129. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/models/claude-opus-4-1.yaml +0 -0
  130. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/models/claude-opus-4-5.yaml +0 -0
  131. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/models/claude-opus-4-6.yaml +0 -0
  132. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/models/claude-opus-4-7.yaml +0 -0
  133. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/models/claude-sonnet-4-5.yaml +0 -0
  134. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/models/claude-sonnet-4-6.yaml +0 -0
  135. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/models/claude-sonnet-4.yaml +0 -0
  136. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/plugin/claude.yaml +0 -0
  137. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/plugin/opencode-index.js.template +0 -0
  138. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/plugin/opencode.yaml +0 -0
  139. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/pricing.yaml +0 -0
  140. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/roles/orchestrator.yaml +0 -0
  141. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/roles/scout.yaml +0 -0
  142. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/roles/worker.yaml +0 -0
  143. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/rules/code-quality.md +0 -0
  144. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/brainstorming/SKILL.md +0 -0
  145. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/caveman/SKILL.md +0 -0
  146. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/code-review/SKILL.md +0 -0
  147. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/debugging-protocol/SKILL.md +0 -0
  148. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/docker/SKILL.md +0 -0
  149. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/docker/compose.md +0 -0
  150. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/docker/dockerfile.md +0 -0
  151. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/git/SKILL.md +0 -0
  152. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/grill-me/SKILL.md +0 -0
  153. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/grill-with-docs/SKILL.md +0 -0
  154. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/improve-codebase-architecture/SKILL.md +0 -0
  155. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/rails/SKILL.md +0 -0
  156. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/rails/controllers.md +0 -0
  157. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/rails/frontend.md +0 -0
  158. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/rails/infra.md +0 -0
  159. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/rails/models.md +0 -0
  160. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/rails/testing.md +0 -0
  161. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/rails/views.md +0 -0
  162. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/refactoring-protocol/SKILL.md +0 -0
  163. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/setup-project-context/SKILL.md +0 -0
  164. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/tdd/SKILL.md +0 -0
  165. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/skills/zoom-out/SKILL.md +0 -0
  166. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/templates/__init__.py +0 -0
  167. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/templates/__pycache__/__init__.cpython-314.pyc +0 -0
  168. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/templates/frontmatter/__init__.py +0 -0
  169. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/templates/frontmatter/__pycache__/__init__.cpython-314.pyc +0 -0
  170. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/templates/frontmatter/__pycache__/claude.cpython-314.pyc +0 -0
  171. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/templates/frontmatter/__pycache__/opencode.cpython-314.pyc +0 -0
  172. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/templates/frontmatter/claude.py +0 -0
  173. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/data/templates/frontmatter/opencode.py +0 -0
  174. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/domain/__init__.py +0 -0
  175. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/domain/agent.py +0 -0
  176. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/domain/cli_backend.py +0 -0
  177. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/domain/diagnostics.py +0 -0
  178. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/domain/diff.py +0 -0
  179. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/domain/model.py +0 -0
  180. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/domain/rule.py +0 -0
  181. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/domain/state.py +0 -0
  182. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/registries/__init__.py +0 -0
  183. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/registries/_base.py +0 -0
  184. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/registries/agent_registry.py +0 -0
  185. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/registries/cli_registry.py +0 -0
  186. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/registries/model_registry.py +0 -0
  187. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/registries/rule_registry.py +0 -0
  188. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/scripts/__init__.py +0 -0
  189. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/scripts/_claude_backend.py +0 -0
  190. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/scripts/_formatting.py +0 -0
  191. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/scripts/_opencode_backend.py +0 -0
  192. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/scripts/_pricing.py +0 -0
  193. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/scripts/cost_report.py +0 -0
  194. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/__init__.py +0 -0
  195. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/counts.py +0 -0
  196. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/credentials.py +0 -0
  197. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/diagnostics/__init__.py +0 -0
  198. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/diagnostics/_display.py +0 -0
  199. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/diff.py +0 -0
  200. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/fs.py +0 -0
  201. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/install_state_builder.py +0 -0
  202. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/session_context.py +0 -0
  203. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/state_store.py +0 -0
  204. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/ui.py +0 -0
  205. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/user_config.py +0 -0
  206. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes/services/validation.py +0 -0
  207. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes.egg-info/dependency_links.txt +0 -0
  208. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes.egg-info/entry_points.txt +0 -0
  209. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes.egg-info/requires.txt +0 -0
  210. {agent_notes-2.22.0 → agent_notes-2.23.0}/agent_notes.egg-info/top_level.txt +0 -0
  211. {agent_notes-2.22.0 → agent_notes-2.23.0}/pyproject.toml +0 -0
  212. {agent_notes-2.22.0 → agent_notes-2.23.0}/setup.cfg +0 -0
  213. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/conftest.py +0 -0
  214. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/functional/__init__.py +0 -0
  215. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/functional/commands/__init__.py +0 -0
  216. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/functional/commands/test_doctor_command.py +0 -0
  217. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/functional/commands/test_info_command.py +0 -0
  218. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/functional/commands/test_install_command.py +0 -0
  219. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/functional/commands/test_list_command.py +0 -0
  220. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/functional/commands/test_validate_command.py +0 -0
  221. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/functional/memory/__init__.py +0 -0
  222. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/functional/scripts/__init__.py +0 -0
  223. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/functional/scripts/test_release_script.py +0 -0
  224. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/integration/__init__.py +0 -0
  225. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/integration/build_output/__init__.py +0 -0
  226. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/integration/build_output/test_build_output.py +0 -0
  227. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/integration/install/__init__.py +0 -0
  228. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/integration/install/test_install_methods.py +0 -0
  229. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/integration/plugin_builders/__init__.py +0 -0
  230. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/integration/plugin_builders/test_plugin_builders.py +0 -0
  231. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/plugins/__init__.py +0 -0
  232. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/plugins/claude/__init__.py +0 -0
  233. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/plugins/claude/test_agents.py +0 -0
  234. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/__init__.py +0 -0
  235. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/commands/__init__.py +0 -0
  236. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/commands/test_cost_report_subcommand.py +0 -0
  237. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/commands/test_count_agents.py +0 -0
  238. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/commands/test_wizard_orchestrator_skip.py +0 -0
  239. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/commands/test_wizard_preflight.py +0 -0
  240. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/commands/test_wizard_steps.py +0 -0
  241. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/registries/__init__.py +0 -0
  242. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/scripts/__init__.py +0 -0
  243. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/scripts/test_cost_report.py +0 -0
  244. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/scripts/test_cost_report_scoping.py +0 -0
  245. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/scripts/test_formatting_tty.py +0 -0
  246. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/scripts/test_time_aggregation.py +0 -0
  247. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/services/__init__.py +0 -0
  248. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/services/test_build_functions.py +0 -0
  249. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/services/test_credentials.py +0 -0
  250. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/services/test_fs.py +0 -0
  251. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/services/test_installer_plan.py +0 -0
  252. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/services/test_rendering_includes.py +0 -0
  253. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/services/test_session_context.py +0 -0
  254. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/services/test_settings_writer.py +0 -0
  255. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/services/test_state_store.py +0 -0
  256. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/services/test_validation.py +0 -0
  257. {agent_notes-2.22.0 → agent_notes-2.23.0}/tests/unit/test_import_health.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-notes
3
- Version: 2.22.0
3
+ Version: 2.23.0
4
4
  Summary: AI agent configuration manager for Claude Code, OpenCode, and Copilot
5
5
  Author-email: Eugene Naumov <min.verkligheten@gmail.com>
6
6
  License-Expression: MIT
@@ -0,0 +1 @@
1
+ 2.23.0
@@ -284,7 +284,7 @@ def main():
284
284
  # config
285
285
  p_config = subparsers.add_parser("config", help="Reconfigure role/agent/model/memory/skill assignments after install")
286
286
  p_config.add_argument("action", nargs="?", default="wizard",
287
- choices=["wizard", "show", "role-model", "role-agent", "provider", "providers"],
287
+ choices=["wizard", "show", "role-model", "role-agent", "provider", "providers", "memory"],
288
288
  help="Config action (default: wizard)")
289
289
  p_config.add_argument("extra", nargs="*", help="Additional positional args (role, model, agent)")
290
290
  p_config.add_argument("--cli", help="Target CLI (claude / opencode / both)")
@@ -91,18 +91,18 @@ def count_lines(file_path: Path) -> int:
91
91
 
92
92
  def build() -> None:
93
93
  """Build agent configuration files from source."""
94
- from .. import state as state_module
94
+ from ..services.state_store import load_state
95
95
  from ..config import ROOT
96
-
96
+
97
97
  # Read configuration
98
98
  try:
99
99
  agents_config, tiers = load_agents_config()
100
100
  except FileNotFoundError as e:
101
101
  print(f"Error: {e}")
102
102
  return
103
-
103
+
104
104
  # Load state if present (no error if missing)
105
- state = state_module.load()
105
+ state = load_state()
106
106
 
107
107
  # Generate agent files (state=None is backward compatible)
108
108
  print("Generating agent files...")
@@ -7,11 +7,13 @@ import sys
7
7
  from pathlib import Path
8
8
  from typing import Optional
9
9
 
10
+ from ..constants import DEFAULT_VAULT_DIR, DEFAULT_VAULT_NAME, Wiki, Obsidian
11
+
10
12
 
11
13
  def _load_state():
12
14
  """Load state or exit with a clear message."""
13
- from .. import state as state_mod
14
- st = state_mod.load()
15
+ from ..services.state_store import load_state
16
+ st = load_state()
15
17
  if st is None:
16
18
  print("No installation found. Run `agent-notes install` first.")
17
19
  sys.exit(1)
@@ -95,7 +97,7 @@ def _print_diff(before: str, after: str) -> None:
95
97
 
96
98
  def _apply_and_regenerate(state, before: str) -> None:
97
99
  """Show diff, prompt, then write + regenerate on Y."""
98
- from .. import install_state
100
+ from ..services.state_store import record_install_state
99
101
  from ..config import Color
100
102
  from ..services.ui import _safe_input
101
103
 
@@ -114,7 +116,7 @@ def _apply_and_regenerate(state, before: str) -> None:
114
116
  print("Discarded. No changes written.")
115
117
  return
116
118
 
117
- install_state.record_install_state(state)
119
+ record_install_state(state)
118
120
  print("State written.")
119
121
 
120
122
  # Regenerate
@@ -311,9 +313,9 @@ def _wizard_memory(state, before: str) -> bool:
311
313
  from ..services.ui import _safe_input, _path_input
312
314
 
313
315
  storage_options = {
314
- "1": ("local", "Local markdown files (~/.claude/agent-memory/)"),
316
+ "1": ("local", "Local files"),
315
317
  "2": ("obsidian", "Obsidian vault"),
316
- "3": ("none", "None (disable memory)"),
318
+ "3": ("none", "Disabled"),
317
319
  }
318
320
 
319
321
  print("\nMemory storage options:")
@@ -329,7 +331,10 @@ def _wizard_memory(state, before: str) -> bool:
329
331
  path = ""
330
332
 
331
333
  if backend == "obsidian":
332
- mode_options = {"1": ("obsidian", "Session-oriented"), "2": ("wiki", "Wiki")}
334
+ mode_options = {
335
+ "1": ("obsidian", "Session notes — project-scoped decisions, patterns, mistakes"),
336
+ "2": ("wiki", "Knowledge wiki — Karpathy compile-once pattern with concepts, entities, sources"),
337
+ }
333
338
  print("\n Obsidian mode:")
334
339
  for key, (_, mlabel) in mode_options.items():
335
340
  print(f" {key}) {mlabel}")
@@ -337,8 +342,8 @@ def _wizard_memory(state, before: str) -> bool:
337
342
  if mode_choice in mode_options:
338
343
  backend, label = mode_options[mode_choice]
339
344
 
340
- subfolder = "notes" if backend == "obsidian" else "knowledge"
341
- default_vault = str(Path.home() / "Documents" / "Obsidian Vault")
345
+ subfolder = Obsidian.SUBFOLDER if backend == "obsidian" else Wiki.SUBFOLDER
346
+ default_vault = str(Path.home() / DEFAULT_VAULT_DIR / DEFAULT_VAULT_NAME)
342
347
  print(f" Folder name: {subfolder}")
343
348
  print(" Press Tab to autocomplete paths")
344
349
  raw = _path_input(f" Vault path [{default_vault}]: ", default_vault).strip()
@@ -453,6 +458,13 @@ def interactive_config() -> None:
453
458
  print(f"Unknown choice '{choice}'. Quit.")
454
459
 
455
460
 
461
+ def interactive_config_memory() -> None:
462
+ """Run the interactive memory config wizard."""
463
+ state = _load_state()
464
+ before = _state_snapshot(state)
465
+ _wizard_memory(state, before)
466
+
467
+
456
468
  # ── Entry point ──────────────────────────────────────────────────────────────
457
469
 
458
470
  def config(action: str = "wizard", args: Optional[list] = None, cli_filter: Optional[str] = None) -> None:
@@ -483,7 +495,9 @@ def config(action: str = "wizard", args: Optional[list] = None, cli_filter: Opti
483
495
  print("Usage: agent-notes config provider <name>")
484
496
  sys.exit(1)
485
497
  _wizard_provider_status(args[0])
498
+ elif action == "memory":
499
+ interactive_config_memory()
486
500
  else:
487
501
  print(f"Unknown config action: {action}")
488
- print("Actions: wizard, show, role-model, role-agent, providers, provider")
502
+ print("Actions: wizard, show, role-model, role-agent, providers, provider, memory")
489
503
  sys.exit(1)
@@ -64,13 +64,12 @@ def _check_session_hook(scope: str, issues: list) -> None:
64
64
 
65
65
  def check_version_drift(scope: str, issues: list, fix_actions: list) -> None:
66
66
  """Check if the installed package version matches the current running version."""
67
- from .. import install_state
67
+ from ..services.state_store import load_current_state, get_scope
68
68
  from ..config import get_version
69
69
  from ..domain.diagnostics import Issue, FixAction
70
- from ..services.state_store import get_scope
71
70
  from pathlib import Path
72
71
 
73
- state = install_state.load_current_state()
72
+ state = load_current_state()
74
73
  if state is None:
75
74
  return
76
75
 
@@ -96,8 +95,6 @@ def check_version_drift(scope: str, issues: list, fix_actions: list) -> None:
96
95
 
97
96
  def diagnose(scope: str, fix: bool = False) -> bool:
98
97
  """Run all diagnostic checks and optionally apply fixes."""
99
- from .. import install_state
100
-
101
98
  print_summary(scope)
102
99
 
103
100
  issues = []
@@ -120,7 +117,8 @@ def diagnose(scope: str, fix: bool = False) -> bool:
120
117
  _check_session_hook(scope, issues)
121
118
 
122
119
  # Print role→model assignments
123
- state = install_state.load_current_state()
120
+ from ..services.state_store import load_current_state
121
+ state = load_current_state()
124
122
  if state is not None:
125
123
  _check_role_models(state)
126
124
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
- from .. import install_state
5
+ from ..services.state_store import load_current_state as _load_current_state
6
6
  from ..config import get_version, CLAUDE_HOME, Color
7
7
  from ._install_helpers import count_skills, count_agents, count_global
8
8
 
@@ -49,7 +49,7 @@ def show_info() -> None:
49
49
  print(f" Local: {Color.CYAN}not detected{Color.NC}")
50
50
 
51
51
  # State info
52
- st = install_state.load_current_state()
52
+ st = _load_current_state()
53
53
  if st is not None:
54
54
  print("")
55
55
  print("Last install:")
@@ -3,19 +3,20 @@
3
3
  from pathlib import Path
4
4
 
5
5
  from ..config import Color, PKG_DIR
6
- from .. import install_state
6
+ from ..services.install_state_builder import build_install_state
7
+ from ..services.state_store import load_current_state, record_install_state, remove_install_state
7
8
  from ._install_helpers import _verify_install
8
9
 
9
10
 
10
11
  def install(local: bool = False, copy: bool = False, reconfigure: bool = False) -> None:
11
12
  """Build from source and install to targets."""
12
- from ..state import get_scope, state_file
13
+ from ..services.state_store import get_scope, state_file
13
14
  from pathlib import Path
14
-
15
+
15
16
  scope = "local" if local else "global"
16
17
  project_path = Path.cwd().resolve() if local else None
17
18
 
18
- state = install_state.load_current_state()
19
+ state = load_current_state()
19
20
  existing = get_scope(state, scope, project_path) if state else None
20
21
 
21
22
  if existing and not reconfigure:
@@ -55,7 +56,7 @@ def install(local: bool = False, copy: bool = False, reconfigure: bool = False)
55
56
 
56
57
  if existing and reconfigure:
57
58
  print(f"Clearing existing {scope} state (--reconfigure) ...")
58
- install_state.remove_install_state(scope, project_path)
59
+ remove_install_state(scope, project_path)
59
60
  # Fall through to normal install flow
60
61
 
61
62
  # Validate args
@@ -88,13 +89,13 @@ def install(local: bool = False, copy: bool = False, reconfigure: bool = False)
88
89
  # Record state
89
90
  try:
90
91
  project_path = Path.cwd() if local else None
91
- st = install_state.build_install_state(
92
+ st = build_install_state(
92
93
  mode="copy" if copy else "symlink",
93
94
  scope="local" if local else "global",
94
95
  repo_root=PKG_DIR.parent, # repo root (parent of agent_notes pkg)
95
96
  project_path=project_path,
96
97
  )
97
- install_state.record_install_state(st)
98
+ record_install_state(st)
98
99
  except Exception as e:
99
100
  print(f"{Color.YELLOW}Warning: failed to write state.json: {e}{Color.NC}")
100
101
 
@@ -117,7 +118,7 @@ def uninstall(local: bool = False, global_: bool = False) -> None:
117
118
 
118
119
  # Remove state entry for this scope
119
120
  try:
120
- install_state.remove_install_state(scope, project_path)
121
+ remove_install_state(scope, project_path)
121
122
  except Exception as e:
122
123
  print(f"{Color.YELLOW}Warning: failed to clear state.json: {e}{Color.NC}")
123
124
 
@@ -0,0 +1,107 @@
1
+ """Manage agent memory stored in ~/.claude/agent-memory/."""
2
+
3
+ from typing import Optional
4
+
5
+ from . import _common
6
+ from ._common import _load_memory_config, get_directory_size, format_size, _WIKI_TYPE_MAP
7
+ from .vault import do_vault, do_init, do_index
8
+ from .notes import do_add, do_list, do_show, do_size
9
+ from .transfer import do_export, do_import
10
+ from .wiki import do_ingest, do_query, do_lint, do_scan_raw
11
+ from .migrate import do_migrate
12
+ from .reset import do_reset
13
+
14
+
15
+ def show_help() -> None:
16
+ """Show memory command help."""
17
+ help_text = """Usage: agent-notes memory [command] [args]
18
+
19
+ Manage agent memory.
20
+
21
+ Commands:
22
+ init Create folder structure and Index.md
23
+ list List all agent memories with sizes (default)
24
+ vault Show current backend and memory path
25
+ index Regenerate Index.md for the current backend
26
+ add <title> <body> Add a note (obsidian and wiki backends)
27
+ migrate Migrate old per-project layout to new shared flat layout
28
+ size Total disk usage
29
+ show <name> Show memory contents for one agent/category
30
+ reset Clear ALL memories (requires confirmation)
31
+ reset <name> Clear one agent's memory
32
+ export Back up memories to agent-notes/memory-backup/
33
+ import Restore from agent-notes/memory-backup/
34
+ ingest <title> <body> Ingest source material and fan-out to concepts/entities (wiki backend)
35
+ query <keyword> Search wiki pages by keyword (wiki backend)
36
+ lint Check wiki health: orphans, broken links, stale index (wiki backend)
37
+
38
+ Examples:
39
+ agent-notes memory List all memories
40
+ agent-notes memory vault Show backend configuration
41
+ agent-notes memory index Regenerate Index.md
42
+ agent-notes memory migrate Migrate to new flat layout
43
+ agent-notes memory show coder View coder agent's memory
44
+ agent-notes memory reset reviewer Clear reviewer's memory
45
+ agent-notes memory export Back up before cleanup"""
46
+
47
+ print(help_text)
48
+
49
+
50
+ def memory(action: str = "list", name: Optional[str] = None, extra: Optional[list] = None) -> None:
51
+ """Manage agent memory."""
52
+ if action == "list":
53
+ do_list()
54
+ elif action == "init":
55
+ do_init()
56
+ elif action == "vault":
57
+ do_vault()
58
+ elif action == "index":
59
+ do_index()
60
+ elif action == "add":
61
+ # name is title, extra[0] is body
62
+ if not name:
63
+ print("Error: add requires a title.")
64
+ exit(1)
65
+ body = extra[0] if extra else ""
66
+ note_type = extra[1] if extra and len(extra) > 1 else "context"
67
+ agent = extra[2] if extra and len(extra) > 2 else ""
68
+ project = extra[3] if extra and len(extra) > 3 else ""
69
+ do_add(name, body, note_type=note_type, agent=agent, project=project)
70
+ elif action == "size":
71
+ do_size()
72
+ elif action == "show":
73
+ if not name:
74
+ print("Error: show requires an agent name.")
75
+ exit(1)
76
+ do_show(name)
77
+ elif action == "reset":
78
+ do_reset(name)
79
+ elif action == "export":
80
+ do_export()
81
+ elif action == "import":
82
+ do_import()
83
+ elif action == "migrate":
84
+ do_migrate()
85
+ elif action == "ingest":
86
+ if not name:
87
+ do_scan_raw()
88
+ exit(0)
89
+ body = extra[0] if extra else ""
90
+ concepts_csv = extra[1] if extra and len(extra) > 1 else ""
91
+ entities_csv = extra[2] if extra and len(extra) > 2 else ""
92
+ tags_csv = extra[3] if extra and len(extra) > 3 else ""
93
+ concepts = [c.strip() for c in concepts_csv.split(",") if c.strip()] if concepts_csv else None
94
+ entities = [e.strip() for e in entities_csv.split(",") if e.strip()] if entities_csv else None
95
+ tags = [t.strip() for t in tags_csv.split(",") if t.strip()] if tags_csv else None
96
+ do_ingest(name, body, concepts=concepts, entities=entities, tags=tags)
97
+ elif action == "query":
98
+ if not name:
99
+ print("Error: query requires a keyword.")
100
+ exit(1)
101
+ do_query(name)
102
+ elif action == "lint":
103
+ do_lint()
104
+ else:
105
+ print(f"Unknown command: {action}")
106
+ show_help()
107
+ exit(1)
@@ -0,0 +1,63 @@
1
+ """Shared helpers for memory subcommands."""
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ from ...config import MEMORY_DIR
7
+
8
+
9
+ _WIKI_TYPE_MAP = {
10
+ "pattern": "concepts",
11
+ "decision": "concepts",
12
+ "mistake": "concepts",
13
+ "context": "concepts",
14
+ "concept": "concepts",
15
+ "concepts": "concepts",
16
+ "entity": "entities",
17
+ "entities": "entities",
18
+ "synthesis": "synthesis",
19
+ "session": "sessions",
20
+ "sessions": "sessions",
21
+ "source": "sources",
22
+ "sources": "sources",
23
+ }
24
+
25
+
26
+ def _load_memory_config():
27
+ from ...services.state_store import load_state
28
+ from ...config import memory_dir_for_backend
29
+ state = load_state()
30
+ if state is None:
31
+ return "local", MEMORY_DIR
32
+ backend = state.memory.backend
33
+ path = memory_dir_for_backend(backend, state.memory.path)
34
+ return backend, path
35
+
36
+
37
+ def get_directory_size(path: Path) -> int:
38
+ """Calculate total size of directory in bytes."""
39
+ total = 0
40
+ try:
41
+ for item in path.rglob('*'):
42
+ if item.is_file():
43
+ total += item.stat().st_size
44
+ except (OSError, PermissionError):
45
+ pass
46
+ return total
47
+
48
+
49
+ def format_size(size_bytes: int) -> str:
50
+ """Format size in human-readable format."""
51
+ if size_bytes == 0:
52
+ return "0B"
53
+
54
+ original_size = size_bytes
55
+ for unit in ['B', 'K', 'M', 'G', 'T']:
56
+ if original_size < 1024:
57
+ if unit == 'B':
58
+ return f"{original_size}B"
59
+ else:
60
+ return f"{original_size:.1f}{unit}"
61
+ original_size /= 1024
62
+
63
+ return f"{original_size:.1f}P"
@@ -0,0 +1,152 @@
1
+ """Migration subcommand: migrate."""
2
+
3
+ import re
4
+ import shutil
5
+ from datetime import datetime, timezone
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ from . import _common
10
+ from ...config import Color
11
+
12
+
13
+ def do_migrate() -> None:
14
+ """Migrate vault from per-project layout to flat shared layout with new filenames."""
15
+ backend, vault = _common._load_memory_config()
16
+ if backend != "obsidian":
17
+ print("migrate is only available for obsidian storage.")
18
+ return
19
+ if vault is None:
20
+ print("Memory path not configured.")
21
+ return
22
+
23
+ from ...services.obsidian_backend import OBSIDIAN_CATEGORIES
24
+ from ...services.memory_router import memory_regenerate_index
25
+ from ...services._memory_utils import _parse_frontmatter
26
+
27
+ _NEW_FILE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}_")
28
+ _LEGACY_TS_RE = re.compile(r"^(\d{4})-(\d{2})-(\d{2})-(\d{2})-(\d{2})-(\d{2})-(.+)$")
29
+ _BARE_UUID_RE = re.compile(
30
+ r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.IGNORECASE
31
+ )
32
+
33
+ moved = 0
34
+ renamed = 0
35
+ skipped = 0
36
+ errors: list[str] = []
37
+
38
+ def _date_from_frontmatter(path: Path) -> Optional[str]:
39
+ try:
40
+ text = path.read_text()
41
+ fm, _ = _parse_frontmatter(text)
42
+ ca = fm.get("created_at", "")
43
+ if re.fullmatch(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z", ca):
44
+ return ca[:10]
45
+ except OSError:
46
+ pass
47
+ return None
48
+
49
+ def _date_from_mtime(path: Path) -> str:
50
+ ts = path.stat().st_mtime
51
+ return datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%d")
52
+
53
+ def _safe_rename(src: Path, dst: Path) -> bool:
54
+ if dst.exists():
55
+ return False
56
+ src.rename(dst)
57
+ return True
58
+
59
+ def _new_stem(old_stem: str, folder: Path, path: Path) -> Optional[str]:
60
+ """Return new stem under new naming scheme, or None if already correct."""
61
+ if _NEW_FILE_RE.match(old_stem):
62
+ return None # already in new format
63
+
64
+ # Legacy timestamp: YYYY-MM-DD-HH-MM-SS-<slug>
65
+ m = _LEGACY_TS_RE.match(old_stem)
66
+ if m:
67
+ date_part = f"{m.group(1)}-{m.group(2)}-{m.group(3)}"
68
+ slug_part = m.group(7)
69
+ base = f"{date_part}_{slug_part}"
70
+ candidate = f"{base}.md"
71
+ if not (folder / candidate).exists():
72
+ return base
73
+ # collision: append HHMMSS from the original timestamp
74
+ hhmmss = f"{m.group(4)}{m.group(5)}{m.group(6)}"
75
+ return f"{base}_{hhmmss}"
76
+
77
+ # Bare session UUID: <uuid>.md → <date>_<uuid>.md
78
+ if _BARE_UUID_RE.match(old_stem):
79
+ date_part = _date_from_frontmatter(path) or _date_from_mtime(path)
80
+ base = f"{date_part}_{old_stem}"
81
+ candidate = f"{base}.md"
82
+ if not (folder / candidate).exists():
83
+ return base
84
+ hhmmss = datetime.fromtimestamp(path.stat().st_mtime, tz=timezone.utc).strftime("%H%M%S")
85
+ return f"{base}_{hhmmss}"
86
+
87
+ # Unrecognized pattern — skip
88
+ return None
89
+
90
+ # Step 1: move files from per-project subfolders into the shared root
91
+ for item in list(vault.iterdir()):
92
+ if not item.is_dir():
93
+ continue
94
+ if item.name in OBSIDIAN_CATEGORIES or item.name == "Index.md":
95
+ continue
96
+ # item is a per-project subfolder
97
+ for cat in OBSIDIAN_CATEGORIES:
98
+ src_cat = item / cat
99
+ if not src_cat.exists():
100
+ continue
101
+ dst_cat = vault / cat
102
+ dst_cat.mkdir(exist_ok=True)
103
+ for note in src_cat.glob("*.md"):
104
+ dst = dst_cat / note.name
105
+ if dst.exists():
106
+ errors.append(f"collision: {note} -> {dst}")
107
+ continue
108
+ try:
109
+ shutil.move(str(note), str(dst))
110
+ moved += 1
111
+ except OSError as exc:
112
+ errors.append(f"move failed: {note}: {exc}")
113
+ # Remove now-empty category subdir so parent rmdir can succeed
114
+ try:
115
+ src_cat.rmdir()
116
+ except OSError:
117
+ pass
118
+ # Remove subfolder only if empty (preserves any uncategorized files the user may have there)
119
+ try:
120
+ item.rmdir()
121
+ except OSError:
122
+ errors.append(f"per-project subfolder not removed (non-empty): {item}")
123
+
124
+ # Step 2: rename files in each category to the new naming scheme
125
+ for cat in OBSIDIAN_CATEGORIES:
126
+ cat_dir = vault / cat
127
+ if not cat_dir.exists():
128
+ continue
129
+ for note in list(cat_dir.glob("*.md")):
130
+ new_stem = _new_stem(note.stem, cat_dir, note)
131
+ if new_stem is None:
132
+ skipped += 1
133
+ continue
134
+ dst = cat_dir / f"{new_stem}.md"
135
+ try:
136
+ if not _safe_rename(note, dst):
137
+ errors.append(f"rename collision: {note.name} -> {dst.name}")
138
+ skipped += 1
139
+ else:
140
+ renamed += 1
141
+ except OSError as exc:
142
+ errors.append(f"rename failed: {note}: {exc}")
143
+ skipped += 1
144
+
145
+ # Step 3: regenerate index
146
+ memory_regenerate_index(backend, vault)
147
+
148
+ print(f"{moved} moved, {renamed} renamed, {skipped} skipped", end="")
149
+ if errors:
150
+ print(f", errors: {'; '.join(errors)}")
151
+ else:
152
+ print()