agent-notes 2.29.0__tar.gz → 2.30.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 (315) hide show
  1. {agent_notes-2.29.0 → agent_notes-2.30.0}/PKG-INFO +56 -8
  2. {agent_notes-2.29.0 → agent_notes-2.30.0}/README.md +55 -7
  3. agent_notes-2.30.0/agent_notes/VERSION +1 -0
  4. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/cli.py +1 -1
  5. agent_notes-2.30.0/agent_notes/commands/hook.py +479 -0
  6. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/wizard/__init__.py +5 -11
  7. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/constants.py +6 -0
  8. agent_notes-2.30.0/agent_notes/data/agents/shared/pipelines.md +18 -0
  9. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/shared/verification.md +1 -1
  10. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/pricing.yaml +8 -6
  11. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/roles/reasoner.yaml +0 -1
  12. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/chrome-test/SKILL.md +1 -1
  13. agent_notes-2.30.0/agent_notes/data/templates/frontmatter/__pycache__/base.cpython-314.pyc +0 -0
  14. agent_notes-2.30.0/agent_notes/data/templates/frontmatter/__pycache__/codex.cpython-314.pyc +0 -0
  15. agent_notes-2.30.0/agent_notes/data/templates/frontmatter/__pycache__/opencode.cpython-314.pyc +0 -0
  16. agent_notes-2.30.0/agent_notes/data/templates/frontmatter/base.py +47 -0
  17. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/templates/frontmatter/codex.py +7 -24
  18. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/templates/frontmatter/opencode.py +3 -47
  19. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/domain/model.py +1 -0
  20. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/domain/role.py +1 -2
  21. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/registries/model_registry.py +1 -0
  22. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/registries/role_registry.py +0 -1
  23. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/scripts/_claude_backend.py +24 -8
  24. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/scripts/_pricing.py +39 -7
  25. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/credentials.py +2 -2
  26. agent_notes-2.30.0/agent_notes/services/install_executor.py +142 -0
  27. agent_notes-2.30.0/agent_notes/services/install_plan.py +283 -0
  28. agent_notes-2.30.0/agent_notes/services/installer.py +306 -0
  29. agent_notes-2.30.0/agent_notes/services/memory_backend.py +63 -0
  30. agent_notes-2.30.0/agent_notes/services/memory_router.py +14 -0
  31. agent_notes-2.30.0/agent_notes/services/model_resolver.py +222 -0
  32. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/rendering.py +13 -98
  33. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/settings_writer.py +2 -2
  34. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes.egg-info/PKG-INFO +56 -8
  35. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes.egg-info/SOURCES.txt +16 -0
  36. {agent_notes-2.29.0 → agent_notes-2.30.0}/pyproject.toml +3 -0
  37. agent_notes-2.30.0/tests/functional/commands/test_install_roundtrip.py +162 -0
  38. agent_notes-2.30.0/tests/functional/commands/test_uninstall_full_hooks.py +297 -0
  39. agent_notes-2.30.0/tests/functional/commands/test_wizard_happy_path.py +163 -0
  40. agent_notes-2.30.0/tests/unit/commands/test_guard_credentials.py +652 -0
  41. agent_notes-2.30.0/tests/unit/commands/test_precompact_memory_bridge.py +182 -0
  42. agent_notes-2.30.0/tests/unit/commands/test_wizard_role_model_default.py +108 -0
  43. agent_notes-2.30.0/tests/unit/scripts/test_cache_write_pricing.py +348 -0
  44. agent_notes-2.30.0/tests/unit/services/test_installer_credential_guard.py +149 -0
  45. agent_notes-2.30.0/tests/unit/services/test_installer_precompact_hook.py +154 -0
  46. agent_notes-2.30.0/tests/unit/services/test_model_resolver_characterization.py +530 -0
  47. agent_notes-2.29.0/agent_notes/VERSION +0 -1
  48. agent_notes-2.29.0/agent_notes/commands/hook.py +0 -69
  49. agent_notes-2.29.0/agent_notes/data/agents/shared/pipelines.md +0 -18
  50. agent_notes-2.29.0/agent_notes/data/templates/frontmatter/__pycache__/codex.cpython-314.pyc +0 -0
  51. agent_notes-2.29.0/agent_notes/data/templates/frontmatter/__pycache__/opencode.cpython-314.pyc +0 -0
  52. agent_notes-2.29.0/agent_notes/services/installer.py +0 -586
  53. agent_notes-2.29.0/agent_notes/services/memory_router.py +0 -32
  54. {agent_notes-2.29.0 → agent_notes-2.30.0}/LICENSE +0 -0
  55. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/__init__.py +0 -0
  56. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/__main__.py +0 -0
  57. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/__init__.py +0 -0
  58. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/_install_helpers.py +0 -0
  59. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/build.py +0 -0
  60. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/config.py +0 -0
  61. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/doctor.py +0 -0
  62. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/info.py +0 -0
  63. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/install.py +0 -0
  64. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/list.py +0 -0
  65. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/memory/__init__.py +0 -0
  66. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/memory/_common.py +0 -0
  67. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/memory/migrate.py +0 -0
  68. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/memory/notes.py +0 -0
  69. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/memory/reset.py +0 -0
  70. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/memory/transfer.py +0 -0
  71. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/memory/vault.py +0 -0
  72. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/memory/wiki.py +0 -0
  73. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/regenerate.py +0 -0
  74. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/set_role.py +0 -0
  75. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/uninstall.py +0 -0
  76. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/validate.py +0 -0
  77. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/wizard/_common.py +0 -0
  78. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/wizard/cost_report.py +0 -0
  79. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/wizard/execute.py +0 -0
  80. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/commands/wizard/orchestrator.py +0 -0
  81. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/config.py +0 -0
  82. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/agents.yaml +0 -0
  83. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/analyst.md +0 -0
  84. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/api-reviewer.md +0 -0
  85. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/architect.md +0 -0
  86. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/coder.md +0 -0
  87. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/database-specialist.md +0 -0
  88. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/debugger.md +0 -0
  89. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/devil.md +0 -0
  90. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/devops.md +0 -0
  91. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/explorer.md +0 -0
  92. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/integrations.md +0 -0
  93. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/lead.md +0 -0
  94. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/performance-profiler.md +0 -0
  95. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/refactorer.md +0 -0
  96. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/reviewer.md +0 -0
  97. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/security-auditor.md +0 -0
  98. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/shared/cost_reporting.md +0 -0
  99. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/shared/execution.md +0 -0
  100. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/shared/guardrails.md +0 -0
  101. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/shared/hard_limits.md +0 -0
  102. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/shared/phase0.md +0 -0
  103. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/shared/review.md +0 -0
  104. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/shared/wiki_compile.md +0 -0
  105. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/system-auditor.md +0 -0
  106. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/tech-writer.md +0 -0
  107. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/test-runner.md +0 -0
  108. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/test-writer.md +0 -0
  109. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/agents/wiki-compiler.md +0 -0
  110. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/cli/claude.yaml +0 -0
  111. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/cli/codex.yaml +0 -0
  112. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/cli/copilot.yaml +0 -0
  113. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/cli/opencode.yaml +0 -0
  114. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/commands/brainstorm.md +0 -0
  115. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/commands/debug.md +0 -0
  116. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/commands/review.md +0 -0
  117. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/global-claude.md +0 -0
  118. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/global-codex.md +0 -0
  119. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/global-copilot.md +0 -0
  120. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/global-opencode.md +0 -0
  121. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/hooks/session-context.md.tpl +0 -0
  122. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/models/claude-haiku-4-5.yaml +0 -0
  123. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/models/claude-opus-4-1.yaml +0 -0
  124. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/models/claude-opus-4-5.yaml +0 -0
  125. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/models/claude-opus-4-6.yaml +0 -0
  126. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/models/claude-opus-4-7.yaml +0 -0
  127. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/models/claude-opus-4-8.yaml +0 -0
  128. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/models/claude-sonnet-4-5.yaml +0 -0
  129. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/models/claude-sonnet-4-6.yaml +0 -0
  130. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/models/claude-sonnet-4.yaml +0 -0
  131. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/models/gpt-5-4-mini.yaml +0 -0
  132. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/models/gpt-5-4.yaml +0 -0
  133. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/models/gpt-5-5.yaml +0 -0
  134. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/plugin/claude.yaml +0 -0
  135. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/plugin/opencode-index.js.template +0 -0
  136. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/plugin/opencode.yaml +0 -0
  137. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/roles/orchestrator.yaml +0 -0
  138. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/roles/scout.yaml +0 -0
  139. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/roles/worker.yaml +0 -0
  140. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/rules/code-quality.md +0 -0
  141. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/rules/safety.md +0 -0
  142. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/brainstorming/SKILL.md +0 -0
  143. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/caveman/SKILL.md +0 -0
  144. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/code-review/SKILL.md +0 -0
  145. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/debugging-protocol/SKILL.md +0 -0
  146. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/docker/SKILL.md +0 -0
  147. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/docker/compose.md +0 -0
  148. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/docker/dockerfile.md +0 -0
  149. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/git/SKILL.md +0 -0
  150. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/grill-me/SKILL.md +0 -0
  151. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/grill-with-docs/SKILL.md +0 -0
  152. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/handoff/SKILL.md +0 -0
  153. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/improve-codebase-architecture/SKILL.md +0 -0
  154. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/ingest/SKILL.md +0 -0
  155. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/migrate-memory/SKILL.md +0 -0
  156. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/obsidian-memory/SKILL.md +0 -0
  157. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/prototype/LOGIC.md +0 -0
  158. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/prototype/SKILL.md +0 -0
  159. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/prototype/UI.md +0 -0
  160. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/rails/SKILL.md +0 -0
  161. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/rails/controllers.md +0 -0
  162. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/rails/frontend.md +0 -0
  163. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/rails/infra.md +0 -0
  164. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/rails/models.md +0 -0
  165. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/rails/testing.md +0 -0
  166. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/rails/views.md +0 -0
  167. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/refactoring-protocol/SKILL.md +0 -0
  168. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/rsi/SKILL.md +0 -0
  169. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/setup-project-context/SKILL.md +0 -0
  170. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/tdd/SKILL.md +0 -0
  171. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/to-issues/SKILL.md +0 -0
  172. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/to-prd/SKILL.md +0 -0
  173. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/write-a-skill/SKILL.md +0 -0
  174. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/skills/zoom-out/SKILL.md +0 -0
  175. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/templates/__init__.py +0 -0
  176. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/templates/__pycache__/__init__.cpython-312.pyc +0 -0
  177. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/templates/__pycache__/__init__.cpython-314.pyc +0 -0
  178. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/templates/frontmatter/__init__.py +0 -0
  179. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/templates/frontmatter/__pycache__/__init__.cpython-312.pyc +0 -0
  180. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/templates/frontmatter/__pycache__/__init__.cpython-314.pyc +0 -0
  181. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/templates/frontmatter/__pycache__/claude.cpython-312.pyc +0 -0
  182. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/templates/frontmatter/__pycache__/claude.cpython-314.pyc +0 -0
  183. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/templates/frontmatter/__pycache__/codex.cpython-312.pyc +0 -0
  184. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/templates/frontmatter/__pycache__/opencode.cpython-312.pyc +0 -0
  185. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/data/templates/frontmatter/claude.py +0 -0
  186. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/doctor_checks.py +0 -0
  187. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/domain/__init__.py +0 -0
  188. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/domain/agent.py +0 -0
  189. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/domain/cli_backend.py +0 -0
  190. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/domain/diagnostics.py +0 -0
  191. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/domain/diff.py +0 -0
  192. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/domain/rule.py +0 -0
  193. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/domain/skill.py +0 -0
  194. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/domain/state.py +0 -0
  195. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/registries/__init__.py +0 -0
  196. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/registries/_base.py +0 -0
  197. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/registries/agent_registry.py +0 -0
  198. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/registries/cli_registry.py +0 -0
  199. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/registries/rule_registry.py +0 -0
  200. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/registries/skill_registry.py +0 -0
  201. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/scripts/__init__.py +0 -0
  202. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/scripts/_formatting.py +0 -0
  203. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/scripts/_opencode_backend.py +0 -0
  204. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/scripts/cost_report.py +0 -0
  205. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/__init__.py +0 -0
  206. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/_memory_utils.py +0 -0
  207. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/counts.py +0 -0
  208. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/diagnostics/__init__.py +0 -0
  209. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/diagnostics/_checks.py +0 -0
  210. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/diagnostics/_display.py +0 -0
  211. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/diagnostics/_fix.py +0 -0
  212. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/diff.py +0 -0
  213. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/fs.py +0 -0
  214. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/install_state_builder.py +0 -0
  215. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/local_backend.py +0 -0
  216. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/migrations/__init__.py +0 -0
  217. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/obsidian_backend.py +0 -0
  218. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/session_context.py +0 -0
  219. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/state_store.py +0 -0
  220. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/ui.py +0 -0
  221. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/user_config.py +0 -0
  222. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/validation.py +0 -0
  223. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/wiki/__init__.py +0 -0
  224. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/wiki/_wiki_utils.py +0 -0
  225. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/wiki/wiki_index.py +0 -0
  226. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/wiki/wiki_ingest.py +0 -0
  227. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/wiki/wiki_lint.py +0 -0
  228. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/wiki/wiki_query.py +0 -0
  229. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/wiki/wiki_storage.py +0 -0
  230. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes/services/wiki_backend.py +0 -0
  231. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes.egg-info/dependency_links.txt +0 -0
  232. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes.egg-info/entry_points.txt +0 -0
  233. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes.egg-info/requires.txt +0 -0
  234. {agent_notes-2.29.0 → agent_notes-2.30.0}/agent_notes.egg-info/top_level.txt +0 -0
  235. {agent_notes-2.29.0 → agent_notes-2.30.0}/setup.cfg +0 -0
  236. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/conftest.py +0 -0
  237. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/functional/__init__.py +0 -0
  238. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/functional/commands/__init__.py +0 -0
  239. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/functional/commands/test_config_command.py +0 -0
  240. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/functional/commands/test_doctor_command.py +0 -0
  241. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/functional/commands/test_info_command.py +0 -0
  242. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/functional/commands/test_install_command.py +0 -0
  243. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/functional/commands/test_list_command.py +0 -0
  244. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/functional/commands/test_regenerate_command.py +0 -0
  245. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/functional/commands/test_uninstall_command.py +0 -0
  246. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/functional/commands/test_validate_command.py +0 -0
  247. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/functional/commands/test_validate_path_filter.py +0 -0
  248. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/functional/memory/__init__.py +0 -0
  249. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/functional/memory/test_memory_add_local_backend.py +0 -0
  250. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/functional/memory/test_memory_command.py +0 -0
  251. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/functional/scripts/__init__.py +0 -0
  252. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/functional/scripts/test_release_script.py +0 -0
  253. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/integration/__init__.py +0 -0
  254. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/integration/build_output/__init__.py +0 -0
  255. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/integration/build_output/test_build_output.py +0 -0
  256. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/integration/install/__init__.py +0 -0
  257. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/integration/install/test_install_methods.py +0 -0
  258. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/integration/plugin_builders/__init__.py +0 -0
  259. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/integration/plugin_builders/test_plugin_builders.py +0 -0
  260. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/plugins/__init__.py +0 -0
  261. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/plugins/claude/__init__.py +0 -0
  262. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/plugins/claude/test_agents.py +0 -0
  263. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/plugins/codex/__init__.py +0 -0
  264. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/plugins/codex/test_agents.py +0 -0
  265. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/plugins/test_skills.py +0 -0
  266. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/__init__.py +0 -0
  267. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/commands/__init__.py +0 -0
  268. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/commands/test_cost_report_subcommand.py +0 -0
  269. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/commands/test_count_agents.py +0 -0
  270. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/commands/test_info.py +0 -0
  271. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/commands/test_memory_add_description.py +0 -0
  272. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/commands/test_memory_imports.py +0 -0
  273. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/commands/test_memory_migrate.py +0 -0
  274. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/commands/test_wizard_imports.py +0 -0
  275. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/commands/test_wizard_orchestrator_skip.py +0 -0
  276. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/commands/test_wizard_preflight.py +0 -0
  277. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/commands/test_wizard_steps.py +0 -0
  278. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/commands/wizard/test_cost_report_step.py +0 -0
  279. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/registries/__init__.py +0 -0
  280. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/registries/test_cli_registry.py +0 -0
  281. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/registries/test_registries.py +0 -0
  282. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/scripts/__init__.py +0 -0
  283. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/scripts/test_cost_report.py +0 -0
  284. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/scripts/test_cost_report_scoping.py +0 -0
  285. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/scripts/test_formatting_tty.py +0 -0
  286. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/scripts/test_opencode_backend_connection.py +0 -0
  287. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/scripts/test_opencode_backend_pricing.py +0 -0
  288. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/scripts/test_time_aggregation.py +0 -0
  289. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/__init__.py +0 -0
  290. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_build_functions.py +0 -0
  291. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_credential_filter.py +0 -0
  292. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_credentials.py +0 -0
  293. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_fs.py +0 -0
  294. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_installer_codex.py +0 -0
  295. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_installer_hook_quoting.py +0 -0
  296. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_installer_hooks.py +0 -0
  297. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_installer_plan.py +0 -0
  298. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_local_backend.py +0 -0
  299. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_memory_backend.py +0 -0
  300. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_memory_backend_io.py +0 -0
  301. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_memory_router.py +0 -0
  302. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_rendering_includes.py +0 -0
  303. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_session_context.py +0 -0
  304. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_settings_writer.py +0 -0
  305. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_skill_filtering.py +0 -0
  306. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_state_store.py +0 -0
  307. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_user_config_cost_report.py +0 -0
  308. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_validation.py +0 -0
  309. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_wiki_atomic_write.py +0 -0
  310. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_wiki_backend.py +0 -0
  311. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/services/test_wiki_imports.py +0 -0
  312. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/templates/__init__.py +0 -0
  313. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/templates/test_codex_frontmatter.py +0 -0
  314. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/test_import_health.py +0 -0
  315. {agent_notes-2.29.0 → agent_notes-2.30.0}/tests/unit/test_memory_dir_for_backend.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-notes
