obsidian-knowledge 3.26.2__tar.gz → 3.26.4__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.
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/.claude-plugin/marketplace.json +1 -1
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/.claude-plugin/plugin.json +1 -1
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/.codex-plugin/plugin.json +1 -1
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/PKG-INFO +2 -1
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/docs/ARCHITECTURE.md +6 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/lib/vault_index/indexer.py +7 -16
- obsidian_knowledge-3.26.4/lib/vault_index/vector_search.py +104 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/.codex-plugin/plugin.json +1 -1
- obsidian_knowledge-3.26.4/plugins/obsidian-knowledge/skills/vault-organizer/SKILL.md +98 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4/plugins/obsidian-knowledge}/skills/vault-organizer/convention-sweep.py +14 -32
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/skills/vault-organizer/edit-index.py +9 -4
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4/plugins/obsidian-knowledge}/skills/vault-organizer/filter-unresolved-links.py +14 -15
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4/plugins/obsidian-knowledge}/skills/vault-organizer/find-open-questions.py +20 -24
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4/plugins/obsidian-knowledge}/skills/vault-organizer/fix-stacked-frontmatter.py +7 -3
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4/plugins/obsidian-knowledge}/skills/vault-organizer/lib/broken-links.md +4 -15
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4/plugins/obsidian-knowledge}/skills/vault-organizer/lib/index-format.md +7 -12
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/skills/vault-organizer/lib/rename-files.md +2 -4
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4/plugins/obsidian-knowledge}/skills/vault-organizer/lib/stacked-frontmatter.md +2 -2
- obsidian_knowledge-3.26.4/plugins/obsidian-knowledge/skills/vault-organizer/organizer_context.py +30 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4/plugins/obsidian-knowledge}/skills/vault-organizer/recover-unresolved-links.py +12 -16
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4/plugins/obsidian-knowledge}/skills/vault-organizer/vault-audit.py +13 -18
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/pyproject.toml +2 -1
- obsidian_knowledge-3.26.4/skills/vault-organizer/SKILL.md +98 -0
- {obsidian_knowledge-3.26.2/plugins/obsidian-knowledge → obsidian_knowledge-3.26.4}/skills/vault-organizer/convention-sweep.py +14 -32
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/skills/vault-organizer/edit-index.py +9 -4
- {obsidian_knowledge-3.26.2/plugins/obsidian-knowledge → obsidian_knowledge-3.26.4}/skills/vault-organizer/filter-unresolved-links.py +14 -15
- {obsidian_knowledge-3.26.2/plugins/obsidian-knowledge → obsidian_knowledge-3.26.4}/skills/vault-organizer/find-open-questions.py +20 -24
- {obsidian_knowledge-3.26.2/plugins/obsidian-knowledge → obsidian_knowledge-3.26.4}/skills/vault-organizer/fix-stacked-frontmatter.py +7 -3
- {obsidian_knowledge-3.26.2/plugins/obsidian-knowledge → obsidian_knowledge-3.26.4}/skills/vault-organizer/lib/broken-links.md +4 -15
- {obsidian_knowledge-3.26.2/plugins/obsidian-knowledge → obsidian_knowledge-3.26.4}/skills/vault-organizer/lib/index-format.md +7 -12
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/skills/vault-organizer/lib/rename-files.md +2 -4
- {obsidian_knowledge-3.26.2/plugins/obsidian-knowledge → obsidian_knowledge-3.26.4}/skills/vault-organizer/lib/stacked-frontmatter.md +2 -2
- obsidian_knowledge-3.26.4/skills/vault-organizer/organizer_context.py +30 -0
- {obsidian_knowledge-3.26.2/plugins/obsidian-knowledge → obsidian_knowledge-3.26.4}/skills/vault-organizer/recover-unresolved-links.py +12 -16
- {obsidian_knowledge-3.26.2/plugins/obsidian-knowledge → obsidian_knowledge-3.26.4}/skills/vault-organizer/vault-audit.py +13 -18
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_gardener_helpers.py +78 -0
- obsidian_knowledge-3.26.4/tests/test_vector_latency.py +176 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/uv.lock +4 -2
- obsidian_knowledge-3.26.2/plugins/obsidian-knowledge/skills/vault-organizer/SKILL.md +0 -166
- obsidian_knowledge-3.26.2/skills/vault-organizer/SKILL.md +0 -166
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/.agents/plugins/marketplace.json +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/.claude/agents.red-green-tdd.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/.claude/hookify.doc-code-coupling.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/.claude/hookify.no-junk-drawers.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/.claude/hookify.parse-dont-validate.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/.claude/hookify.require-plugin-version-bump.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/.claude/hookify.semantic-types.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/.claude/hookify.taste-enforcer.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/.github/workflows/ci.yml +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/.github/workflows/docs.yml +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/.github/workflows/publish.yml +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/.gitignore +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/.secrets.baseline +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/AGENTS.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/CONVENTIONS.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/LICENSE +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/README.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/commands/scan-secrets.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/docs/.pages +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/docs/CLI.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/docs/MCP.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/docs/QUALITY.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/docs/TODO.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/docs/configuration.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/docs/contributing-docs.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/docs/hooks.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/docs/index.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/docs/superpowers/plans/2026-04-25-improve-harness-implementation.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/docs/superpowers/plans/2026-05-12-changelog-reform.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/docs/superpowers/specs/2026-04-25-improve-harness-design.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/docs/superpowers/specs/2026-05-12-changelog-reform-design.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/capture-session.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/codex-hooks.json +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/doctor.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/hookslib/__init__.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/hookslib/capture.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/hookslib/patterns.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/hookslib/recall_init_lib.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/hookslib/reflect_counter.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/hookslib/repo_memory.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/hookslib/secret_filters.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/hookslib/shell_guard.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/hookslib/stop_hook.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/hookslib/transcript.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/hookslib/vault_config.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/hookslib/vault_policy.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/i-insist.toml +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/i_insist.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/nudge-index-sync.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/recall-init.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/reflect-nudge.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/remind-convos.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/remind-convos.sh +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/scan-vault-secrets.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/update-changelog.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/update-changelog.sh +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/hooks/vault_registry.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/lib/__init__.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/lib/vault_index/__init__.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/lib/vault_index/cli.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/lib/vault_index/config.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/lib/vault_index/filters.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/lib/vault_index/guard_install.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/lib/vault_index/mcp_server.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/lib/vault_index/models.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/lib/vault_index/papercuts.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/lib/vault_index/primer.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/lib/vault_index/registration.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/lib/vault_index/vault_files.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/mkdocs.yml +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/commands/scan-secrets.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/capture-session.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/doctor.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/hooks.json +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/hookslib/__init__.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/hookslib/capture.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/hookslib/patterns.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/hookslib/recall_init_lib.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/hookslib/reflect_counter.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/hookslib/repo_memory.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/hookslib/secret_filters.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/hookslib/shell_guard.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/hookslib/stop_hook.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/hookslib/transcript.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/hookslib/vault_config.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/hookslib/vault_policy.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/i-insist.toml +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/i_insist.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/nudge-index-sync.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/recall-init.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/reflect-nudge.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/remind-convos.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/remind-convos.sh +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/scan-vault-secrets.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/update-changelog.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/update-changelog.sh +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/hooks/vault_registry.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/skills/obsidian-knowledge/SKILL.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/skills/remember-conversations/SKILL.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/skills/vault-organizer/lib/note-types.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/skills/vault-organizer/lib/state-files.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/skills/vault-organizer/link_recovery_models.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/plugins/obsidian-knowledge/skills/vault-organizer/organizer_io.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/prek.toml +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/scripts/build_memory_indexes.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/scripts/cli_smoke_test.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/scripts/migrate_changelog.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/scripts/migrate_claude_memory.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/scripts/new-worktree.sh +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/scripts/prek_hooks/check_architecture.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/scripts/prek_hooks/check_exception_handling.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/scripts/prek_hooks/check_file_length.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/scripts/prek_hooks/check_private_test_imports.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/scripts/prek_hooks/check_timeless_comments.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/scripts/prepare_release.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/scripts/smoke_tool_install.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/scripts/sync_codex_plugin.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/skills/obsidian-knowledge/SKILL.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/skills/remember-conversations/SKILL.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/skills/vault-organizer/lib/note-types.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/skills/vault-organizer/lib/state-files.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/skills/vault-organizer/link_recovery_models.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/skills/vault-organizer/organizer_io.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/__init__.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/conftest.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/fixtures/sample_vault/.claude/obsidian-knowledge.yaml +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/fixtures/sample_vault/Inbox/random.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/fixtures/sample_vault/Journal/diary.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/fixtures/sample_vault/wiki/python.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/fixtures/sample_vault/wiki/rust.md +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/fixtures/transcript_new_file_no_index.jsonl +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/fixtures/transcript_new_file_with_index.jsonl +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_access_errors.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_architecture_check.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_capture_instructions.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_capture_session_hook.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_cli.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_cli_dispatch.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_cli_reliability.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_cli_smoke.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_config.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_doctor.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_filters.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_first_install.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_i_insist_rules.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_indexer.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_indexer_reliability.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_mcp.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_mcp_download.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_migrate_changelog.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_nudge_index_sync.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_papercut_cli.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_papercuts.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_patterns.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_primer.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_protect_vault_destructive_ops.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_public_api.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_recall_init.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_recall_init_lib.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_recover_unresolved_links.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_reflect_counter.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_reflect_nudge.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_release.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_repo_memory.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_scan_vault_secrets.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_search_json.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_secret_scan_filters.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_session_debounce.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_transcript.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_vault_boundaries.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_vault_files.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_vault_policy.py +0 -0
- {obsidian_knowledge-3.26.2 → obsidian_knowledge-3.26.4}/tests/test_vault_registry.py +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: obsidian-knowledge
|
|
3
|
-
Version: 3.26.
|
|
3
|
+
Version: 3.26.4
|
|
4
4
|
Summary: Obsidian vault knowledge integration for Claude Code and Codex
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
7
|
+
Requires-Dist: aiosqlite<1,>=0.20
|
|
7
8
|
Requires-Dist: beartype>=0.22.9
|
|
8
9
|
Requires-Dist: botocore>=1.43.10
|
|
9
10
|
Requires-Dist: httpx[socks]>=0.28.1
|
|
@@ -22,6 +22,12 @@ weights, and `indexer.py` wraps memweave's keyword and dense retrieval.
|
|
|
22
22
|
file operations and bounded CLI search subprocesses over Streamable HTTP. Importing `lib` enables
|
|
23
23
|
beartype runtime checks.
|
|
24
24
|
|
|
25
|
+
`vector_search.py` registers a memweave hybrid strategy using sqlite-vec's native
|
|
26
|
+
nearest-neighbor query. Memweave normalizes embeddings, so L2 candidate ordering
|
|
27
|
+
matches cosine ordering; the strategy keeps the original cosine scores and
|
|
28
|
+
keyword merge. Model and source filters apply before candidate selection. Pools
|
|
29
|
+
above sqlite-vec's 4096-candidate ceiling retain memweave's exhaustive search.
|
|
30
|
+
|
|
25
31
|
The wheel installs `hooks/vault_registry.py` as the top-level `vault_registry`
|
|
26
32
|
module so hooks and the CLI share one registry implementation.
|
|
27
33
|
|
|
@@ -63,6 +63,7 @@ import platformdirs
|
|
|
63
63
|
from lib.vault_index.config import VaultIndexConfig
|
|
64
64
|
from lib.vault_index.filters import path_passes
|
|
65
65
|
from lib.vault_index.models import Hit, IndexBusyError, SearchReport
|
|
66
|
+
from lib.vault_index.vector_search import IndexedHybridSearch, KeywordOnlyEmbeddingProvider
|
|
66
67
|
|
|
67
68
|
# Local embeddings do not need a public model-pricing download. LiteLLM reads
|
|
68
69
|
# this at its first lazy import; preserve an explicit operator override.
|
|
@@ -153,20 +154,6 @@ class SyncStats:
|
|
|
153
154
|
deleted: int
|
|
154
155
|
|
|
155
156
|
|
|
156
|
-
class KeywordOnlyEmbeddingProvider:
|
|
157
|
-
"""Keep memweave's indexing pipeline offline when vectors are disabled.
|
|
158
|
-
|
|
159
|
-
memweave 0.2 still calls its provider with vector.enabled=False. Empty
|
|
160
|
-
vectors retain chunks and FTS entries without caching fake embeddings.
|
|
161
|
-
"""
|
|
162
|
-
|
|
163
|
-
async def embed_query(self, text: str) -> list[float]:
|
|
164
|
-
return []
|
|
165
|
-
|
|
166
|
-
async def embed_batch(self, texts: list[str]) -> list[list[float]]:
|
|
167
|
-
return [[] for _ in texts]
|
|
168
|
-
|
|
169
|
-
|
|
170
157
|
@contextlib.contextmanager
|
|
171
158
|
def index_lock(cache_dir: Path, *, exclusive: bool, blocking: bool = False):
|
|
172
159
|
"""Hold the per-vault SQLite access lock.
|
|
@@ -261,10 +248,14 @@ class Indexer:
|
|
|
261
248
|
return self._vector_enabled
|
|
262
249
|
|
|
263
250
|
def _make_store(self, extra_paths: list[str]) -> memweave.MemWeave:
|
|
264
|
-
|
|
265
|
-
|
|
251
|
+
config = self._make_config(extra_paths=extra_paths)
|
|
252
|
+
store = memweave.MemWeave(
|
|
253
|
+
config,
|
|
266
254
|
embedding_provider=None if self._vector_enabled else KeywordOnlyEmbeddingProvider(),
|
|
267
255
|
)
|
|
256
|
+
if self._vector_enabled:
|
|
257
|
+
store.register_strategy("hybrid", IndexedHybridSearch(config.query.hybrid))
|
|
258
|
+
return store
|
|
268
259
|
|
|
269
260
|
def _embedder_fingerprint(self) -> str:
|
|
270
261
|
"""Stable string identifying the current embedding setup.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Use sqlite-vec's native nearest-neighbor scan for hybrid retrieval."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sqlite3
|
|
6
|
+
import struct
|
|
7
|
+
|
|
8
|
+
import aiosqlite
|
|
9
|
+
from memweave import HybridConfig
|
|
10
|
+
from memweave.search.hybrid import HybridSearch, merge_hybrid_results
|
|
11
|
+
from memweave.search.keyword import KeywordSearch
|
|
12
|
+
from memweave.search.strategy import RawSearchRow
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class IndexedHybridSearch:
|
|
16
|
+
"""Preserve memweave's hybrid scoring without fetching every vector into SQL."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, config: HybridConfig):
|
|
19
|
+
self.config = config
|
|
20
|
+
|
|
21
|
+
async def search(
|
|
22
|
+
self,
|
|
23
|
+
db: aiosqlite.Connection,
|
|
24
|
+
query: str,
|
|
25
|
+
query_vec: list[float] | None,
|
|
26
|
+
model: str,
|
|
27
|
+
limit: int,
|
|
28
|
+
*,
|
|
29
|
+
source_filter: str | None = None,
|
|
30
|
+
) -> list[RawSearchRow]:
|
|
31
|
+
keyword = KeywordSearch()
|
|
32
|
+
if query_vec is None:
|
|
33
|
+
try:
|
|
34
|
+
await db.execute("SELECT vec_version()")
|
|
35
|
+
except sqlite3.OperationalError as exc:
|
|
36
|
+
if "no such function" not in str(exc):
|
|
37
|
+
raise
|
|
38
|
+
return await keyword.search(db, query, None, model, limit, source_filter=source_filter)
|
|
39
|
+
raise ValueError("VectorSearch requires a query_vec (got None)")
|
|
40
|
+
|
|
41
|
+
# NOTE: docs/ARCHITECTURE.md documents normalization and the k ceiling.
|
|
42
|
+
# Match HybridSearch's backend pool. sqlite-vec caps native k at 4096;
|
|
43
|
+
# unusually large requests retain memweave's exhaustive behavior.
|
|
44
|
+
fallback = HybridSearch(vector_weight=self.config.vector_weight, text_weight=self.config.text_weight)
|
|
45
|
+
pool = limit * fallback.candidate_multiplier
|
|
46
|
+
if pool > 4096:
|
|
47
|
+
return await fallback.search(db, query, query_vec, model, limit, source_filter=source_filter)
|
|
48
|
+
|
|
49
|
+
blob = struct.pack(f"<{len(query_vec)}f", *query_vec)
|
|
50
|
+
params: list[object] = [blob, pool, model, source_filter or None, source_filter or None, blob]
|
|
51
|
+
# memweave normalizes stored and query vectors: L2 and cosine rank
|
|
52
|
+
# identically. Compute original cosine scores on the bounded pool.
|
|
53
|
+
# Filter inside KNN so other models/sources cannot exhaust its pool.
|
|
54
|
+
sql = """
|
|
55
|
+
WITH nearest AS MATERIALIZED (
|
|
56
|
+
SELECT id, embedding, distance FROM chunks_vec
|
|
57
|
+
WHERE embedding MATCH ? AND k = ?
|
|
58
|
+
AND id IN (
|
|
59
|
+
SELECT id FROM chunks WHERE model = ? AND (? IS NULL OR source = ?)
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
SELECT c.id, c.path, c.source, c.start_line, c.end_line, c.text,
|
|
63
|
+
1 - vec_distance_cosine(n.embedding, ?) AS score
|
|
64
|
+
FROM nearest n JOIN chunks c ON c.id = n.id
|
|
65
|
+
ORDER BY score DESC
|
|
66
|
+
"""
|
|
67
|
+
vector_rows = []
|
|
68
|
+
async with db.execute(sql, params) as cursor:
|
|
69
|
+
async for row in cursor:
|
|
70
|
+
chunk_id, path, source, start_line, end_line, text, score = row
|
|
71
|
+
vector_rows.append(
|
|
72
|
+
RawSearchRow(
|
|
73
|
+
chunk_id=chunk_id,
|
|
74
|
+
path=path,
|
|
75
|
+
source=source,
|
|
76
|
+
start_line=start_line,
|
|
77
|
+
end_line=end_line,
|
|
78
|
+
text=text,
|
|
79
|
+
score=score,
|
|
80
|
+
vector_score=score,
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
keyword_rows = await keyword.search(db, query, None, model, pool, source_filter=source_filter)
|
|
84
|
+
return merge_hybrid_results(
|
|
85
|
+
vector_rows,
|
|
86
|
+
keyword_rows,
|
|
87
|
+
vector_weight=self.config.vector_weight,
|
|
88
|
+
text_weight=self.config.text_weight,
|
|
89
|
+
limit=limit,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class KeywordOnlyEmbeddingProvider:
|
|
94
|
+
"""Keep memweave's indexing pipeline offline when vectors are disabled.
|
|
95
|
+
|
|
96
|
+
memweave 0.2 still calls its provider with vector.enabled=False. Empty
|
|
97
|
+
vectors retain chunks and FTS entries without caching fake embeddings.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
async def embed_query(self, text: str) -> list[float]:
|
|
101
|
+
return []
|
|
102
|
+
|
|
103
|
+
async def embed_batch(self, texts: list[str]) -> list[list[float]]:
|
|
104
|
+
return [[] for _ in texts]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obsidian-knowledge",
|
|
3
|
-
"version": "3.26.
|
|
3
|
+
"version": "3.26.4+codex.20261003154117",
|
|
4
4
|
"description": "Plugin that manages an Obsidian knowledge vault: capture, organize, index, and garden.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Ricardo Decal"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vault-organizer
|
|
3
|
+
description: >-
|
|
4
|
+
This skill should be used when the user asks to "organize the vault",
|
|
5
|
+
"update indexes", "fix broken links", "rename ambiguous files", "fix
|
|
6
|
+
filenames", "garden the vault", "sync indexes", "clean up the vault",
|
|
7
|
+
"maintain the vault", or after making substantial structural edits
|
|
8
|
+
(creating, moving, renaming, or deleting files) in an Obsidian vault.
|
|
9
|
+
Also triggered by scheduled cron invocations for routine vault maintenance.
|
|
10
|
+
version: 1.4.12
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Vault organizer
|
|
14
|
+
|
|
15
|
+
Maintain indexes, links, filenames, and note locations. Preserve primary note
|
|
16
|
+
content except for intended link and frontmatter repairs. Read each `lib/`
|
|
17
|
+
reference only when its step applies.
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
Set `SCRIPTS` to the directory containing the loaded `SKILL.md`. Use that copy,
|
|
22
|
+
not an arbitrary plugin cache or the working directory. Scripts declare their
|
|
23
|
+
own dependencies; run them with `uv run`.
|
|
24
|
+
|
|
25
|
+
Scripts default to the configured vault containing cwd, or the sole registered
|
|
26
|
+
vault. Pass a vault root as the first argument to override it. When multiple
|
|
27
|
+
vaults are configured and cwd selects none, an explicit root is required.
|
|
28
|
+
|
|
29
|
+
Read the vault's local instructions, `.claude/obsidian-knowledge.yaml`, and
|
|
30
|
+
`Utility/obsidian-knowledge/needs-attention.md`. Set `VAULT_NAME` to its registered
|
|
31
|
+
Obsidian name. Keep `vault="$VAULT_NAME"` before every Obsidian subcommand; after
|
|
32
|
+
it, the CLI can silently ignore the option. Enable automatic internal-link
|
|
33
|
+
updates in Obsidian.
|
|
34
|
+
|
|
35
|
+
Exclude hidden files, protected sources, and Syncthing sync conflicts from
|
|
36
|
+
maintenance. Handle conflicts separately. Use `obsidian-knowledge write` for
|
|
37
|
+
manual Markdown edits; its writer verifies the result. Helpers already use it.
|
|
38
|
+
|
|
39
|
+
## Structure
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uv run "$SCRIPTS/vault-audit.py"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Triage all findings:
|
|
46
|
+
|
|
47
|
+
- `MISSING_INDEX`, `NOT_INDEXED`: create or complete indexes using
|
|
48
|
+
[index conventions](lib/index-format.md). Read children to write useful
|
|
49
|
+
orientation phrases; a descendant link does not replace a child-index link.
|
|
50
|
+
- `EMPTY_FOLDER`: check sync state and local layout. Do not create empty indexes
|
|
51
|
+
or delete folders automatically.
|
|
52
|
+
- `DUMPING_GROUND`: classify misplaced dated, design, diary, and conversation
|
|
53
|
+
notes by meaning. Follow [note locations](lib/note-types.md).
|
|
54
|
+
- `STACKED_FRONTMATTER`: use `uv run "$SCRIPTS/fix-stacked-frontmatter.py" --fix NOTE_PATH`.
|
|
55
|
+
`NEEDS_MERGE` requires [manual frontmatter repair](lib/stacked-frontmatter.md).
|
|
56
|
+
|
|
57
|
+
Rename ambiguous files using [rename guidance](lib/rename-files.md).
|
|
58
|
+
Use Obsidian move and rename commands so internal links update.
|
|
59
|
+
|
|
60
|
+
## Links
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
obsidian vault="$VAULT_NAME" unresolved verbose format=json | uv run "$SCRIPTS/recover-unresolved-links.py"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Inspect the report. Add `--apply` for unique recoveries; triage remaining links
|
|
67
|
+
using [broken-link guidance](lib/broken-links.md). Preserve aliases, headings,
|
|
68
|
+
block references, and primary text. Leave intentional concept stubs intact.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
obsidian vault="$VAULT_NAME" orphans
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Add managed notes to their parent indexes where appropriate. Respect established
|
|
75
|
+
exceptions and ignore orphans outside managed zones.
|
|
76
|
+
|
|
77
|
+
## Conventions and reports
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
uv run "$SCRIPTS/convention-sweep.py"
|
|
81
|
+
uv run "$SCRIPTS/find-open-questions.py" --apply
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Fix `WIKILINK_EXT` (`.md` in note links), `UNDATED_FILE` (missing required date
|
|
85
|
+
prefix), and `YAML_ERR`. The question report defaults to
|
|
86
|
+
`Utility/obsidian-knowledge/reports/open-questions.md`, preserving its header,
|
|
87
|
+
frontmatter, and scope. Use `--report` to preview or `--timestamp` to override
|
|
88
|
+
current local time. With no flags, the scanner emits TSV.
|
|
89
|
+
|
|
90
|
+
## Finish
|
|
91
|
+
|
|
92
|
+
Re-run affected checks. Review changed links and fresh CLI results; reload
|
|
93
|
+
Obsidian only if its cache disagrees with file contents. No checksum comparison
|
|
94
|
+
is needed for an ordinary CLI move or rename.
|
|
95
|
+
|
|
96
|
+
Update unresolved items in `needs-attention.md` and record completed vault
|
|
97
|
+
changes in one same-session changelog fragment. Follow [state-file conventions](lib/state-files.md);
|
|
98
|
+
do not edit a shared changelog index. Skip logging when no vault changes occurred.
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env -S uv run --script
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.12"
|
|
4
|
+
# dependencies = ["pyyaml"]
|
|
5
|
+
# ///
|
|
2
6
|
"""convention-sweep: vault-wide check for write-time convention violations.
|
|
3
7
|
|
|
4
|
-
Usage:
|
|
8
|
+
Usage: uv run convention-sweep.py [vault_root]
|
|
5
9
|
|
|
6
10
|
Walks all `.md` files under <vault_root> (skipping dotfolders, _sources/,
|
|
7
11
|
.trash/, node_modules/) and runs the same three checks as the
|
|
@@ -22,31 +26,14 @@ needs-attention.md entry format.
|
|
|
22
26
|
|
|
23
27
|
from __future__ import annotations
|
|
24
28
|
|
|
25
|
-
import
|
|
29
|
+
import argparse
|
|
26
30
|
from pathlib import Path
|
|
27
31
|
|
|
32
|
+
from organizer_context import resolve_vault
|
|
28
33
|
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
# Source checkout layout is:
|
|
33
|
-
# <repo>/plugins/obsidian-knowledge/hooks/hookslib/patterns.py
|
|
34
|
-
# <repo>/skills/vault-organizer/convention-sweep.py
|
|
35
|
-
def _find_hooks_dir() -> Path:
|
|
36
|
-
here = Path(__file__).resolve()
|
|
37
|
-
candidates = [
|
|
38
|
-
here.parent.parent.parent / "hooks",
|
|
39
|
-
here.parent.parent.parent / "plugins" / "obsidian-knowledge" / "hooks",
|
|
40
|
-
]
|
|
41
|
-
for candidate in candidates:
|
|
42
|
-
if (candidate / "hookslib" / "patterns.py").exists():
|
|
43
|
-
return candidate
|
|
44
|
-
raise ModuleNotFoundError("Could not locate hooks/hookslib/patterns.py from convention-sweep.py")
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
sys.path.insert(0, str(_find_hooks_dir()))
|
|
48
|
-
|
|
49
|
-
from hookslib.patterns import ( # noqa: E402
|
|
34
|
+
# isort: split
|
|
35
|
+
# organizer_context puts the plugin's hooks on sys.path before this import.
|
|
36
|
+
from hookslib.patterns import (
|
|
50
37
|
DATE_PREFIX_RE,
|
|
51
38
|
PERIODIC_NOTE_RE,
|
|
52
39
|
find_wikilink_ext_violations,
|
|
@@ -111,14 +98,9 @@ def print_header(lib_dir: Path, counts: dict[str, int]) -> None:
|
|
|
111
98
|
|
|
112
99
|
|
|
113
100
|
def main() -> None:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
vault_root = Path(sys.argv[1]).resolve()
|
|
119
|
-
if not vault_root.is_dir():
|
|
120
|
-
print(f"Error: {vault_root} is not a directory", file=sys.stderr)
|
|
121
|
-
sys.exit(1)
|
|
101
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
102
|
+
parser.add_argument("vault_root", type=Path, nargs="?", help="default: configured vault")
|
|
103
|
+
vault_root = resolve_vault(parser.parse_args().vault_root)
|
|
122
104
|
|
|
123
105
|
lib_dir = Path(__file__).parent / "lib"
|
|
124
106
|
issues = sweep(vault_root)
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env -S uv run --script
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.12"
|
|
4
|
+
# dependencies = ["pyyaml", "pydantic>=2"]
|
|
5
|
+
# ///
|
|
2
6
|
"""Render reviewed JSON entries into one index section; --apply verifies writes.
|
|
3
7
|
|
|
4
|
-
Usage: edit-index.py VAULT wiki/topic/index.md [--apply] < reviewed.json
|
|
8
|
+
Usage: uv run edit-index.py [VAULT] wiki/topic/index.md [--apply] < reviewed.json
|
|
5
9
|
Input: {"section": "Notes", "entries": [{"target": "wiki/topic/note",
|
|
6
10
|
"description": "orientation", "label": "optional display name"}]}
|
|
7
11
|
For a new flat index, supply "title" instead of "section". Existing prose and
|
|
@@ -15,6 +19,7 @@ import re
|
|
|
15
19
|
import sys
|
|
16
20
|
from pathlib import Path
|
|
17
21
|
|
|
22
|
+
from organizer_context import resolve_vault
|
|
18
23
|
from organizer_io import OrganizerPolicy, visible_path, write_checked
|
|
19
24
|
from pydantic import BaseModel, ConfigDict, field_validator
|
|
20
25
|
|
|
@@ -156,12 +161,12 @@ def render_index(vault: Path, index: Path, before: str | None, review: IndexRevi
|
|
|
156
161
|
|
|
157
162
|
def main() -> None:
|
|
158
163
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
159
|
-
parser.add_argument("vault_root", type=Path)
|
|
164
|
+
parser.add_argument("vault_root", type=Path, nargs="?", help="default: configured vault")
|
|
160
165
|
parser.add_argument("index_path")
|
|
161
166
|
parser.add_argument("--apply", action="store_true")
|
|
162
167
|
args = parser.parse_args()
|
|
163
168
|
try:
|
|
164
|
-
vault = args.vault_root
|
|
169
|
+
vault = resolve_vault(args.vault_root)
|
|
165
170
|
if Path(args.index_path).name != "index.md":
|
|
166
171
|
raise ValueError("edit-index only edits index.md")
|
|
167
172
|
index = OrganizerPolicy.load(vault).writable(vault, args.index_path)
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env -S uv run --script
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.12"
|
|
4
|
+
# dependencies = ["pyyaml"]
|
|
5
|
+
# ///
|
|
2
6
|
"""filter-unresolved-links: filter `obsidian unresolved` JSON output.
|
|
3
7
|
|
|
4
8
|
Usage:
|
|
5
|
-
obsidian unresolved verbose format=json |
|
|
9
|
+
obsidian unresolved verbose format=json | uv run filter-unresolved-links.py [vault_root]
|
|
6
10
|
|
|
7
11
|
Reads `stub_link_patterns:` from <vault_root>/.claude/obsidian-knowledge.yaml
|
|
8
12
|
to know which link names are intentional concept-stubs (not real broken
|
|
@@ -20,22 +24,19 @@ Emits the surviving entries one per line:
|
|
|
20
24
|
|
|
21
25
|
from __future__ import annotations
|
|
22
26
|
|
|
27
|
+
import argparse
|
|
23
28
|
import json
|
|
24
29
|
import re
|
|
25
30
|
import signal
|
|
26
31
|
import sys
|
|
27
32
|
from pathlib import Path
|
|
28
33
|
|
|
34
|
+
import yaml
|
|
35
|
+
from organizer_context import resolve_vault
|
|
36
|
+
|
|
29
37
|
# Don't crash with a BrokenPipeError when stdout is piped to head/less.
|
|
30
38
|
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
|
31
39
|
|
|
32
|
-
try:
|
|
33
|
-
import yaml
|
|
34
|
-
|
|
35
|
-
HAS_YAML = True
|
|
36
|
-
except ImportError:
|
|
37
|
-
HAS_YAML = False
|
|
38
|
-
|
|
39
40
|
DEFAULT_STUB_PATTERNS = [
|
|
40
41
|
r"^\(PAPER\) ",
|
|
41
42
|
r"^\(VIDEO\) ",
|
|
@@ -56,7 +57,7 @@ def load_config(vault_root: Path) -> tuple[list[str], list[re.Pattern]]:
|
|
|
56
57
|
patterns_raw = DEFAULT_STUB_PATTERNS
|
|
57
58
|
|
|
58
59
|
config_path = vault_root / ".claude" / "obsidian-knowledge.yaml"
|
|
59
|
-
if
|
|
60
|
+
if config_path.exists():
|
|
60
61
|
with open(config_path) as f:
|
|
61
62
|
cfg = yaml.safe_load(f) or {}
|
|
62
63
|
zones = cfg.get("ai_managed", zones)
|
|
@@ -73,11 +74,9 @@ def is_stub(link: str, patterns: list[re.Pattern]) -> bool:
|
|
|
73
74
|
|
|
74
75
|
|
|
75
76
|
def main() -> None:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
vault_root = Path(sys.argv[1]).resolve()
|
|
77
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
78
|
+
parser.add_argument("vault_root", type=Path, nargs="?", help="default: configured vault")
|
|
79
|
+
vault_root = resolve_vault(parser.parse_args().vault_root)
|
|
81
80
|
zones, patterns = load_config(vault_root)
|
|
82
81
|
zone_prefixes = tuple(f"{z}/" for z in zones)
|
|
83
82
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env -S uv run --script
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.12"
|
|
4
|
+
# dependencies = ["pyyaml", "pydantic>=2"]
|
|
5
|
+
# ///
|
|
2
6
|
"""find-open-questions: scan ai_managed zones for `> [!question]` callouts.
|
|
3
7
|
|
|
4
|
-
Usage:
|
|
8
|
+
Usage: uv run find-open-questions.py [vault_root]
|
|
5
9
|
|
|
6
10
|
Skips matches inside fenced code blocks (``` or ~~~), which are usually
|
|
7
11
|
documentation examples rather than real open questions. Emits one line per
|
|
@@ -9,8 +13,8 @@ hit:
|
|
|
9
13
|
|
|
10
14
|
<relative_path>\t<line_number>\t<question_text>
|
|
11
15
|
|
|
12
|
-
Add --report
|
|
13
|
-
|
|
16
|
+
Add --report to render the standard Markdown report, --timestamp for
|
|
17
|
+
reproducible output, or --apply to regenerate the report in place.
|
|
14
18
|
Reads ai_managed zones from
|
|
15
19
|
<vault_root>/.claude/obsidian-knowledge.yaml; falls back to ['wiki'].
|
|
16
20
|
"""
|
|
@@ -23,18 +27,15 @@ import sys
|
|
|
23
27
|
from datetime import datetime
|
|
24
28
|
from pathlib import Path
|
|
25
29
|
|
|
30
|
+
import yaml
|
|
31
|
+
from organizer_context import resolve_vault
|
|
26
32
|
from organizer_io import visible_path, write_checked
|
|
27
33
|
from pydantic import BaseModel, ConfigDict
|
|
28
34
|
|
|
29
|
-
try:
|
|
30
|
-
import yaml
|
|
31
|
-
|
|
32
|
-
HAS_YAML = True
|
|
33
|
-
except ImportError:
|
|
34
|
-
HAS_YAML = False
|
|
35
|
-
|
|
36
35
|
QUESTION_MARKER = "> [!question]"
|
|
37
36
|
SCAN_SKIP_DIR_NAMES = {"_sources", ".trash", "node_modules"}
|
|
37
|
+
# NOTE: Standard report location is documented in SKILL.md, Conventions and reports.
|
|
38
|
+
REPORT_PATH = "Utility/obsidian-knowledge/reports/open-questions.md"
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
class QuestionHit(BaseModel):
|
|
@@ -79,7 +80,7 @@ def render_report(before: str | None, hits: tuple[QuestionHit, ...], timestamp:
|
|
|
79
80
|
|
|
80
81
|
def load_managed_zones(vault_root: Path) -> list[str]:
|
|
81
82
|
config_path = vault_root / ".claude" / "obsidian-knowledge.yaml"
|
|
82
|
-
if
|
|
83
|
+
if config_path.exists():
|
|
83
84
|
with open(config_path) as f:
|
|
84
85
|
cfg = yaml.safe_load(f) or {}
|
|
85
86
|
return cfg.get("ai_managed", ["wiki"])
|
|
@@ -130,19 +131,14 @@ def scan_file(path: Path) -> list[tuple[int, str]]:
|
|
|
130
131
|
|
|
131
132
|
def main() -> None:
|
|
132
133
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
133
|
-
parser.add_argument("vault_root", type=Path)
|
|
134
|
-
parser.add_argument("--report", help="render the
|
|
134
|
+
parser.add_argument("vault_root", type=Path, nargs="?", help="default: configured vault")
|
|
135
|
+
parser.add_argument("--report", nargs="?", const=REPORT_PATH, help="render the standard report (dry run)")
|
|
135
136
|
parser.add_argument("--timestamp", help="timezone-aware ISO timestamp; default: current local time")
|
|
136
|
-
parser.add_argument(
|
|
137
|
-
"--apply", action="store_true", help="write the reviewed report using the verified writer"
|
|
138
|
-
)
|
|
137
|
+
parser.add_argument("--apply", action="store_true", help="regenerate the standard report")
|
|
139
138
|
args = parser.parse_args()
|
|
140
|
-
if args.apply
|
|
141
|
-
|
|
142
|
-
vault_root = args.vault_root
|
|
143
|
-
if not vault_root.is_dir():
|
|
144
|
-
print(f"Error: {vault_root} is not a directory", file=sys.stderr)
|
|
145
|
-
sys.exit(1)
|
|
139
|
+
if args.apply:
|
|
140
|
+
args.report = args.report or REPORT_PATH
|
|
141
|
+
vault_root = resolve_vault(args.vault_root)
|
|
146
142
|
|
|
147
143
|
zones = load_managed_zones(vault_root)
|
|
148
144
|
hits = []
|
|
@@ -166,7 +162,7 @@ def main() -> None:
|
|
|
166
162
|
print(f"{hit.path}\t{hit.line}\t{hit.text}")
|
|
167
163
|
return
|
|
168
164
|
try:
|
|
169
|
-
if args.report !=
|
|
165
|
+
if args.report != REPORT_PATH:
|
|
170
166
|
raise ValueError("--report must target Utility/obsidian-knowledge/reports/open-questions.md")
|
|
171
167
|
stamp = datetime.fromisoformat(args.timestamp) if args.timestamp else datetime.now().astimezone()
|
|
172
168
|
if stamp.utcoffset() is None:
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env -S uv run --script
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.12"
|
|
4
|
+
# dependencies = []
|
|
5
|
+
# ///
|
|
2
6
|
"""fix-stacked-frontmatter: detect and auto-fix stray duplicate `---` markers.
|
|
3
7
|
|
|
4
8
|
The most common cause of STACKED_FRONTMATTER is a stray duplicate `---` line
|
|
@@ -10,8 +14,8 @@ are left alone and reported for manual review, since merging may need
|
|
|
10
14
|
human judgment about which keys win.
|
|
11
15
|
|
|
12
16
|
Usage:
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
uv run fix-stacked-frontmatter.py <file> [<file>...] # dry run
|
|
18
|
+
uv run fix-stacked-frontmatter.py --fix <file> [<file>...] # rewrite
|
|
15
19
|
|
|
16
20
|
Exit 0 if no issues or all fixed; exit 1 if files need manual merge.
|
|
17
21
|
"""
|
|
@@ -2,13 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Unresolved links
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
obsidian vault="$VAULT_NAME" unresolved verbose format=json | uv run --no-project --with pyyaml python "$SCRIPTS/filter-unresolved-links.py" "$VAULT"
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
The filter keeps links from `ai_managed` zones and excludes template placeholders
|
|
5
|
+
The recovery script keeps links from `ai_managed` zones and excludes template placeholders
|
|
12
6
|
and configured `stub_link_patterns` from `.claude/obsidian-knowledge.yaml`.
|
|
13
7
|
Defaults cover prefixes such as `(PAPER)`, `(BOOK)`, and `@Person`.
|
|
14
8
|
|
|
@@ -40,14 +34,9 @@ from candidates. Sources must be visible managed Markdown notes outside
|
|
|
40
34
|
`_sources/` and configured read-only paths. Apply rechecks write policy and
|
|
41
35
|
delegates to the verified writer; aliases, heading and block suffixes survive.
|
|
42
36
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
fresh `obsidian vault="$VAULT_NAME" unresolved verbose format=json` results and
|
|
47
|
-
confirm each repaired target disappeared from the relevant source's findings.
|
|
48
|
-
If file contents and CLI results disagree, reload Obsidian with
|
|
49
|
-
`obsidian vault="$VAULT_NAME" command id="app:reload"` and retry after it loads.
|
|
50
|
-
Review fresh orphan results too; unrelated totals can change during sync.
|
|
37
|
+
Review the diff for intended link changes, then check fresh unresolved-link
|
|
38
|
+
results. If CLI results disagree with file contents, reload Obsidian with
|
|
39
|
+
`obsidian vault="$VAULT_NAME" command id="app:reload"` and retry.
|
|
51
40
|
|
|
52
41
|
## Orphans
|
|
53
42
|
|