mednotes-opencode 0.1.0
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.
- package/.opencode/agents/med-chat-triager.md +204 -0
- package/.opencode/agents/med-flashcard-maker.md +63 -0
- package/.opencode/agents/med-knowledge-architect.md +230 -0
- package/.opencode/agents/med-link-graph-curator.md +177 -0
- package/.opencode/agents/med-publish-guard.md +62 -0
- package/.opencode/commands/flashcards.md +25 -0
- package/.opencode/commands/mednotes/create.md +25 -0
- package/.opencode/commands/mednotes/enrich.md +27 -0
- package/.opencode/commands/mednotes/fix-wiki.md +27 -0
- package/.opencode/commands/mednotes/history.md +22 -0
- package/.opencode/commands/mednotes/link-body.md +25 -0
- package/.opencode/commands/mednotes/link-related.md +27 -0
- package/.opencode/commands/mednotes/link.md +27 -0
- package/.opencode/commands/mednotes/pdf-library.md +27 -0
- package/.opencode/commands/mednotes/process-chats.md +23 -0
- package/.opencode/commands/mednotes/setup.md +21 -0
- package/.opencode/commands/mednotes/status.md +27 -0
- package/.opencode/commands/mednotes/telemetry.md +27 -0
- package/.opencode/commands/report.md +26 -0
- package/.opencode/mednotes/AGENTS.md +57 -0
- package/.opencode/mednotes/agents/med-chat-triager.md +197 -0
- package/.opencode/mednotes/agents/med-flashcard-maker.md +56 -0
- package/.opencode/mednotes/agents/med-knowledge-architect.md +224 -0
- package/.opencode/mednotes/agents/med-link-graph-curator.md +171 -0
- package/.opencode/mednotes/agents/med-publish-guard.md +55 -0
- package/.opencode/mednotes/contracts/.gitkeep +1 -0
- package/.opencode/mednotes/contracts/agents.json +116 -0
- package/.opencode/mednotes/contracts/opencode-plugin.json +70 -0
- package/.opencode/mednotes/docs/agent-prompt-hardening.md +567 -0
- package/.opencode/mednotes/docs/agent-role-contracts.md +94 -0
- package/.opencode/mednotes/docs/anki-mcp-twenty-rules.md +214 -0
- package/.opencode/mednotes/docs/anki-templates/README.md +39 -0
- package/.opencode/mednotes/docs/anki-templates/cloze.back.html +23 -0
- package/.opencode/mednotes/docs/anki-templates/cloze.front.html +14 -0
- package/.opencode/mednotes/docs/anki-templates/qa.back.html +24 -0
- package/.opencode/mednotes/docs/anki-templates/qa.front.html +14 -0
- package/.opencode/mednotes/docs/anki-templates/style.css +182 -0
- package/.opencode/mednotes/docs/atomicity-splitting-policy.md +113 -0
- package/.opencode/mednotes/docs/extension-docs.md +40 -0
- package/.opencode/mednotes/docs/flashcard-ingestion.md +278 -0
- package/.opencode/mednotes/docs/knowledge-architect.md +208 -0
- package/.opencode/mednotes/docs/merge-policy.md +110 -0
- package/.opencode/mednotes/docs/public-vocabulary.md +104 -0
- package/.opencode/mednotes/docs/semantic-linker.md +141 -0
- package/.opencode/mednotes/docs/taxonomy-policy.md +90 -0
- package/.opencode/mednotes/docs/triage-policy.md +187 -0
- package/.opencode/mednotes/docs/vault-version-control.md +758 -0
- package/.opencode/mednotes/docs/vocabulary-db-recovery.md +58 -0
- package/.opencode/mednotes/docs/workflow-output-contract.md +779 -0
- package/.opencode/mednotes/hooks/hooks.json +79 -0
- package/.opencode/mednotes/package-lock.json +6361 -0
- package/.opencode/mednotes/package.json +15 -0
- package/.opencode/mednotes/pyproject.toml +48 -0
- package/.opencode/mednotes/scripts/bootstrap_windows_python_uv.cmd +13 -0
- package/.opencode/mednotes/scripts/bootstrap_windows_python_uv.ps1 +172 -0
- package/.opencode/mednotes/scripts/enrich_notes.py +23 -0
- package/.opencode/mednotes/scripts/full_reset_windows_python_uv.cmd +13 -0
- package/.opencode/mednotes/scripts/hooks/antigravity_hook_status.mjs +212 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/antigravity.mjs +169 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/harness_payload.mjs +103 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/opencode_plugin.mjs +341 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/opencode_user_config_sync.mjs +177 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/anki_preflight.mjs +214 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/cli.mjs +143 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/diagnostics.mjs +11 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/domain/agent_directive_core.mjs +160 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/fsm_directive.mjs +1470 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/hook_errors.mjs +120 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/retention.mjs +114 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/runtime.mjs +174 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/telemetry_capture.mjs +511 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/vault_guard.mjs +624 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook.mjs +5 -0
- package/.opencode/mednotes/scripts/mednotes/_runtime_paths.py +24 -0
- package/.opencode/mednotes/scripts/mednotes/anki_model_validator.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/capture_extension_diff.py +1562 -0
- package/.opencode/mednotes/scripts/mednotes/feedback_report.py +16 -0
- package/.opencode/mednotes/scripts/mednotes/flashcard_index.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/flashcard_pipeline.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/flashcard_report.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/flashcard_sources.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/obsidian/README.md +6 -0
- package/.opencode/mednotes/scripts/mednotes/obsidian_note_utils.py +20 -0
- package/.opencode/mednotes/scripts/mednotes/pdf_library/cli.py +16 -0
- package/.opencode/mednotes/scripts/mednotes/project_fsm.py +229 -0
- package/.opencode/mednotes/scripts/mednotes/setup_telemetry_email.py +404 -0
- package/.opencode/mednotes/scripts/mednotes/sync_anki_twenty_rules.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/sync_opencode_user_config.py +36 -0
- package/.opencode/mednotes/scripts/mednotes/wiki/cli.py +20 -0
- package/.opencode/mednotes/scripts/mednotes/wiki_graph.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/wiki_tree.py +134 -0
- package/.opencode/mednotes/scripts/reset_windows_python_uv.ps1 +625 -0
- package/.opencode/mednotes/scripts/run_python.mjs +109 -0
- package/.opencode/mednotes/scripts/vault/vault_commit.ps1 +19 -0
- package/.opencode/mednotes/scripts/vault/vault_commit.sh +18 -0
- package/.opencode/mednotes/scripts/vault/vault_git.ps1 +19 -0
- package/.opencode/mednotes/scripts/vault/vault_git.py +3107 -0
- package/.opencode/mednotes/scripts/vault/vault_git.sh +18 -0
- package/.opencode/mednotes/scripts/vault/vault_precommit.ps1 +19 -0
- package/.opencode/mednotes/scripts/vault/vault_precommit.sh +18 -0
- package/.opencode/mednotes/skills/THIRD_PARTY_NOTICES.md +45 -0
- package/.opencode/mednotes/skills/create-medical-flashcards/SKILL.md +113 -0
- package/.opencode/mednotes/skills/create-medical-note/SKILL.md +90 -0
- package/.opencode/mednotes/skills/enrich-medical-note/SKILL.md +120 -0
- package/.opencode/mednotes/skills/fix-medical-wiki/SKILL.md +559 -0
- package/.opencode/mednotes/skills/link-medical-wiki/SKILL.md +224 -0
- package/.opencode/mednotes/skills/obsidian-cli/SKILL.md +118 -0
- package/.opencode/mednotes/skills/obsidian-markdown/SKILL.md +207 -0
- package/.opencode/mednotes/skills/obsidian-markdown/references/CALLOUTS.md +58 -0
- package/.opencode/mednotes/skills/obsidian-markdown/references/EMBEDS.md +63 -0
- package/.opencode/mednotes/skills/obsidian-markdown/references/PROPERTIES.md +61 -0
- package/.opencode/mednotes/skills/obsidian-ops/SKILL.md +136 -0
- package/.opencode/mednotes/skills/pdf-library/SKILL.md +45 -0
- package/.opencode/mednotes/skills/process-medical-chats/SKILL.md +246 -0
- package/.opencode/mednotes/skills/workflow-report/SKILL.md +100 -0
- package/.opencode/mednotes/src/mednotes/__init__.py +5 -0
- package/.opencode/mednotes/src/mednotes/domains/__init__.py +5 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/README.md +26 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/__init__.py +2 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/build_demo_apkg.py +177 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/contracts.py +385 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/flashcards_machine.py +522 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/fsm.py +817 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/index.py +630 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/install_models.py +445 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/model.py +359 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/obsidian_links.py +135 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/obsidian_note_utils.py +546 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/pipeline.py +580 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/report.py +510 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/sources.py +682 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/sync_rules.py +184 -0
- package/.opencode/mednotes/src/mednotes/domains/history/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/history/history_fsm.py +852 -0
- package/.opencode/mednotes/src/mednotes/domains/history/history_machine.py +453 -0
- package/.opencode/mednotes/src/mednotes/domains/setup/__init__.py +7 -0
- package/.opencode/mednotes/src/mednotes/domains/setup/setup_fsm.py +808 -0
- package/.opencode/mednotes/src/mednotes/domains/setup/setup_machine.py +973 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/README.md +64 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/api.py +668 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/batch_state.py +102 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/atomicity/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/atomicity/atomicity.py +877 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/body_link/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/body_link/body_linker.py +1562 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/effect_adapters.py +949 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/fix_wiki_runtime_adapters.py +433 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/coverage.py +413 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/graph.py +396 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/graph_fixes.py +161 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/hygiene/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/hygiene/hygiene.py +483 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/__init__.py +2 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/anchors.py +185 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/__init__.py +0 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/cache.py +223 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/config.py +131 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/download.py +224 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/frontmatter.py +59 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/insert.py +227 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/local_import.py +54 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/__init__.py +42 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/web_profiles.py +99 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/web_search.py +203 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/wikimedia.py +102 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_db_adapter.mjs +434 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_node_runtime.py +274 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_query.py +227 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/artifacts.py +605 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/canonical_merge.py +277 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/markdown_zones.py +85 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/meaning_planner.py +307 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_iter.py +67 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_merge.py +278 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_plan.py +409 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_policy.py +22 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/__init__.py +79 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/fixes.py +264 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/frontmatter.py +435 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/models.py +208 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/prompts.py +37 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/tables.py +236 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/validate.py +404 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/provenance.py +478 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/raw_chats.py +273 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/sources_backfill.py +235 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/__init__.py +10 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/anchors.py +16 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/captions.py +47 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/cli.py +179 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/cloud.py +52 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/config.py +196 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/context_packets.py +76 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/db.py +81 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/doctor.py +102 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/figure_ids.py +42 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/ingest.py +326 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/insert.py +316 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/mentions.py +57 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/ocr.py +71 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/paths.py +35 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/pdf_engine.py +77 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/schema.py +155 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/search.py +188 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/app.py +89 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/image_backend.py +29 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/state.py +65 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish.py +1139 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish_receipts.py +365 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish_recovery.py +240 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_behavior_corpus.py +2069 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_report_validation.py +4448 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_run_audit.py +852 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/architect_prompt_eval.py +341 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/body_linker_eval.py +240 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/curator_output_validation.py +175 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/curator_prompt_eval.py +865 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/triager_prompt_eval.py +1295 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/related_notes.py +1920 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/related_notes_headless.py +1186 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/plan_attestation.py +148 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_receipts.py +360 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_runtime.py +52 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_task_runner.py +2470 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/style/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/style/style.py +1952 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/subagents/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/subagents/agents.py +1767 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/alias_projection.py +331 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/link_terms.py +151 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/llm_disambiguation.py +182 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/__init__.py +116 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/audit.py +201 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/migration.py +314 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/normalize.py +72 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/policy.py +135 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/resolve.py +413 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/schema.py +157 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/status.py +137 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_bootstrap.py +509 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_curator_batch.py +1115 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_ingestion.py +632 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_map.py +930 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_recovery.py +1388 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/cli.py +6665 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/common.py +69 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/config.py +210 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/__init__.py +74 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agent_report.py +242 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agent_run_audit.py +196 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agents.py +601 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/curator.py +256 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/effect_payloads.py +519 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/happy_path.py +190 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/link_git.py +110 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/link_runtime_artifact.py +52 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/note_plan.py +75 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/paths.py +114 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/public_report.py +53 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/publish.py +111 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/raw_coverage.py +217 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes.py +136 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes_headless.py +153 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes_runtime.py +395 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/schema_registry.py +637 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/specialist.py +432 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/status.py +62 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/style_rewrite.py +568 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/vocabulary_ingestion.py +223 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_blockers.py +510 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_guardrails.py +637 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_outcomes.py +121 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_receipts.py +100 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/__main__.py +4 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/cli.py +275 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/__init__.py +2 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/candidates.py +193 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/cli.py +189 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/gemini.py +220 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/inputs.py +120 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/models.py +34 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/parsing.py +48 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/prompts.py +216 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/quality.py +54 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/reporting.py +24 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/runner.py +433 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/utils.py +39 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/vault_guard_bridge.py +17 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_context_packets.py +454 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_decision_projection.py +133 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_effects.py +1260 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_fsm.py +2768 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_machine.py +1588 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_plan.py +306 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_primary_objective.py +316 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_problem.py +153 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_receipt_evidence.py +306 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_states.py +290 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_user_report.py +342 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/health.py +6332 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_fsm.py +1119 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_git.py +638 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_machine.py +1106 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_retry_governance.py +374 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_runtime_result.py +485 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_triggers.py +183 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/linking.py +2758 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/reference_repair.py +718 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/related_notes_fsm.py +1855 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link_related/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link_related/link_related_machine.py +834 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_fsm.py +1592 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_machine.py +3097 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_primary_objective.py +28 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_runtime_result.py +185 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/performance.py +97 -0
- package/.opencode/mednotes/src/mednotes/kernel/__init__.py +6 -0
- package/.opencode/mednotes/src/mednotes/kernel/agent_directive.py +336 -0
- package/.opencode/mednotes/src/mednotes/kernel/base.py +51 -0
- package/.opencode/mednotes/src/mednotes/kernel/blockers.py +39 -0
- package/.opencode/mednotes/src/mednotes/kernel/effect_executor.py +55 -0
- package/.opencode/mednotes/src/mednotes/kernel/effect_intent.py +69 -0
- package/.opencode/mednotes/src/mednotes/kernel/effects.py +160 -0
- package/.opencode/mednotes/src/mednotes/kernel/errors.py +38 -0
- package/.opencode/mednotes/src/mednotes/kernel/fsm_event.py +35 -0
- package/.opencode/mednotes/src/mednotes/kernel/fsm_model.py +55 -0
- package/.opencode/mednotes/src/mednotes/kernel/fsm_transition_result.py +75 -0
- package/.opencode/mednotes/src/mednotes/kernel/guardrails.py +188 -0
- package/.opencode/mednotes/src/mednotes/kernel/progress.py +319 -0
- package/.opencode/mednotes/src/mednotes/kernel/public_report.py +346 -0
- package/.opencode/mednotes/src/mednotes/kernel/state_machine.py +164 -0
- package/.opencode/mednotes/src/mednotes/kernel/workflow.py +619 -0
- package/.opencode/mednotes/src/mednotes/platform/__init__.py +5 -0
- package/.opencode/mednotes/src/mednotes/platform/backup_policy.py +382 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/__init__.py +62 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/cli.py +275 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/contracts.py +83 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/core.py +4168 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/integrity.py +989 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/operational_contract.py +2293 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/telemetry.py +875 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/telemetry_config.py +65 -0
- package/.opencode/mednotes/src/mednotes/platform/opencode_runtime_config.py +182 -0
- package/.opencode/mednotes/src/mednotes/platform/paths/__init__.py +1560 -0
- package/.opencode/mednotes/src/mednotes/platform/secrets.py +89 -0
- package/.opencode/mednotes/src/mednotes/platform/user_config.py +103 -0
- package/.opencode/mednotes/src/mednotes/platform/vault_guard.py +214 -0
- package/.opencode/mednotes/uv.lock +932 -0
- package/.opencode/mednotes.generated.json +395 -0
- package/.opencode/opencode.json +31 -0
- package/.opencode/plugins/mednotes-fsm.mjs +7 -0
- package/.opencode/plugins/mednotes_hook/adapters/antigravity.mjs +169 -0
- package/.opencode/plugins/mednotes_hook/adapters/harness_payload.mjs +103 -0
- package/.opencode/plugins/mednotes_hook/adapters/opencode_plugin.mjs +341 -0
- package/.opencode/plugins/mednotes_hook/adapters/opencode_user_config_sync.mjs +177 -0
- package/.opencode/plugins/mednotes_hook/anki_preflight.mjs +214 -0
- package/.opencode/plugins/mednotes_hook/cli.mjs +143 -0
- package/.opencode/plugins/mednotes_hook/diagnostics.mjs +11 -0
- package/.opencode/plugins/mednotes_hook/domain/agent_directive_core.mjs +160 -0
- package/.opencode/plugins/mednotes_hook/fsm_directive.mjs +1470 -0
- package/.opencode/plugins/mednotes_hook/hook_errors.mjs +120 -0
- package/.opencode/plugins/mednotes_hook/retention.mjs +114 -0
- package/.opencode/plugins/mednotes_hook/runtime.mjs +174 -0
- package/.opencode/plugins/mednotes_hook/telemetry_capture.mjs +511 -0
- package/.opencode/plugins/mednotes_hook/vault_guard.mjs +624 -0
- package/AGENTS.md +57 -0
- package/README.md +194 -0
- package/adapters/antigravity/agents.json +80 -0
- package/adapters/antigravity/templates/med-chat-triager.md +214 -0
- package/adapters/antigravity/templates/med-flashcard-maker.md +72 -0
- package/adapters/antigravity/templates/med-knowledge-architect.md +241 -0
- package/adapters/antigravity/templates/med-link-graph-curator.md +187 -0
- package/adapters/antigravity/templates/med-publish-guard.md +71 -0
- package/adapters/gemini-cli/gemini-extension.json +14 -0
- package/adapters/gemini-cli/package.json +15 -0
- package/adapters/gemini-cli/pyproject.toml +48 -0
- package/bin/mednotes-opencode.mjs +155 -0
- package/contracts/agents.json +116 -0
- package/core/agents/med-chat-triager.md +197 -0
- package/core/agents/med-flashcard-maker.md +56 -0
- package/core/agents/med-knowledge-architect.md +224 -0
- package/core/agents/med-link-graph-curator.md +171 -0
- package/core/agents/med-publish-guard.md +55 -0
- package/core/commands/flashcards.toml +22 -0
- package/core/commands/mednotes/create.toml +22 -0
- package/core/commands/mednotes/enrich.toml +24 -0
- package/core/commands/mednotes/fix-wiki.toml +24 -0
- package/core/commands/mednotes/history.toml +19 -0
- package/core/commands/mednotes/link-body.toml +22 -0
- package/core/commands/mednotes/link-related.toml +24 -0
- package/core/commands/mednotes/link.toml +24 -0
- package/core/commands/mednotes/pdf-library.toml +24 -0
- package/core/commands/mednotes/process-chats.toml +20 -0
- package/core/commands/mednotes/setup.toml +18 -0
- package/core/commands/mednotes/status.toml +24 -0
- package/core/commands/mednotes/telemetry.toml +24 -0
- package/core/commands/report.toml +23 -0
- package/core/skills/THIRD_PARTY_NOTICES.md +45 -0
- package/core/skills/create-medical-flashcards/SKILL.md +113 -0
- package/core/skills/create-medical-note/SKILL.md +90 -0
- package/core/skills/enrich-medical-note/SKILL.md +120 -0
- package/core/skills/fix-medical-wiki/SKILL.md +559 -0
- package/core/skills/link-medical-wiki/SKILL.md +224 -0
- package/core/skills/obsidian-cli/SKILL.md +118 -0
- package/core/skills/obsidian-markdown/SKILL.md +207 -0
- package/core/skills/obsidian-markdown/references/CALLOUTS.md +58 -0
- package/core/skills/obsidian-markdown/references/EMBEDS.md +63 -0
- package/core/skills/obsidian-markdown/references/PROPERTIES.md +61 -0
- package/core/skills/obsidian-ops/SKILL.md +136 -0
- package/core/skills/pdf-library/SKILL.md +45 -0
- package/core/skills/process-medical-chats/SKILL.md +246 -0
- package/core/skills/workflow-report/SKILL.md +100 -0
- package/package.json +45 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"""Coverage inventory validation for raw chat publishing."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from pydantic import ValidationError as PydanticValidationError
|
|
10
|
+
|
|
11
|
+
from mednotes.domains.wiki.batch_state import COVERAGE_SELF_HASH_KEYS, artifact_json_hash, clean_state_value
|
|
12
|
+
from mednotes.domains.wiki.capabilities.notes.note_plan import (
|
|
13
|
+
PLANNED_MEANING_ACTION,
|
|
14
|
+
note_plan_hash,
|
|
15
|
+
note_plan_summary,
|
|
16
|
+
parse_triage_note_plan,
|
|
17
|
+
planned_meaning_titles,
|
|
18
|
+
)
|
|
19
|
+
from mednotes.domains.wiki.capabilities.notes.raw_chats import read_note_meta
|
|
20
|
+
from mednotes.domains.wiki.capabilities.vocabulary.link_terms import normalize_key
|
|
21
|
+
from mednotes.domains.wiki.common import MissingPathError, ValidationError
|
|
22
|
+
from mednotes.domains.wiki.config import _path
|
|
23
|
+
from mednotes.domains.wiki.contracts.note_plan import TriageNotePlan, TriageNotePlanItem
|
|
24
|
+
from mednotes.domains.wiki.contracts.raw_coverage import (
|
|
25
|
+
RAW_COVERAGE_SCHEMA,
|
|
26
|
+
RawCoverage,
|
|
27
|
+
RawCoverageItem,
|
|
28
|
+
RawCoverageSource,
|
|
29
|
+
RawCoverageSummary,
|
|
30
|
+
)
|
|
31
|
+
from mednotes.kernel.base import JsonObject, JsonObjectAdapter, contract_error
|
|
32
|
+
|
|
33
|
+
NOT_A_NOTE_ACTION = "not_a_note"
|
|
34
|
+
COVERAGE_NOTE_PLAN_BINDING_KEYS = ("batch_id", "run_id", "source_artifact_hash")
|
|
35
|
+
MULTI_SOURCE_STATUSES = {"covered", "already_covered", "not_relevant"}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class _LoadedCoverage:
|
|
40
|
+
"""Raw JSON plus its canonical typed view.
|
|
41
|
+
|
|
42
|
+
``payload`` exists only for stable hashing. Every workflow decision below
|
|
43
|
+
reads ``inventory`` so a malformed coverage file cannot fabricate success.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
payload: JsonObject
|
|
47
|
+
inventory: RawCoverage
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _paths_match(left: str, right: Path) -> bool:
|
|
51
|
+
left_path = _path(left)
|
|
52
|
+
try:
|
|
53
|
+
return left_path.resolve() == right.resolve()
|
|
54
|
+
except OSError:
|
|
55
|
+
return str(left_path) == str(right)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _load_coverage(path: Path) -> _LoadedCoverage:
|
|
59
|
+
if not path.exists():
|
|
60
|
+
raise MissingPathError(f"Coverage inventory not found: {path}")
|
|
61
|
+
try:
|
|
62
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
63
|
+
except json.JSONDecodeError as exc:
|
|
64
|
+
raise ValidationError(f"Invalid coverage inventory JSON: {exc}") from exc
|
|
65
|
+
if not isinstance(raw, dict):
|
|
66
|
+
raise ValidationError("Coverage inventory must be a JSON object")
|
|
67
|
+
try:
|
|
68
|
+
payload = JsonObjectAdapter.validate_python(raw)
|
|
69
|
+
except PydanticValidationError as exc:
|
|
70
|
+
raise contract_error(exc, prefix="raw_coverage.json_invalid") from exc
|
|
71
|
+
if "schema" not in payload or payload["schema"] != RAW_COVERAGE_SCHEMA:
|
|
72
|
+
raise ValidationError(f"Coverage inventory schema must be {RAW_COVERAGE_SCHEMA}")
|
|
73
|
+
try:
|
|
74
|
+
inventory = RawCoverage.model_validate(payload)
|
|
75
|
+
except PydanticValidationError as exc:
|
|
76
|
+
raise contract_error(exc, prefix="raw_coverage.contract_invalid") from exc
|
|
77
|
+
return _LoadedCoverage(payload=payload, inventory=inventory)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _triage_note_plan(raw_file: Path, *, required: bool) -> JsonObject | None:
|
|
81
|
+
raw_meta = read_note_meta(raw_file)
|
|
82
|
+
raw_plan = raw_meta["note_plan"] if "note_plan" in raw_meta else ""
|
|
83
|
+
if not raw_plan:
|
|
84
|
+
if required:
|
|
85
|
+
raise ValidationError("Raw chat missing triage note_plan; rerun triage with --note-plan")
|
|
86
|
+
return None
|
|
87
|
+
return parse_triage_note_plan(raw_plan, raw_file)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _coverage_planned_meaning_titles(items: Sequence[RawCoverageItem]) -> set[str]:
|
|
91
|
+
return {
|
|
92
|
+
item.planned_title
|
|
93
|
+
for item in items
|
|
94
|
+
if item.action == PLANNED_MEANING_ACTION and item.planned_title
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _coverage_planned_meaning_keys(items: Sequence[RawCoverageItem]) -> set[str]:
|
|
99
|
+
return {normalize_key(title) for title in _coverage_planned_meaning_titles(items)}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _path_key(path: Path) -> str:
|
|
103
|
+
try:
|
|
104
|
+
return str(path.resolve())
|
|
105
|
+
except OSError:
|
|
106
|
+
return str(path)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _coverage_raw_files(data: RawCoverage, primary_raw_file: Path) -> list[Path]:
|
|
110
|
+
if not data.raw_files:
|
|
111
|
+
return [primary_raw_file]
|
|
112
|
+
raw_files: list[Path] = []
|
|
113
|
+
seen: set[str] = set()
|
|
114
|
+
for index, value in enumerate(data.raw_files, start=1):
|
|
115
|
+
raw_file_text = value.strip()
|
|
116
|
+
path = _path(raw_file_text)
|
|
117
|
+
if not raw_file_text:
|
|
118
|
+
raise ValidationError(f"provenance_gap: coverage raw_files item #{index} is empty")
|
|
119
|
+
key = _path_key(path)
|
|
120
|
+
if key in seen:
|
|
121
|
+
continue
|
|
122
|
+
seen.add(key)
|
|
123
|
+
if not path.exists():
|
|
124
|
+
raise MissingPathError(f"Raw file not found: {path}")
|
|
125
|
+
raw_files.append(path)
|
|
126
|
+
if _path_key(primary_raw_file) not in {_path_key(path) for path in raw_files}:
|
|
127
|
+
raise ValidationError("provenance_gap: coverage raw_files must include the primary raw_file")
|
|
128
|
+
return raw_files
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _item_signature(item: RawCoverageItem) -> tuple[str, str, str, str]:
|
|
132
|
+
"""Canonical signature used to compare coverage and note_plan items.
|
|
133
|
+
|
|
134
|
+
raw-coverage stores only coverage-bearing v2 actions. ``planned_meaning``
|
|
135
|
+
drives note output; ``not_a_note`` records explicitly discarded material.
|
|
136
|
+
"""
|
|
137
|
+
return (item.id.strip(), item.action, item.planned_title, "")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _note_plan_item_as_coverage_signature(item: TriageNotePlanItem) -> tuple[str, str, str, str] | None:
|
|
141
|
+
"""Translate a v2 note_plan item to a coverage-comparison signature.
|
|
142
|
+
|
|
143
|
+
attach_to_planned_meaning and needs_context items do not appear in
|
|
144
|
+
raw-coverage.v1 inventories: attach is folded into the target's note, and
|
|
145
|
+
needs_context blocks the architect. They are skipped on this translation
|
|
146
|
+
layer.
|
|
147
|
+
"""
|
|
148
|
+
if item.action not in {PLANNED_MEANING_ACTION, NOT_A_NOTE_ACTION}:
|
|
149
|
+
return None
|
|
150
|
+
title = (item.staged_title or item.title or "").strip()
|
|
151
|
+
return (item.id.strip(), item.action, title, "")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _coverage_metadata_value(data: RawCoverage, key: str) -> str:
|
|
155
|
+
return clean_state_value(getattr(data, key, None))
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _json_metadata_value(data: JsonObject, key: str) -> str:
|
|
159
|
+
return clean_state_value(data[key] if key in data else None)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _validate_coverage_sources(
|
|
163
|
+
data: RawCoverage,
|
|
164
|
+
raw_files: list[Path],
|
|
165
|
+
items: Sequence[RawCoverageItem],
|
|
166
|
+
) -> list[RawCoverageSource]:
|
|
167
|
+
if not data.sources:
|
|
168
|
+
if len(raw_files) == 1:
|
|
169
|
+
return []
|
|
170
|
+
raise ValidationError("provenance_gap: multi-source coverage must include sources[]")
|
|
171
|
+
|
|
172
|
+
raw_file_keys = {_path_key(path) for path in raw_files}
|
|
173
|
+
seen: set[str] = set()
|
|
174
|
+
sources: list[RawCoverageSource] = []
|
|
175
|
+
coverage_titles = _coverage_planned_meaning_titles(items)
|
|
176
|
+
coverage_title_keys = {normalize_key(title) for title in coverage_titles}
|
|
177
|
+
for index, source in enumerate(data.sources, start=1):
|
|
178
|
+
raw_file_value = source.raw_file.strip()
|
|
179
|
+
if not raw_file_value:
|
|
180
|
+
raise ValidationError(f"provenance_gap: coverage source #{index} missing raw_file")
|
|
181
|
+
raw_file = _path(raw_file_value)
|
|
182
|
+
raw_key = _path_key(raw_file)
|
|
183
|
+
if raw_key not in raw_file_keys:
|
|
184
|
+
raise ValidationError(f"provenance_gap: coverage source raw_file is not in raw_files: {raw_file}")
|
|
185
|
+
if raw_key in seen:
|
|
186
|
+
raise ValidationError(f"provenance_gap: duplicate coverage source for raw_file: {raw_file}")
|
|
187
|
+
seen.add(raw_key)
|
|
188
|
+
status = source.status
|
|
189
|
+
if status not in MULTI_SOURCE_STATUSES:
|
|
190
|
+
raise ValidationError(
|
|
191
|
+
f"provenance_gap: coverage source {raw_file} has invalid status {status!r}; "
|
|
192
|
+
f"expected one of {', '.join(sorted(MULTI_SOURCE_STATUSES))}"
|
|
193
|
+
)
|
|
194
|
+
clean_source = source.model_copy(update={"raw_file": str(raw_file)})
|
|
195
|
+
if status == "covered":
|
|
196
|
+
if normalize_key(clean_source.target_title.strip()) not in coverage_title_keys:
|
|
197
|
+
raise ValidationError(
|
|
198
|
+
f"provenance_gap: covered source {raw_file} target_title is absent from coverage items"
|
|
199
|
+
)
|
|
200
|
+
sources.append(clean_source)
|
|
201
|
+
|
|
202
|
+
missing_sources = [str(path) for path in raw_files if _path_key(path) not in seen]
|
|
203
|
+
if missing_sources:
|
|
204
|
+
raise ValidationError("provenance_gap: coverage sources missing raw_files: " + ", ".join(missing_sources))
|
|
205
|
+
return sources
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _validate_multi_source_note_plans(
|
|
209
|
+
raw_files: list[Path],
|
|
210
|
+
items: Sequence[RawCoverageItem],
|
|
211
|
+
*,
|
|
212
|
+
required: bool,
|
|
213
|
+
) -> JsonObject:
|
|
214
|
+
coverage_keys = _coverage_planned_meaning_keys(items)
|
|
215
|
+
union_plan_keys: set[str] = set()
|
|
216
|
+
note_plan_hashes: dict[str, str] = {}
|
|
217
|
+
planned_meaning_count = 0
|
|
218
|
+
item_count = 0
|
|
219
|
+
for raw_file in raw_files:
|
|
220
|
+
note_plan = _triage_note_plan(raw_file, required=required)
|
|
221
|
+
if not note_plan:
|
|
222
|
+
continue
|
|
223
|
+
plan_titles = planned_meaning_titles(note_plan)
|
|
224
|
+
plan_keys = {normalize_key(title) for title in plan_titles}
|
|
225
|
+
missing = sorted(plan_titles, key=normalize_key)
|
|
226
|
+
missing = [title for title in missing if normalize_key(title) not in coverage_keys]
|
|
227
|
+
if missing:
|
|
228
|
+
raise ValidationError(
|
|
229
|
+
f"provenance_gap: coverage is missing triage-planned notes for {raw_file}: "
|
|
230
|
+
+ ", ".join(missing)
|
|
231
|
+
)
|
|
232
|
+
union_plan_keys.update(plan_keys)
|
|
233
|
+
note_plan_hashes[str(raw_file)] = note_plan_hash(note_plan)
|
|
234
|
+
summary = note_plan_summary(note_plan)
|
|
235
|
+
planned_meaning_count += int(summary["note_plan_planned_meaning_count"])
|
|
236
|
+
item_count += int(summary["note_plan_item_count"])
|
|
237
|
+
extra_keys = sorted(coverage_keys - union_plan_keys)
|
|
238
|
+
if required and extra_keys:
|
|
239
|
+
raise ValidationError(
|
|
240
|
+
"provenance_gap: coverage has planned_meaning targets absent from all source note_plans: "
|
|
241
|
+
+ ", ".join(extra_keys)
|
|
242
|
+
)
|
|
243
|
+
return JsonObjectAdapter.validate_python({
|
|
244
|
+
"note_plan_source_count": len(note_plan_hashes),
|
|
245
|
+
"note_plan_hashes": note_plan_hashes,
|
|
246
|
+
"note_plan_item_count": item_count,
|
|
247
|
+
"note_plan_planned_meaning_count": planned_meaning_count,
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _merge_note_plan_metadata(
|
|
252
|
+
result: RawCoverageSummary,
|
|
253
|
+
coverage_data: RawCoverage,
|
|
254
|
+
note_plan: JsonObject | None,
|
|
255
|
+
) -> RawCoverageSummary:
|
|
256
|
+
if note_plan is None:
|
|
257
|
+
updates: dict[str, object] = {}
|
|
258
|
+
coverage_note_plan_hash = _coverage_metadata_value(coverage_data, "note_plan_hash")
|
|
259
|
+
if coverage_note_plan_hash:
|
|
260
|
+
updates["note_plan_hash"] = coverage_note_plan_hash
|
|
261
|
+
for key in COVERAGE_NOTE_PLAN_BINDING_KEYS:
|
|
262
|
+
value = _coverage_metadata_value(coverage_data, key)
|
|
263
|
+
if value:
|
|
264
|
+
updates[key] = value
|
|
265
|
+
return result.model_copy(update=updates)
|
|
266
|
+
|
|
267
|
+
expected_note_plan_hash = note_plan_hash(note_plan)
|
|
268
|
+
coverage_note_plan_hash = _coverage_metadata_value(coverage_data, "note_plan_hash")
|
|
269
|
+
if coverage_note_plan_hash and coverage_note_plan_hash != expected_note_plan_hash:
|
|
270
|
+
raise ValidationError(
|
|
271
|
+
"batch_state_mismatch: coverage note_plan_hash does not match raw triage note_plan. "
|
|
272
|
+
"Regenerate raw coverage from the current note_plan and rerun stage-note."
|
|
273
|
+
)
|
|
274
|
+
updates = {"note_plan_hash": expected_note_plan_hash}
|
|
275
|
+
|
|
276
|
+
for key in COVERAGE_NOTE_PLAN_BINDING_KEYS:
|
|
277
|
+
coverage_value = _coverage_metadata_value(coverage_data, key)
|
|
278
|
+
plan_value = _json_metadata_value(note_plan, key)
|
|
279
|
+
if coverage_value and plan_value and coverage_value != plan_value:
|
|
280
|
+
raise ValidationError(
|
|
281
|
+
"batch_state_mismatch: "
|
|
282
|
+
f"coverage {key}={coverage_value} does not match raw triage note_plan {key}={plan_value}. "
|
|
283
|
+
"Regenerate raw coverage from the current note_plan and rerun stage-note."
|
|
284
|
+
)
|
|
285
|
+
value = coverage_value or plan_value
|
|
286
|
+
if value:
|
|
287
|
+
updates[key] = value
|
|
288
|
+
return result.model_copy(update=updates)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _validate_raw_coverage_structure_model(
|
|
292
|
+
path: Path,
|
|
293
|
+
raw_file: Path,
|
|
294
|
+
*,
|
|
295
|
+
require_triage_note_plan: bool = True,
|
|
296
|
+
) -> RawCoverageSummary:
|
|
297
|
+
"""Validate structure and raw-file binding without checking staged notes."""
|
|
298
|
+
|
|
299
|
+
loaded = _load_coverage(path)
|
|
300
|
+
data = loaded.inventory
|
|
301
|
+
raw_value = data.raw_file
|
|
302
|
+
if not raw_value:
|
|
303
|
+
raise ValidationError("coverage_invalid: Coverage inventory missing raw_file")
|
|
304
|
+
if not _paths_match(raw_value, raw_file):
|
|
305
|
+
raise ValidationError(f"coverage_invalid: Coverage inventory raw_file does not match manifest batch: {raw_value}")
|
|
306
|
+
raw_files = _coverage_raw_files(data, raw_file)
|
|
307
|
+
items = data.items
|
|
308
|
+
|
|
309
|
+
planned_meaning_count = sum(1 for item in items if item.action == PLANNED_MEANING_ACTION)
|
|
310
|
+
not_a_note_count = sum(1 for item in items if item.action == NOT_A_NOTE_ACTION)
|
|
311
|
+
|
|
312
|
+
sources = _validate_coverage_sources(data, raw_files, items)
|
|
313
|
+
result = RawCoverageSummary(
|
|
314
|
+
coverage_path=str(path),
|
|
315
|
+
coverage_hash=artifact_json_hash(loaded.payload, exclude_keys=COVERAGE_SELF_HASH_KEYS),
|
|
316
|
+
raw_file=str(raw_file),
|
|
317
|
+
raw_files=[str(path) for path in raw_files],
|
|
318
|
+
multi_source=len(raw_files) > 1,
|
|
319
|
+
source_count=len(sources) or len(raw_files),
|
|
320
|
+
exhaustive=True,
|
|
321
|
+
item_count=len(items),
|
|
322
|
+
planned_meaning_count=planned_meaning_count,
|
|
323
|
+
not_a_note_count=not_a_note_count,
|
|
324
|
+
raw_file_count=len(raw_files),
|
|
325
|
+
covered_count=len(raw_files),
|
|
326
|
+
)
|
|
327
|
+
if sources:
|
|
328
|
+
status_counts = dict.fromkeys(sorted(MULTI_SOURCE_STATUSES), 0)
|
|
329
|
+
for source in sources:
|
|
330
|
+
status_counts[source.status] += 1
|
|
331
|
+
result = result.model_copy(update={"sources": sources, "source_status_counts": status_counts})
|
|
332
|
+
if len(raw_files) > 1:
|
|
333
|
+
result = result.model_copy(
|
|
334
|
+
update=_validate_multi_source_note_plans(
|
|
335
|
+
raw_files,
|
|
336
|
+
items,
|
|
337
|
+
required=require_triage_note_plan,
|
|
338
|
+
),
|
|
339
|
+
)
|
|
340
|
+
return _merge_note_plan_metadata(result, data, None)
|
|
341
|
+
|
|
342
|
+
note_plan = _triage_note_plan(raw_file, required=require_triage_note_plan)
|
|
343
|
+
if note_plan:
|
|
344
|
+
plan_titles = planned_meaning_titles(note_plan)
|
|
345
|
+
coverage_titles = _coverage_planned_meaning_titles(items)
|
|
346
|
+
missing = sorted(plan_titles - coverage_titles)
|
|
347
|
+
extra = sorted(coverage_titles - plan_titles)
|
|
348
|
+
if missing:
|
|
349
|
+
raise ValidationError(
|
|
350
|
+
"Coverage inventory is missing triage-planned notes: " + ", ".join(missing)
|
|
351
|
+
)
|
|
352
|
+
if extra:
|
|
353
|
+
raise ValidationError(
|
|
354
|
+
"Coverage inventory has planned_meaning items absent from triage note_plan: " + ", ".join(extra)
|
|
355
|
+
)
|
|
356
|
+
plan_signatures: set[tuple[str, str, str, str]] = set()
|
|
357
|
+
for plan_item in TriageNotePlan.model_validate(note_plan).items:
|
|
358
|
+
sig = _note_plan_item_as_coverage_signature(plan_item)
|
|
359
|
+
if sig is not None:
|
|
360
|
+
plan_signatures.add(sig)
|
|
361
|
+
coverage_signatures = {_item_signature(item) for item in items}
|
|
362
|
+
if plan_signatures != coverage_signatures:
|
|
363
|
+
raise ValidationError("Coverage inventory does not match triage note_plan items")
|
|
364
|
+
result = result.model_copy(update=note_plan_summary(note_plan))
|
|
365
|
+
return _merge_note_plan_metadata(result, data, note_plan)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def validate_raw_coverage_structure(path: Path, raw_file: Path, *, require_triage_note_plan: bool = True) -> JsonObject:
|
|
369
|
+
"""Validate structure and raw-file binding without checking staged notes."""
|
|
370
|
+
|
|
371
|
+
return _validate_raw_coverage_structure_model(
|
|
372
|
+
path,
|
|
373
|
+
raw_file,
|
|
374
|
+
require_triage_note_plan=require_triage_note_plan,
|
|
375
|
+
).to_payload()
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def validate_raw_coverage(
|
|
379
|
+
path: Path,
|
|
380
|
+
raw_file: Path,
|
|
381
|
+
staged_titles: list[str],
|
|
382
|
+
*,
|
|
383
|
+
require_triage_note_plan: bool = True,
|
|
384
|
+
) -> JsonObject:
|
|
385
|
+
"""Validate that the exhaustive inventory and staged manifest agree."""
|
|
386
|
+
|
|
387
|
+
summary = _validate_raw_coverage_structure_model(
|
|
388
|
+
path,
|
|
389
|
+
raw_file,
|
|
390
|
+
require_triage_note_plan=require_triage_note_plan,
|
|
391
|
+
)
|
|
392
|
+
data = _load_coverage(path).inventory
|
|
393
|
+
items = data.items
|
|
394
|
+
staged = {title.strip() for title in staged_titles if title.strip()}
|
|
395
|
+
planned_titles: set[str] = set()
|
|
396
|
+
for item in items:
|
|
397
|
+
if item.action != PLANNED_MEANING_ACTION:
|
|
398
|
+
continue
|
|
399
|
+
planned_titles.add(item.planned_title)
|
|
400
|
+
|
|
401
|
+
missing = sorted(planned_titles - staged)
|
|
402
|
+
unexpected = sorted(staged - planned_titles)
|
|
403
|
+
if missing:
|
|
404
|
+
raise ValidationError(
|
|
405
|
+
"coverage_invalid: Coverage inventory has planned_meaning items not staged in manifest: "
|
|
406
|
+
+ ", ".join(missing)
|
|
407
|
+
)
|
|
408
|
+
if unexpected:
|
|
409
|
+
raise ValidationError(
|
|
410
|
+
"coverage_invalid: Manifest has staged notes absent from coverage inventory: " + ", ".join(unexpected)
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
return summary.model_copy(update={"staged_note_count": len(staged)}).to_payload()
|