3
- Version: 2.29.0
3
+ Version: 2.30.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
@@ -63,12 +63,6 @@ pipx install agent-notes
63
63
  agent-notes install
64
64
  ```
65
65
 
66
- Update anytime:
67
-
68
- ```bash
69
- pipx upgrade agent-notes && agent-notes install
70
- ```
71
-
72
66
  **venv + pip (if you prefer to manage your own environment):**
73
67
 
74
68
  ```bash
@@ -77,12 +71,34 @@ pip install agent-notes
77
71
  agent-notes install
78
72
  ```
79
73
 
80
- Update anytime:
74
+ ### Upgrade from a previous version
75
+
76
+ **Update in place (normal upgrade):**
77
+
78
+ With pipx:
79
+ ```bash
80
+ pipx upgrade agent-notes && agent-notes install
81
+ ```
81
82
 
83
+ With venv:
82
84
  ```bash
83
85
  pip install --upgrade agent-notes && agent-notes install
84
86
  ```
85
87
 
88
+ **Clean reinstall (from an older version, fresh slate):**
89
+
90
+ If you are on a significantly older version or want a fresh configuration, uninstall completely and reinstall:
91
+
92
+ With pipx:
93
+ ```bash
94
+ agent-notes uninstall && pipx uninstall agent-notes && pipx install agent-notes && agent-notes install
95
+ ```
96
+
97
+ With venv:
98
+ ```bash
99
+ agent-notes uninstall && pip uninstall agent-notes && pip install agent-notes && agent-notes install
100
+ ```
101
+
86
102
  ### Local build (developers)
87
103
 
88
104
  ```bash
@@ -119,6 +135,36 @@ agent-notes config provider openrouter # prints "configured" or "no key"
119
135
 
120
136
  </details>
121
137
 
138
+ <details>
139
+ <summary>Uninstall</summary>
140
+
141
+ To fully remove agent-notes:
142
+
143
+ **pipx:**
144
+
145
+ ```bash
146
+ agent-notes uninstall
147
+ pipx uninstall agent-notes
148
+ ```
149
+
150
+ Run `agent-notes uninstall` first — it removes agent files, hooks, settings.json entries, context file, and state that agent-notes installed. Then `pipx uninstall` removes the package itself. The `agent-notes uninstall` command must run while the CLI is still installed.
151
+
152
+ **venv:**
153
+
154
+ ```bash
155
+ agent-notes uninstall
156
+ deactivate
157
+ rm -rf .venv
158
+ ```
159
+
160
+ Run `agent-notes uninstall` first to clean up installed components, then deactivate the environment and delete the `.venv` directory.
161
+
162
+ **Note on memory and vault permissions:**
163
+
164
+ If you configured agent-notes to access your memory vault (Obsidian or local), those read/write permissions are intentionally left in place after uninstalling so Claude Code and other tools can still access your notes. To remove them, edit `~/.claude/settings.json` and delete the entries under `permissions.allow` that reference your vault path (e.g. `Read(/path/to/your/vault/**)`, `Write(/path/to/your/vault/**)`, `Edit(/path/to/your/vault/**)`).
165
+
166
+ </details>
167
+
122
168
  ---
123
169
 
124
170
  <details>
@@ -409,6 +455,8 @@ By default, `agent-notes install` re-runs the interactive wizard and prompts for
409
455
 
410
456
  The plugin build scripts (`scripts/build-claude-plugin.sh`, `scripts/build-opencode-plugin.sh`) automatically use `.venv/bin/python` when present, fall back to system `python3`, and honor a `PYTHON=` override.
411
457
 
458
+ **Note:** `uv` is used only in the local development and release workflow (see `scripts/release`). It is not required to install or use agent-notes — end users should use `pipx` or `pip` / `venv`.
459
+
412
460
  ### Test structure
413
461
 
414
462
  - `tests/functional/` — Unit tests
@@ -31,12 +31,6 @@ pipx install agent-notes
31
31
  agent-notes install
32
32
  ```
33
33
 
34
- Update anytime:
35
-
36
- ```bash
37
- pipx upgrade agent-notes && agent-notes install
38
- ```
39
-
40
34
  **venv + pip (if you prefer to manage your own environment):**
41
35
 
42
36
  ```bash
@@ -45,12 +39,34 @@ pip install agent-notes
45
39
  agent-notes install
46
40
  ```
47
41
 
48
- Update anytime:
42
+ ### Upgrade from a previous version
43
+
44
+ **Update in place (normal upgrade):**
45
+
46
+ With pipx:
47
+ ```bash
48
+ pipx upgrade agent-notes && agent-notes install
49
+ ```
49
50
 
51
+ With venv:
50
52
  ```bash
51
53
  pip install --upgrade agent-notes && agent-notes install
52
54
  ```
53
55
 
56
+ **Clean reinstall (from an older version, fresh slate):**
57
+
58
+ If you are on a significantly older version or want a fresh configuration, uninstall completely and reinstall:
59
+
60
+ With pipx:
61
+ ```bash
62
+ agent-notes uninstall && pipx uninstall agent-notes && pipx install agent-notes && agent-notes install
63
+ ```
64
+
65
+ With venv:
66
+ ```bash
67
+ agent-notes uninstall && pip uninstall agent-notes && pip install agent-notes && agent-notes install
68
+ ```
69
+
54
70
  ### Local build (developers)
55
71
 
56
72
  ```bash
@@ -87,6 +103,36 @@ agent-notes config provider openrouter # prints "configured" or "no key"
87
103
 
88
104
  </details>
89
105
 
106
+ <details>
107
+ <summary>Uninstall</summary>
108
+
109
+ To fully remove agent-notes:
110
+
111
+ **pipx:**
112
+
113
+ ```bash
114
+ agent-notes uninstall
115
+ pipx uninstall agent-notes
116
+ ```
117
+
118
+ Run `agent-notes uninstall` first — it removes agent files, hooks, settings.json entries, context file, and state that agent-notes installed. Then `pipx uninstall` removes the package itself. The `agent-notes uninstall` command must run while the CLI is still installed.
119
+
120
+ **venv:**
121
+
122
+ ```bash
123
+ agent-notes uninstall
124
+ deactivate
125
+ rm -rf .venv
126
+ ```
127
+
128
+ Run `agent-notes uninstall` first to clean up installed components, then deactivate the environment and delete the `.venv` directory.
129
+
130
+ **Note on memory and vault permissions:**
131
+
132
+ If you configured agent-notes to access your memory vault (Obsidian or local), those read/write permissions are intentionally left in place after uninstalling so Claude Code and other tools can still access your notes. To remove them, edit `~/.claude/settings.json` and delete the entries under `permissions.allow` that reference your vault path (e.g. `Read(/path/to/your/vault/**)`, `Write(/path/to/your/vault/**)`, `Edit(/path/to/your/vault/**)`).
133
+
134
+ </details>
135
+
90
136
  ---
91
137
 
92
138
  <details>
@@ -377,6 +423,8 @@ By default, `agent-notes install` re-runs the interactive wizard and prompts for
377
423
 
378
424
  The plugin build scripts (`scripts/build-claude-plugin.sh`, `scripts/build-opencode-plugin.sh`) automatically use `.venv/bin/python` when present, fall back to system `python3`, and honor a `PYTHON=` override.
379
425
 
426
+ **Note:** `uv` is used only in the local development and release workflow (see `scripts/release`). It is not required to install or use agent-notes — end users should use `pipx` or `pip` / `venv`.
427
+
380
428
  ### Test structure
381
429
 
382
430
  - `tests/functional/` — Unit tests
@@ -0,0 +1 @@
1
+ 2.30.0
@@ -291,7 +291,7 @@ def main():
291
291
 
292
292
  # hook
293
293
  p_hook = subparsers.add_parser("hook", help="Claude Code hook integrations")
294
- p_hook.add_argument("subaction", choices=["memory-bridge", "session-discover"],
294
+ p_hook.add_argument("subaction", choices=["memory-bridge", "precompact-memory-bridge", "session-discover", "guard-credentials"],
295
295
  help="Hook to run")
296
296
 
297
297
  # cost-report
@@ -0,0 +1,479 @@
1
+ """Hook command - Claude Code hook integrations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+
12
+ def hook(subaction: str) -> None:
13
+ """Handle hook subactions."""
14
+ if subaction == "memory-bridge":
15
+ _memory_bridge()
16
+ elif subaction == "precompact-memory-bridge":
17
+ _precompact_memory_bridge()
18
+ elif subaction == "session-discover":
19
+ _session_discover()
20
+ elif subaction == "guard-credentials":
21
+ _guard_credentials()
22
+
23
+
24
+ def _session_discover() -> None:
25
+ """Discover all agent-notes profiles for the current project and emit combined context."""
26
+ try:
27
+ from ..services.state_store import load_state, get_profiles_for_project
28
+ from ..registries.cli_registry import load_registry
29
+
30
+ state = load_state()
31
+ if state is None:
32
+ return
33
+
34
+ registry = load_registry()
35
+ default_local_dirs = {b.name: b.local_dir for b in registry.all()}
36
+
37
+ for key, scope_state in get_profiles_for_project(state, Path.cwd()):
38
+ for cli_name, backend_state in scope_state.clis.items():
39
+ local_dir = backend_state.local_dir_override or default_local_dirs.get(cli_name, ".claude")
40
+ context_file = Path(local_dir) / "agent-notes-context.md"
41
+ if context_file.exists():
42
+ label = scope_state.profile_label or "default"
43
+ print(f"<!-- agent-notes profile: {label} -->")
44
+ print(context_file.read_text(encoding="utf-8"))
45
+ except Exception:
46
+ return
47
+
48
+
49
+ def _guard_credentials() -> None:
50
+ """PreToolUse hook that denies attempts to read credential files.
51
+
52
+ Reads the PreToolUse JSON payload from stdin, calls evaluate_credential_access,
53
+ and prints a deny or allow response. Always exits 0 so Claude Code parses stdout.
54
+
55
+ Fail-closed for recognized tool types: if stdin parsed and tool_name is a
56
+ guarded tool (Read/Bash/Grep), any downstream error emits a static deny.
57
+ Only a total stdin/JSON parse failure fails open (writes to stderr).
58
+ """
59
+ raw = ""
60
+ try:
61
+ raw = sys.stdin.read()
62
+ payload = json.loads(raw)
63
+ except Exception as exc:
64
+ # Total infrastructure failure — cannot parse stdin at all; fail open
65
+ # with a stderr note so operators can diagnose.
66
+ print(f"credential-guard: stdin parse error ({exc})", file=sys.stderr)
67
+ return
68
+
69
+ tool_name = payload.get("tool_name", "")
70
+ tool_input = payload.get("tool_input", {})
71
+
72
+ _GUARDED_TOOLS = {"Read", "Bash", "Grep"}
73
+
74
+ if tool_name in _GUARDED_TOOLS:
75
+ try:
76
+ decision = evaluate_credential_access(tool_name, tool_input)
77
+ if decision is not None:
78
+ print(json.dumps(decision))
79
+ except Exception:
80
+ # Evaluation error on a guarded tool — fail closed
81
+ print(json.dumps(_deny_payload(
82
+ "Credential guard: evaluation error — denying as a precaution; "
83
+ "remove the guard hook from settings.json to override."
84
+ )))
85
+ else:
86
+ # Non-guarded tool: pass through (hook matcher already scopes to Read|Bash|Grep,
87
+ # but defensive check here in case Claude Code invokes us for other tools)
88
+ try:
89
+ decision = evaluate_credential_access(tool_name, tool_input)
90
+ if decision is not None:
91
+ print(json.dumps(decision))
92
+ except Exception:
93
+ pass # Non-guarded tool errors fail open
94
+
95
+
96
+ # ---------------------------------------------------------------------------
97
+ # Credential patterns (from safety.md "Credentials — ABSOLUTE PROHIBITION")
98
+ # ---------------------------------------------------------------------------
99
+
100
+ # Non-secret template suffixes — .env.example / .env.sample / .env.template /
101
+ # .env.dist are not real secret files. Allow them explicitly.
102
+ _ENV_TEMPLATE_SUFFIXES = frozenset({
103
+ ".example", ".sample", ".template", ".dist",
104
+ })
105
+
106
+ # Filename-level patterns: matched against the basename of the path.
107
+ # Order: most specific first; the .env allowlist check runs BEFORE these.
108
+ _CREDENTIAL_BASENAME_PATTERNS: list[re.Pattern[str]] = [
109
+ re.compile(r"^service-account.*\.json$", re.IGNORECASE),
110
+ re.compile(r"^credentials\.", re.IGNORECASE),
111
+ re.compile(r"^secrets\.", re.IGNORECASE),
112
+ re.compile(r"^.*-secrets\.", re.IGNORECASE),
113
+ re.compile(r"^.*\.env$", re.IGNORECASE), # bare *.env
114
+ re.compile(r"^\.env(\.|$)", re.IGNORECASE), # .env, .env.production, etc.
115
+ re.compile(r"^.*\.key$", re.IGNORECASE),
116
+ re.compile(r"^.*\.pem$", re.IGNORECASE),
117
+ re.compile(r"^.*\.p12$", re.IGNORECASE),
118
+ re.compile(r"^.*\.pfx$", re.IGNORECASE),
119
+ re.compile(r"^.*\.jks$", re.IGNORECASE),
120
+ re.compile(r"^.*\.keystore$", re.IGNORECASE),
121
+ re.compile(r"^.*\.truststore$", re.IGNORECASE),
122
+ ]
123
+
124
+ # Word-boundary separators for keyword matching within a path segment.
125
+ # A keyword only matches when surrounded by start/end of segment or one of these.
126
+ _SEG_SEPS = re.compile(r"[-_.\s]")
127
+
128
+ # Path-segment keywords: match only as DELIMITED words within a segment.
129
+ # E.g. "token" matches "auth-token.json" and "my_token" but NOT "tokenizer.py"
130
+ # or "PasswordResetToken.tsx". Boundaries are start/end of segment or [-_.\s].
131
+ _CREDENTIAL_SEGMENT_KEYWORDS = (
132
+ "secret",
133
+ "credential",
134
+ "token",
135
+ "apikey",
136
+ "auth-key",
137
+ "private-key",
138
+ )
139
+
140
+ # Existence/metadata commands that are ALWAYS allowed even if they reference a
141
+ # credential path — these never surface file contents.
142
+ _SAFE_EXISTENCE_COMMANDS = frozenset({"test", "[", "ls", "stat", "find", "file"})
143
+
144
+ # Prefix tokens that do not change the effective command — stripped before
145
+ # identifying the first real command.
146
+ _COMMAND_PREFIXES = frozenset({
147
+ "sudo", "time", "nohup", "env", "xargs", "exec", "command",
148
+ "nice", "doas",
149
+ })
150
+
151
+
152
+ def _is_env_template(basename: str) -> bool:
153
+ """Return True if the basename is a non-secret .env template file.
154
+
155
+ .env.example / .env.sample / .env.template / .env.dist are scaffolding
156
+ files checked into repos — they contain placeholder values, not real secrets.
157
+ """
158
+ lower = basename.lower()
159
+ if not lower.startswith(".env"):
160
+ return False
161
+ suffix = lower[4:] # everything after ".env"
162
+ return suffix in _ENV_TEMPLATE_SUFFIXES
163
+
164
+
165
+ def _keyword_in_segment(keyword: str, segment: str) -> bool:
166
+ """Return True if keyword appears as a delimited word within segment.
167
+
168
+ Boundaries are start/end of segment or a separator character (-, _, ., space).
169
+ Case-insensitive. Multi-word keywords like "auth-key" are matched as a unit.
170
+ """
171
+ seg_lower = segment.lower()
172
+ kw_lower = keyword.lower()
173
+ kw_len = len(kw_lower)
174
+ seg_len = len(seg_lower)
175
+
176
+ start = 0
177
+ while True:
178
+ idx = seg_lower.find(kw_lower, start)
179
+ if idx == -1:
180
+ break
181
+ # Check left boundary
182
+ left_ok = (idx == 0) or bool(_SEG_SEPS.match(seg_lower[idx - 1]))
183
+ # Check right boundary
184
+ right_idx = idx + kw_len
185
+ right_ok = (right_idx == seg_len) or bool(_SEG_SEPS.match(seg_lower[right_idx]))
186
+ if left_ok and right_ok:
187
+ return True
188
+ start = idx + 1
189
+ return False
190
+
191
+
192
+ def _is_credential_path(path_str: str) -> bool:
193
+ """Return True if path_str refers to a credential file.
194
+
195
+ Checks (in order):
196
+ 1. Explicit allow: non-secret .env template files (.env.example etc.)
197
+ 2. Basename against known credential file patterns.
198
+ 3. Every path segment for credential-related DELIMITED keywords.
199
+
200
+ This function ONLY inspects the path string — it never reads file contents.
201
+ """
202
+ if not path_str:
203
+ return False
204
+
205
+ # Normalise separators for segment splitting
206
+ normalised = path_str.replace("\\", "/")
207
+ segments = [s for s in normalised.split("/") if s]
208
+ if not segments:
209
+ return False
210
+
211
+ basename = segments[-1]
212
+
213
+ # 1. Allow non-secret .env templates before any deny pattern fires
214
+ if _is_env_template(basename):
215
+ return False
216
+
217
+ # 2. Basename pattern match
218
+ for pattern in _CREDENTIAL_BASENAME_PATTERNS:
219
+ if pattern.match(basename):
220
+ return True
221
+
222
+ # 3. Delimited keyword match in any segment
223
+ for seg in segments:
224
+ for keyword in _CREDENTIAL_SEGMENT_KEYWORDS:
225
+ if _keyword_in_segment(keyword, seg):
226
+ return True
227
+
228
+ return False
229
+
230
+
231
+ def _first_effective_command(tokens: list[str]) -> str:
232
+ """Return the basename of the first non-prefix, non-env-var token."""
233
+ i = 0
234
+ while i < len(tokens):
235
+ t = tokens[i]
236
+ # Skip VAR=value env-var assignments
237
+ if "=" in t and not t.startswith("-"):
238
+ i += 1
239
+ continue
240
+ # Strip absolute path prefix (/usr/bin/cat → cat)
241
+ cmd = t.rsplit("/", 1)[-1].rsplit("\\", 1)[-1]
242
+ if cmd in _COMMAND_PREFIXES:
243
+ i += 1
244
+ continue
245
+ # Handle `su -c` (next token is the command string, not a prefix)
246
+ if cmd == "su":
247
+ break
248
+ return cmd
249
+ return ""
250
+
251
+
252
+ def _strip_token_punctuation(token: str) -> str:
253
+ """Strip surrounding shell metacharacters that shlex may leave on a token.
254
+
255
+ Handles: leading < > ' " ( and trailing ) ' " so that tokens like
256
+ `'.env)'`, `<.env`, `"< .env`, `$(cat .env)` pass through to path check.
257
+ """
258
+ return token.strip("<>()'\"`")
259
+
260
+
261
+ def _command_contains_credential_path(command: str) -> bool:
262
+ """Return True if any path-like token or substring in the command is a credential.
263
+
264
+ Two-pass scan:
265
+ 1. shlex tokens (handles quoted paths, explicit < redirections)
266
+ 2. Raw regex scan of the whole command string (catches subshells, backticks,
267
+ no-space redirections like `<.env`, compound commands)
268
+ """
269
+ # Pass 1: shlex tokens
270
+ try:
271
+ import shlex
272
+ tokens = shlex.split(command)
273
+ except ValueError:
274
+ tokens = command.split()
275
+
276
+ for token in tokens:
277
+ cleaned = _strip_token_punctuation(token)
278
+ if cleaned and _is_credential_path(cleaned):
279
+ return True
280
+
281
+ # Pass 2: raw scan — extract all path-candidate substrings via regex.
282
+ # Matches sequences that look like a path (optional leading ./ or / or ~/,
283
+ # then word chars, dots, hyphens, underscores, slashes) and also bare
284
+ # no-space redirections like `<.env`, `if=.env`, and quoted paths like `'.env'`.
285
+ #
286
+ # Known non-goals (static string analysis cannot catch):
287
+ # - Variable-built paths: f=.env; cat $f (path not present as literal)
288
+ # - Glob obfuscation: cat .e* (glob expands at runtime)
289
+ # These are accepted limitations, not bugs.
290
+ for match in re.finditer(
291
+ r"(?:^|(?<=\s)|(?<=[<>|;&()`=']))([~./]?[\w./\-]+)",
292
+ command,
293
+ ):
294
+ candidate = _strip_token_punctuation(match.group(1))
295
+ if candidate and _is_credential_path(candidate):
296
+ return True
297
+
298
+ return False
299
+
300
+
301
+ def _bash_reads_credential(command: str) -> bool:
302
+ """Return True if the Bash command may expose credential file contents.
303
+
304
+ New deny boundary (defense-in-depth, correctness over leniency):
305
+
306
+ ALLOW only when the first effective command (after stripping prefixes) is a
307
+ pure existence/metadata op: test / [ / ls / stat / find / file.
308
+ These never surface file contents even when given a credential path.
309
+
310
+ DENY when a credential path appears ANYWHERE in the command AND the first
311
+ effective command is NOT a safe existence check.
312
+
313
+ Over-blocking writes/deletes (rm .env, git add .env, echo x >> .env) is
314
+ intentional — deny those too. Defense-in-depth beats precision here.
315
+
316
+ This function ONLY inspects the command string — it never reads file contents.
317
+ """
318
+ if not command:
319
+ return False
320
+
321
+ try:
322
+ import shlex
323
+ tokens = shlex.split(command)
324
+ except ValueError:
325
+ tokens = command.split()
326
+
327
+ if not tokens:
328
+ return False
329
+
330
+ first_cmd = _first_effective_command(tokens)
331
+
332
+ # Pure existence/metadata commands: always allow regardless of path
333
+ if first_cmd in _SAFE_EXISTENCE_COMMANDS:
334
+ return False
335
+
336
+ # For everything else: deny if a credential path appears anywhere
337
+ return _command_contains_credential_path(command)
338
+
339
+
340
+ def _deny_payload(reason: str) -> dict:
341
+ """Return the Claude Code PreToolUse deny payload."""
342
+ return {
343
+ "hookSpecificOutput": {
344
+ "hookEventName": "PreToolUse",
345
+ "permissionDecision": "deny",
346
+ "permissionDecisionReason": reason,
347
+ }
348
+ }
349
+
350
+
351
+ def evaluate_credential_access(
352
+ tool_name: str, tool_input: dict
353
+ ) -> Optional[dict]:
354
+ """Evaluate whether the tool call attempts to read a credential file.
355
+
356
+ Returns a deny payload dict if the call should be blocked, or None to allow.
357
+
358
+ This is a pure function with no side effects — it only inspects the
359
+ tool_name and tool_input strings and never reads file contents or logs values.
360
+
361
+ Deny cases:
362
+ - Read tool: file_path matches a credential pattern
363
+ - Bash tool: credential path appears anywhere in command AND first effective
364
+ command is not a safe existence check (test/ls/stat/find/file/[)
365
+ - Grep tool: path or glob targets a credential path
366
+
367
+ Allow cases:
368
+ - Read of non-credential paths (and .env.example/.env.sample templates)
369
+ - Bash with first effective command = test/ls/stat/find/file/[
370
+ - Bash with no credential path in the command
371
+ - Grep on non-credential paths
372
+ - Edit/Write/WebFetch — out of scope; not in matcher
373
+ - Any other unrecognized tool
374
+
375
+ Note: Edit and Write are intentionally excluded from the guard scope —
376
+ writing to a credential path is not the same threat as reading it into
377
+ model context. WebFetch is not path-based. Both are left to the prompt rule.
378
+ """
379
+ if tool_name == "Read":
380
+ path_str = tool_input.get("file_path", "")
381
+ if _is_credential_path(path_str):
382
+ return _deny_payload(
383
+ f"Credential guard: reading '{path_str}' is prohibited. "
384
+ "Use 'test -f' or 'ls' to verify file existence instead."
385
+ )
386
+ return None
387
+
388
+ if tool_name == "Bash":
389
+ command = tool_input.get("command", "")
390
+ if _bash_reads_credential(command):
391
+ return _deny_payload(
392
+ "Credential guard: the command may expose credential file contents. "
393
+ "Use 'test -f <path>' to check existence without exposing contents."
394
+ )
395
+ return None
396
+
397
+ if tool_name == "Grep":
398
+ # Grep can surface file contents; deny when path/glob targets a credential.
399
+ # tool_input fields used by Claude Code's Grep: "path" and/or "glob".
400
+ for field in ("path", "glob"):
401
+ val = tool_input.get(field, "")
402
+ if val and _is_credential_path(val):
403
+ return _deny_payload(
404
+ f"Credential guard: grep on '{val}' is prohibited. "
405
+ "Credential file contents must not be surfaced."
406
+ )
407
+ return None
408
+
409
+ # All other tools: allow (hook matcher scopes to Read|Bash|Grep)
410
+ return None
411
+
412
+
413
+ def _load_memory_index() -> Optional[str]:
414
+ """Load the agent-notes memory index content, or return None if unavailable.
415
+
416
+ Shared renderer used by both the SessionStart memory-bridge hook and the
417
+ PreCompact memory-bridge hook so both emit from one source of truth.
418
+ """
419
+ try:
420
+ from .memory._common import _load_memory_config
421
+ from ..constants import Obsidian, Wiki
422
+
423
+ backend, path = _load_memory_config()
424
+
425
+ if backend == "none" or backend is None:
426
+ return None
427
+
428
+ if backend == "obsidian":
429
+ index_file = Path(path) / Obsidian.INDEX
430
+ elif backend == "wiki":
431
+ index_file = Path(path) / Wiki.DIR / Wiki.INDEX
432
+ else:
433
+ # local and any unknown backends: use Index.md at root
434
+ index_file = Path(path) / "Index.md"
435
+
436
+ if not index_file.exists():
437
+ return None
438
+
439
+ return index_file.read_text(encoding="utf-8")
440
+ except Exception:
441
+ return None
442
+
443
+
444
+ def _memory_bridge() -> None:
445
+ """SessionStart hook that prints the agent-notes memory index.
446
+
447
+ Unconditionally loads and prints the memory index so it is visible in
448
+ context at the start of every Claude Code session.
449
+ """
450
+ content = _load_memory_index()
451
+ if content is None:
452
+ return
453
+ print("<!-- agent-notes memory index (auto-loaded) -->")
454
+ print(content)
455
+
456
+
457
+ def _precompact_memory_bridge() -> None:
458
+ """PreCompact hook that re-emits the memory index before context compaction.
459
+
460
+ When Claude Code compacts a long conversation the SessionStart context
461
+ injected by memory-bridge can be summarised away, losing the pointer to
462
+ durable memory. This hook fires immediately before compaction and returns
463
+ the index via the `additionalContext` field in the hook JSON output schema
464
+ (see docs/CLI_CAPABILITIES.md §Hooks → "JSON output schema"). Claude Code
465
+ merges `additionalContext` into the compacted context, keeping the memory
466
+ pointer alive.
467
+
468
+ Contract: exit 0, emit a single JSON object to stdout:
469
+ {"additionalContext": "<header>\\n<index content>"}
470
+ If the memory index is unavailable (no backend configured, no index file)
471
+ the hook exits silently with no output so compaction proceeds normally.
472
+ """
473
+ content = _load_memory_index()
474
+ if content is None:
475
+ return
476
+ payload = {
477
+ "additionalContext": "<!-- agent-notes memory index (auto-loaded) -->\n" + content,
478
+ }
479
+ print(json.dumps(payload))