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.
Files changed (430) hide show
  1. package/.opencode/agents/med-chat-triager.md +204 -0
  2. package/.opencode/agents/med-flashcard-maker.md +63 -0
  3. package/.opencode/agents/med-knowledge-architect.md +230 -0
  4. package/.opencode/agents/med-link-graph-curator.md +177 -0
  5. package/.opencode/agents/med-publish-guard.md +62 -0
  6. package/.opencode/commands/flashcards.md +25 -0
  7. package/.opencode/commands/mednotes/create.md +25 -0
  8. package/.opencode/commands/mednotes/enrich.md +27 -0
  9. package/.opencode/commands/mednotes/fix-wiki.md +27 -0
  10. package/.opencode/commands/mednotes/history.md +22 -0
  11. package/.opencode/commands/mednotes/link-body.md +25 -0
  12. package/.opencode/commands/mednotes/link-related.md +27 -0
  13. package/.opencode/commands/mednotes/link.md +27 -0
  14. package/.opencode/commands/mednotes/pdf-library.md +27 -0
  15. package/.opencode/commands/mednotes/process-chats.md +23 -0
  16. package/.opencode/commands/mednotes/setup.md +21 -0
  17. package/.opencode/commands/mednotes/status.md +27 -0
  18. package/.opencode/commands/mednotes/telemetry.md +27 -0
  19. package/.opencode/commands/report.md +26 -0
  20. package/.opencode/mednotes/AGENTS.md +57 -0
  21. package/.opencode/mednotes/agents/med-chat-triager.md +197 -0
  22. package/.opencode/mednotes/agents/med-flashcard-maker.md +56 -0
  23. package/.opencode/mednotes/agents/med-knowledge-architect.md +224 -0
  24. package/.opencode/mednotes/agents/med-link-graph-curator.md +171 -0
  25. package/.opencode/mednotes/agents/med-publish-guard.md +55 -0
  26. package/.opencode/mednotes/contracts/.gitkeep +1 -0
  27. package/.opencode/mednotes/contracts/agents.json +116 -0
  28. package/.opencode/mednotes/contracts/opencode-plugin.json +70 -0
  29. package/.opencode/mednotes/docs/agent-prompt-hardening.md +567 -0
  30. package/.opencode/mednotes/docs/agent-role-contracts.md +94 -0
  31. package/.opencode/mednotes/docs/anki-mcp-twenty-rules.md +214 -0
  32. package/.opencode/mednotes/docs/anki-templates/README.md +39 -0
  33. package/.opencode/mednotes/docs/anki-templates/cloze.back.html +23 -0
  34. package/.opencode/mednotes/docs/anki-templates/cloze.front.html +14 -0
  35. package/.opencode/mednotes/docs/anki-templates/qa.back.html +24 -0
  36. package/.opencode/mednotes/docs/anki-templates/qa.front.html +14 -0
  37. package/.opencode/mednotes/docs/anki-templates/style.css +182 -0
  38. package/.opencode/mednotes/docs/atomicity-splitting-policy.md +113 -0
  39. package/.opencode/mednotes/docs/extension-docs.md +40 -0
  40. package/.opencode/mednotes/docs/flashcard-ingestion.md +278 -0
  41. package/.opencode/mednotes/docs/knowledge-architect.md +208 -0
  42. package/.opencode/mednotes/docs/merge-policy.md +110 -0
  43. package/.opencode/mednotes/docs/public-vocabulary.md +104 -0
  44. package/.opencode/mednotes/docs/semantic-linker.md +141 -0
  45. package/.opencode/mednotes/docs/taxonomy-policy.md +90 -0
  46. package/.opencode/mednotes/docs/triage-policy.md +187 -0
  47. package/.opencode/mednotes/docs/vault-version-control.md +758 -0
  48. package/.opencode/mednotes/docs/vocabulary-db-recovery.md +58 -0
  49. package/.opencode/mednotes/docs/workflow-output-contract.md +779 -0
  50. package/.opencode/mednotes/hooks/hooks.json +79 -0
  51. package/.opencode/mednotes/package-lock.json +6361 -0
  52. package/.opencode/mednotes/package.json +15 -0
  53. package/.opencode/mednotes/pyproject.toml +48 -0
  54. package/.opencode/mednotes/scripts/bootstrap_windows_python_uv.cmd +13 -0
  55. package/.opencode/mednotes/scripts/bootstrap_windows_python_uv.ps1 +172 -0
  56. package/.opencode/mednotes/scripts/enrich_notes.py +23 -0
  57. package/.opencode/mednotes/scripts/full_reset_windows_python_uv.cmd +13 -0
  58. package/.opencode/mednotes/scripts/hooks/antigravity_hook_status.mjs +212 -0
  59. package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/antigravity.mjs +169 -0
  60. package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/harness_payload.mjs +103 -0
  61. package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/opencode_plugin.mjs +341 -0
  62. package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/opencode_user_config_sync.mjs +177 -0
  63. package/.opencode/mednotes/scripts/hooks/mednotes_hook/anki_preflight.mjs +214 -0
  64. package/.opencode/mednotes/scripts/hooks/mednotes_hook/cli.mjs +143 -0
  65. package/.opencode/mednotes/scripts/hooks/mednotes_hook/diagnostics.mjs +11 -0
  66. package/.opencode/mednotes/scripts/hooks/mednotes_hook/domain/agent_directive_core.mjs +160 -0
  67. package/.opencode/mednotes/scripts/hooks/mednotes_hook/fsm_directive.mjs +1470 -0
  68. package/.opencode/mednotes/scripts/hooks/mednotes_hook/hook_errors.mjs +120 -0
  69. package/.opencode/mednotes/scripts/hooks/mednotes_hook/retention.mjs +114 -0
  70. package/.opencode/mednotes/scripts/hooks/mednotes_hook/runtime.mjs +174 -0
  71. package/.opencode/mednotes/scripts/hooks/mednotes_hook/telemetry_capture.mjs +511 -0
  72. package/.opencode/mednotes/scripts/hooks/mednotes_hook/vault_guard.mjs +624 -0
  73. package/.opencode/mednotes/scripts/hooks/mednotes_hook.mjs +5 -0
  74. package/.opencode/mednotes/scripts/mednotes/_runtime_paths.py +24 -0
  75. package/.opencode/mednotes/scripts/mednotes/anki_model_validator.py +18 -0
  76. package/.opencode/mednotes/scripts/mednotes/capture_extension_diff.py +1562 -0
  77. package/.opencode/mednotes/scripts/mednotes/feedback_report.py +16 -0
  78. package/.opencode/mednotes/scripts/mednotes/flashcard_index.py +18 -0
  79. package/.opencode/mednotes/scripts/mednotes/flashcard_pipeline.py +18 -0
  80. package/.opencode/mednotes/scripts/mednotes/flashcard_report.py +18 -0
  81. package/.opencode/mednotes/scripts/mednotes/flashcard_sources.py +18 -0
  82. package/.opencode/mednotes/scripts/mednotes/obsidian/README.md +6 -0
  83. package/.opencode/mednotes/scripts/mednotes/obsidian_note_utils.py +20 -0
  84. package/.opencode/mednotes/scripts/mednotes/pdf_library/cli.py +16 -0
  85. package/.opencode/mednotes/scripts/mednotes/project_fsm.py +229 -0
  86. package/.opencode/mednotes/scripts/mednotes/setup_telemetry_email.py +404 -0
  87. package/.opencode/mednotes/scripts/mednotes/sync_anki_twenty_rules.py +18 -0
  88. package/.opencode/mednotes/scripts/mednotes/sync_opencode_user_config.py +36 -0
  89. package/.opencode/mednotes/scripts/mednotes/wiki/cli.py +20 -0
  90. package/.opencode/mednotes/scripts/mednotes/wiki_graph.py +18 -0
  91. package/.opencode/mednotes/scripts/mednotes/wiki_tree.py +134 -0
  92. package/.opencode/mednotes/scripts/reset_windows_python_uv.ps1 +625 -0
  93. package/.opencode/mednotes/scripts/run_python.mjs +109 -0
  94. package/.opencode/mednotes/scripts/vault/vault_commit.ps1 +19 -0
  95. package/.opencode/mednotes/scripts/vault/vault_commit.sh +18 -0
  96. package/.opencode/mednotes/scripts/vault/vault_git.ps1 +19 -0
  97. package/.opencode/mednotes/scripts/vault/vault_git.py +3107 -0
  98. package/.opencode/mednotes/scripts/vault/vault_git.sh +18 -0
  99. package/.opencode/mednotes/scripts/vault/vault_precommit.ps1 +19 -0
  100. package/.opencode/mednotes/scripts/vault/vault_precommit.sh +18 -0
  101. package/.opencode/mednotes/skills/THIRD_PARTY_NOTICES.md +45 -0
  102. package/.opencode/mednotes/skills/create-medical-flashcards/SKILL.md +113 -0
  103. package/.opencode/mednotes/skills/create-medical-note/SKILL.md +90 -0
  104. package/.opencode/mednotes/skills/enrich-medical-note/SKILL.md +120 -0
  105. package/.opencode/mednotes/skills/fix-medical-wiki/SKILL.md +559 -0
  106. package/.opencode/mednotes/skills/link-medical-wiki/SKILL.md +224 -0
  107. package/.opencode/mednotes/skills/obsidian-cli/SKILL.md +118 -0
  108. package/.opencode/mednotes/skills/obsidian-markdown/SKILL.md +207 -0
  109. package/.opencode/mednotes/skills/obsidian-markdown/references/CALLOUTS.md +58 -0
  110. package/.opencode/mednotes/skills/obsidian-markdown/references/EMBEDS.md +63 -0
  111. package/.opencode/mednotes/skills/obsidian-markdown/references/PROPERTIES.md +61 -0
  112. package/.opencode/mednotes/skills/obsidian-ops/SKILL.md +136 -0
  113. package/.opencode/mednotes/skills/pdf-library/SKILL.md +45 -0
  114. package/.opencode/mednotes/skills/process-medical-chats/SKILL.md +246 -0
  115. package/.opencode/mednotes/skills/workflow-report/SKILL.md +100 -0
  116. package/.opencode/mednotes/src/mednotes/__init__.py +5 -0
  117. package/.opencode/mednotes/src/mednotes/domains/__init__.py +5 -0
  118. package/.opencode/mednotes/src/mednotes/domains/flashcards/README.md +26 -0
  119. package/.opencode/mednotes/src/mednotes/domains/flashcards/__init__.py +2 -0
  120. package/.opencode/mednotes/src/mednotes/domains/flashcards/build_demo_apkg.py +177 -0
  121. package/.opencode/mednotes/src/mednotes/domains/flashcards/contracts.py +385 -0
  122. package/.opencode/mednotes/src/mednotes/domains/flashcards/flashcards_machine.py +522 -0
  123. package/.opencode/mednotes/src/mednotes/domains/flashcards/fsm.py +817 -0
  124. package/.opencode/mednotes/src/mednotes/domains/flashcards/index.py +630 -0
  125. package/.opencode/mednotes/src/mednotes/domains/flashcards/install_models.py +445 -0
  126. package/.opencode/mednotes/src/mednotes/domains/flashcards/model.py +359 -0
  127. package/.opencode/mednotes/src/mednotes/domains/flashcards/obsidian_links.py +135 -0
  128. package/.opencode/mednotes/src/mednotes/domains/flashcards/obsidian_note_utils.py +546 -0
  129. package/.opencode/mednotes/src/mednotes/domains/flashcards/pipeline.py +580 -0
  130. package/.opencode/mednotes/src/mednotes/domains/flashcards/report.py +510 -0
  131. package/.opencode/mednotes/src/mednotes/domains/flashcards/sources.py +682 -0
  132. package/.opencode/mednotes/src/mednotes/domains/flashcards/sync_rules.py +184 -0
  133. package/.opencode/mednotes/src/mednotes/domains/history/__init__.py +1 -0
  134. package/.opencode/mednotes/src/mednotes/domains/history/history_fsm.py +852 -0
  135. package/.opencode/mednotes/src/mednotes/domains/history/history_machine.py +453 -0
  136. package/.opencode/mednotes/src/mednotes/domains/setup/__init__.py +7 -0
  137. package/.opencode/mednotes/src/mednotes/domains/setup/setup_fsm.py +808 -0
  138. package/.opencode/mednotes/src/mednotes/domains/setup/setup_machine.py +973 -0
  139. package/.opencode/mednotes/src/mednotes/domains/wiki/README.md +64 -0
  140. package/.opencode/mednotes/src/mednotes/domains/wiki/__init__.py +1 -0
  141. package/.opencode/mednotes/src/mednotes/domains/wiki/api.py +668 -0
  142. package/.opencode/mednotes/src/mednotes/domains/wiki/batch_state.py +102 -0
  143. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/__init__.py +1 -0
  144. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/atomicity/__init__.py +1 -0
  145. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/atomicity/atomicity.py +877 -0
  146. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/body_link/__init__.py +1 -0
  147. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/body_link/body_linker.py +1562 -0
  148. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/__init__.py +1 -0
  149. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/effect_adapters.py +949 -0
  150. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/fix_wiki_runtime_adapters.py +433 -0
  151. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/__init__.py +1 -0
  152. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/coverage.py +413 -0
  153. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/graph.py +396 -0
  154. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/graph_fixes.py +161 -0
  155. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/hygiene/__init__.py +1 -0
  156. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/hygiene/hygiene.py +483 -0
  157. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/__init__.py +2 -0
  158. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/anchors.py +185 -0
  159. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/__init__.py +0 -0
  160. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/cache.py +223 -0
  161. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/config.py +131 -0
  162. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/download.py +224 -0
  163. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/frontmatter.py +59 -0
  164. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/insert.py +227 -0
  165. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/local_import.py +54 -0
  166. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/__init__.py +42 -0
  167. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/web_profiles.py +99 -0
  168. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/web_search.py +203 -0
  169. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/wikimedia.py +102 -0
  170. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/__init__.py +1 -0
  171. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_db_adapter.mjs +434 -0
  172. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_node_runtime.py +274 -0
  173. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_query.py +227 -0
  174. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/__init__.py +1 -0
  175. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/artifacts.py +605 -0
  176. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/canonical_merge.py +277 -0
  177. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/markdown_zones.py +85 -0
  178. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/meaning_planner.py +307 -0
  179. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_iter.py +67 -0
  180. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_merge.py +278 -0
  181. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_plan.py +409 -0
  182. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_policy.py +22 -0
  183. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/__init__.py +79 -0
  184. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/fixes.py +264 -0
  185. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/frontmatter.py +435 -0
  186. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/models.py +208 -0
  187. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/prompts.py +37 -0
  188. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/tables.py +236 -0
  189. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/validate.py +404 -0
  190. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/provenance.py +478 -0
  191. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/raw_chats.py +273 -0
  192. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/sources_backfill.py +235 -0
  193. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/__init__.py +10 -0
  194. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/anchors.py +16 -0
  195. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/captions.py +47 -0
  196. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/cli.py +179 -0
  197. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/cloud.py +52 -0
  198. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/config.py +196 -0
  199. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/context_packets.py +76 -0
  200. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/db.py +81 -0
  201. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/doctor.py +102 -0
  202. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/figure_ids.py +42 -0
  203. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/ingest.py +326 -0
  204. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/insert.py +316 -0
  205. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/mentions.py +57 -0
  206. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/ocr.py +71 -0
  207. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/paths.py +35 -0
  208. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/pdf_engine.py +77 -0
  209. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/schema.py +155 -0
  210. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/search.py +188 -0
  211. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/__init__.py +1 -0
  212. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/app.py +89 -0
  213. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/image_backend.py +29 -0
  214. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/state.py +65 -0
  215. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/__init__.py +1 -0
  216. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish.py +1139 -0
  217. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish_receipts.py +365 -0
  218. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish_recovery.py +240 -0
  219. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/__init__.py +1 -0
  220. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_behavior_corpus.py +2069 -0
  221. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_report_validation.py +4448 -0
  222. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_run_audit.py +852 -0
  223. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/architect_prompt_eval.py +341 -0
  224. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/body_linker_eval.py +240 -0
  225. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/curator_output_validation.py +175 -0
  226. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/curator_prompt_eval.py +865 -0
  227. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/triager_prompt_eval.py +1295 -0
  228. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/__init__.py +1 -0
  229. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/related_notes.py +1920 -0
  230. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/related_notes_headless.py +1186 -0
  231. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/__init__.py +1 -0
  232. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/plan_attestation.py +148 -0
  233. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_receipts.py +360 -0
  234. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_runtime.py +52 -0
  235. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_task_runner.py +2470 -0
  236. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/style/__init__.py +1 -0
  237. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/style/style.py +1952 -0
  238. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/subagents/__init__.py +1 -0
  239. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/subagents/agents.py +1767 -0
  240. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/__init__.py +1 -0
  241. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/alias_projection.py +331 -0
  242. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/link_terms.py +151 -0
  243. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/llm_disambiguation.py +182 -0
  244. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/__init__.py +116 -0
  245. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/audit.py +201 -0
  246. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/migration.py +314 -0
  247. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/normalize.py +72 -0
  248. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/policy.py +135 -0
  249. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/resolve.py +413 -0
  250. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/schema.py +157 -0
  251. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/status.py +137 -0
  252. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_bootstrap.py +509 -0
  253. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_curator_batch.py +1115 -0
  254. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_ingestion.py +632 -0
  255. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_map.py +930 -0
  256. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_recovery.py +1388 -0
  257. package/.opencode/mednotes/src/mednotes/domains/wiki/cli.py +6665 -0
  258. package/.opencode/mednotes/src/mednotes/domains/wiki/common.py +69 -0
  259. package/.opencode/mednotes/src/mednotes/domains/wiki/config.py +210 -0
  260. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/__init__.py +74 -0
  261. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agent_report.py +242 -0
  262. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agent_run_audit.py +196 -0
  263. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agents.py +601 -0
  264. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/curator.py +256 -0
  265. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/effect_payloads.py +519 -0
  266. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/happy_path.py +190 -0
  267. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/link_git.py +110 -0
  268. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/link_runtime_artifact.py +52 -0
  269. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/note_plan.py +75 -0
  270. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/paths.py +114 -0
  271. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/public_report.py +53 -0
  272. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/publish.py +111 -0
  273. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/raw_coverage.py +217 -0
  274. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes.py +136 -0
  275. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes_headless.py +153 -0
  276. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes_runtime.py +395 -0
  277. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/schema_registry.py +637 -0
  278. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/specialist.py +432 -0
  279. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/status.py +62 -0
  280. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/style_rewrite.py +568 -0
  281. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/vocabulary_ingestion.py +223 -0
  282. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_blockers.py +510 -0
  283. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_guardrails.py +637 -0
  284. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_outcomes.py +121 -0
  285. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_receipts.py +100 -0
  286. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/__init__.py +1 -0
  287. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/__init__.py +1 -0
  288. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/__main__.py +4 -0
  289. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/cli.py +275 -0
  290. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/__init__.py +2 -0
  291. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/candidates.py +193 -0
  292. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/cli.py +189 -0
  293. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/gemini.py +220 -0
  294. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/inputs.py +120 -0
  295. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/models.py +34 -0
  296. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/parsing.py +48 -0
  297. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/prompts.py +216 -0
  298. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/quality.py +54 -0
  299. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/reporting.py +24 -0
  300. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/runner.py +433 -0
  301. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/utils.py +39 -0
  302. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/vault_guard_bridge.py +17 -0
  303. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/__init__.py +1 -0
  304. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_context_packets.py +454 -0
  305. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_decision_projection.py +133 -0
  306. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_effects.py +1260 -0
  307. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_fsm.py +2768 -0
  308. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_machine.py +1588 -0
  309. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_plan.py +306 -0
  310. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_primary_objective.py +316 -0
  311. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_problem.py +153 -0
  312. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_receipt_evidence.py +306 -0
  313. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_states.py +290 -0
  314. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_user_report.py +342 -0
  315. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/health.py +6332 -0
  316. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/__init__.py +1 -0
  317. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_fsm.py +1119 -0
  318. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_git.py +638 -0
  319. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_machine.py +1106 -0
  320. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_retry_governance.py +374 -0
  321. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_runtime_result.py +485 -0
  322. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_triggers.py +183 -0
  323. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/linking.py +2758 -0
  324. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/reference_repair.py +718 -0
  325. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/related_notes_fsm.py +1855 -0
  326. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link_related/__init__.py +1 -0
  327. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link_related/link_related_machine.py +834 -0
  328. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/__init__.py +1 -0
  329. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_fsm.py +1592 -0
  330. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_machine.py +3097 -0
  331. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_primary_objective.py +28 -0
  332. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_runtime_result.py +185 -0
  333. package/.opencode/mednotes/src/mednotes/domains/wiki/performance.py +97 -0
  334. package/.opencode/mednotes/src/mednotes/kernel/__init__.py +6 -0
  335. package/.opencode/mednotes/src/mednotes/kernel/agent_directive.py +336 -0
  336. package/.opencode/mednotes/src/mednotes/kernel/base.py +51 -0
  337. package/.opencode/mednotes/src/mednotes/kernel/blockers.py +39 -0
  338. package/.opencode/mednotes/src/mednotes/kernel/effect_executor.py +55 -0
  339. package/.opencode/mednotes/src/mednotes/kernel/effect_intent.py +69 -0
  340. package/.opencode/mednotes/src/mednotes/kernel/effects.py +160 -0
  341. package/.opencode/mednotes/src/mednotes/kernel/errors.py +38 -0
  342. package/.opencode/mednotes/src/mednotes/kernel/fsm_event.py +35 -0
  343. package/.opencode/mednotes/src/mednotes/kernel/fsm_model.py +55 -0
  344. package/.opencode/mednotes/src/mednotes/kernel/fsm_transition_result.py +75 -0
  345. package/.opencode/mednotes/src/mednotes/kernel/guardrails.py +188 -0
  346. package/.opencode/mednotes/src/mednotes/kernel/progress.py +319 -0
  347. package/.opencode/mednotes/src/mednotes/kernel/public_report.py +346 -0
  348. package/.opencode/mednotes/src/mednotes/kernel/state_machine.py +164 -0
  349. package/.opencode/mednotes/src/mednotes/kernel/workflow.py +619 -0
  350. package/.opencode/mednotes/src/mednotes/platform/__init__.py +5 -0
  351. package/.opencode/mednotes/src/mednotes/platform/backup_policy.py +382 -0
  352. package/.opencode/mednotes/src/mednotes/platform/feedback/__init__.py +62 -0
  353. package/.opencode/mednotes/src/mednotes/platform/feedback/cli.py +275 -0
  354. package/.opencode/mednotes/src/mednotes/platform/feedback/contracts.py +83 -0
  355. package/.opencode/mednotes/src/mednotes/platform/feedback/core.py +4168 -0
  356. package/.opencode/mednotes/src/mednotes/platform/feedback/integrity.py +989 -0
  357. package/.opencode/mednotes/src/mednotes/platform/feedback/operational_contract.py +2293 -0
  358. package/.opencode/mednotes/src/mednotes/platform/feedback/telemetry.py +875 -0
  359. package/.opencode/mednotes/src/mednotes/platform/feedback/telemetry_config.py +65 -0
  360. package/.opencode/mednotes/src/mednotes/platform/opencode_runtime_config.py +182 -0
  361. package/.opencode/mednotes/src/mednotes/platform/paths/__init__.py +1560 -0
  362. package/.opencode/mednotes/src/mednotes/platform/secrets.py +89 -0
  363. package/.opencode/mednotes/src/mednotes/platform/user_config.py +103 -0
  364. package/.opencode/mednotes/src/mednotes/platform/vault_guard.py +214 -0
  365. package/.opencode/mednotes/uv.lock +932 -0
  366. package/.opencode/mednotes.generated.json +395 -0
  367. package/.opencode/opencode.json +31 -0
  368. package/.opencode/plugins/mednotes-fsm.mjs +7 -0
  369. package/.opencode/plugins/mednotes_hook/adapters/antigravity.mjs +169 -0
  370. package/.opencode/plugins/mednotes_hook/adapters/harness_payload.mjs +103 -0
  371. package/.opencode/plugins/mednotes_hook/adapters/opencode_plugin.mjs +341 -0
  372. package/.opencode/plugins/mednotes_hook/adapters/opencode_user_config_sync.mjs +177 -0
  373. package/.opencode/plugins/mednotes_hook/anki_preflight.mjs +214 -0
  374. package/.opencode/plugins/mednotes_hook/cli.mjs +143 -0
  375. package/.opencode/plugins/mednotes_hook/diagnostics.mjs +11 -0
  376. package/.opencode/plugins/mednotes_hook/domain/agent_directive_core.mjs +160 -0
  377. package/.opencode/plugins/mednotes_hook/fsm_directive.mjs +1470 -0
  378. package/.opencode/plugins/mednotes_hook/hook_errors.mjs +120 -0
  379. package/.opencode/plugins/mednotes_hook/retention.mjs +114 -0
  380. package/.opencode/plugins/mednotes_hook/runtime.mjs +174 -0
  381. package/.opencode/plugins/mednotes_hook/telemetry_capture.mjs +511 -0
  382. package/.opencode/plugins/mednotes_hook/vault_guard.mjs +624 -0
  383. package/AGENTS.md +57 -0
  384. package/README.md +194 -0
  385. package/adapters/antigravity/agents.json +80 -0
  386. package/adapters/antigravity/templates/med-chat-triager.md +214 -0
  387. package/adapters/antigravity/templates/med-flashcard-maker.md +72 -0
  388. package/adapters/antigravity/templates/med-knowledge-architect.md +241 -0
  389. package/adapters/antigravity/templates/med-link-graph-curator.md +187 -0
  390. package/adapters/antigravity/templates/med-publish-guard.md +71 -0
  391. package/adapters/gemini-cli/gemini-extension.json +14 -0
  392. package/adapters/gemini-cli/package.json +15 -0
  393. package/adapters/gemini-cli/pyproject.toml +48 -0
  394. package/bin/mednotes-opencode.mjs +155 -0
  395. package/contracts/agents.json +116 -0
  396. package/core/agents/med-chat-triager.md +197 -0
  397. package/core/agents/med-flashcard-maker.md +56 -0
  398. package/core/agents/med-knowledge-architect.md +224 -0
  399. package/core/agents/med-link-graph-curator.md +171 -0
  400. package/core/agents/med-publish-guard.md +55 -0
  401. package/core/commands/flashcards.toml +22 -0
  402. package/core/commands/mednotes/create.toml +22 -0
  403. package/core/commands/mednotes/enrich.toml +24 -0
  404. package/core/commands/mednotes/fix-wiki.toml +24 -0
  405. package/core/commands/mednotes/history.toml +19 -0
  406. package/core/commands/mednotes/link-body.toml +22 -0
  407. package/core/commands/mednotes/link-related.toml +24 -0
  408. package/core/commands/mednotes/link.toml +24 -0
  409. package/core/commands/mednotes/pdf-library.toml +24 -0
  410. package/core/commands/mednotes/process-chats.toml +20 -0
  411. package/core/commands/mednotes/setup.toml +18 -0
  412. package/core/commands/mednotes/status.toml +24 -0
  413. package/core/commands/mednotes/telemetry.toml +24 -0
  414. package/core/commands/report.toml +23 -0
  415. package/core/skills/THIRD_PARTY_NOTICES.md +45 -0
  416. package/core/skills/create-medical-flashcards/SKILL.md +113 -0
  417. package/core/skills/create-medical-note/SKILL.md +90 -0
  418. package/core/skills/enrich-medical-note/SKILL.md +120 -0
  419. package/core/skills/fix-medical-wiki/SKILL.md +559 -0
  420. package/core/skills/link-medical-wiki/SKILL.md +224 -0
  421. package/core/skills/obsidian-cli/SKILL.md +118 -0
  422. package/core/skills/obsidian-markdown/SKILL.md +207 -0
  423. package/core/skills/obsidian-markdown/references/CALLOUTS.md +58 -0
  424. package/core/skills/obsidian-markdown/references/EMBEDS.md +63 -0
  425. package/core/skills/obsidian-markdown/references/PROPERTIES.md +61 -0
  426. package/core/skills/obsidian-ops/SKILL.md +136 -0
  427. package/core/skills/pdf-library/SKILL.md +45 -0
  428. package/core/skills/process-medical-chats/SKILL.md +246 -0
  429. package/core/skills/workflow-report/SKILL.md +100 -0
  430. package/package.json +45 -0
@@ -0,0 +1,409 @@
1
+ """Triage-authored note plan validation (schema v2, hard cut).
2
+
3
+ v2 actions:
4
+
5
+ - ``planned_meaning``: durable semantic unit declared from the raw chat.
6
+ Requires ``meaning_claim`` with label/scope/boundaries/kind/evidence_summary.
7
+ - ``attach_to_planned_meaning``: subordinate detail that belongs to another
8
+ unit of the same raw chat. Carries ``target_item_id`` pointing to a sibling
9
+ ``planned_meaning`` item.
10
+ - ``not_a_note``: content that should not become a Wiki note.
11
+ - ``needs_context``: raw chat does not support safe segmentation for this
12
+ unit.
13
+
14
+ Older note-plan schemas are rejected at the boundary. Runtime code does not
15
+ migrate or reinterpret them; the triage contract must be regenerated as v2.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import re
21
+ from pathlib import Path
22
+
23
+ from pydantic import ValidationError as PydanticValidationError
24
+
25
+ from mednotes.domains.wiki.batch_state import NOTE_PLAN_SELF_HASH_KEYS, artifact_json_hash, clean_state_value
26
+ from mednotes.domains.wiki.capabilities.vocabulary.link_terms import normalize_key
27
+ from mednotes.domains.wiki.common import MissingPathError, ValidationError
28
+ from mednotes.domains.wiki.config import _path
29
+ from mednotes.domains.wiki.contracts.note_plan import TriageNotePlan
30
+ from mednotes.kernel.base import JsonObject, JsonObjectAdapter, contract_error
31
+
32
+ TRIAGE_NOTE_PLAN_SCHEMA = "medical-notes-workbench.triage-note-plan.v2"
33
+ TRIAGE_NOTE_PLAN_V2_SCHEMA = TRIAGE_NOTE_PLAN_SCHEMA
34
+
35
+ PLANNED_MEANING_ACTION = "planned_meaning"
36
+ ATTACH_TO_PLANNED_MEANING_ACTION = "attach_to_planned_meaning"
37
+ NOT_A_NOTE_ACTION = "not_a_note"
38
+ NEEDS_CONTEXT_ACTION = "needs_context"
39
+
40
+ ALLOWED_ACTIONS = {
41
+ PLANNED_MEANING_ACTION,
42
+ ATTACH_TO_PLANNED_MEANING_ACTION,
43
+ NOT_A_NOTE_ACTION,
44
+ NEEDS_CONTEXT_ACTION,
45
+ }
46
+ ALLOWED_V2_ACTIONS = ALLOWED_ACTIONS
47
+
48
+ # Closed sets used by triage-policy.md.
49
+ MEANING_CLAIM_KINDS = {
50
+ "clinical_concept",
51
+ "drug_concept",
52
+ "diagnostic_criterion",
53
+ "management_strategy",
54
+ "procedure",
55
+ "physiology_or_mechanism",
56
+ "epidemiology_or_definition",
57
+ }
58
+ MEANING_CLAIM_REQUIRED_FIELDS = ("label", "scope", "boundaries", "kind", "evidence_summary")
59
+
60
+ ATTACH_REASON_CODES = {
61
+ "supporting_detail",
62
+ "boundary_clarification",
63
+ "example_or_case",
64
+ "cross_reference",
65
+ }
66
+ NOT_A_NOTE_REASON_CODES = {
67
+ "administrative_chatter",
68
+ "repetition_no_new_information",
69
+ "out_of_scope_for_medical_wiki",
70
+ "low_value_fragment",
71
+ }
72
+ NEEDS_CONTEXT_REASON_CODES = {
73
+ "evidence_insufficient",
74
+ "multiple_topics_undifferentiated",
75
+ "clinical_ambiguity",
76
+ "language_or_encoding_blocker",
77
+ }
78
+
79
+ TRIAGE_NOTE_PLAN_BATCH_KEYS = ("batch_id", "run_id", "source_artifact_hash")
80
+ _FENCED_JSON_RE = re.compile(r"```(?:json|JSON)?[ \t]*\n(?P<body>.*?)\n```", re.DOTALL)
81
+ _MOJIBAKE_RE = re.compile(r"(?:Ã.|Â.|â€|–|—|�)")
82
+
83
+
84
+ def _json_object(value: object, *, message: str) -> JsonObject:
85
+ """Normalize raw JSON before note-plan policy reads operational fields."""
86
+
87
+ if not isinstance(value, dict):
88
+ raise ValidationError(message)
89
+ return JsonObjectAdapter.validate_python(value)
90
+
91
+
92
+ def _json_field(payload: JsonObject, key: str) -> object:
93
+ if key not in payload:
94
+ return None
95
+ return payload[key]
96
+
97
+
98
+ def _json_str_field(payload: JsonObject, key: str) -> str:
99
+ value = _json_field(payload, key)
100
+ return str(value).strip() if value else ""
101
+
102
+
103
+ def _paths_match(left: str, right: Path) -> bool:
104
+ left_path = _path(left)
105
+ try:
106
+ return left_path.resolve() == right.resolve()
107
+ except OSError:
108
+ return str(left_path) == str(right)
109
+
110
+
111
+ def _load_json_file(path: Path) -> JsonObject:
112
+ if not path.exists():
113
+ raise MissingPathError(f"Triage note plan not found: {path}")
114
+ try:
115
+ data = json.loads(path.read_text(encoding="utf-8"))
116
+ except json.JSONDecodeError as exc:
117
+ raise ValidationError(f"Invalid triage note plan JSON: {exc}") from exc
118
+ return _json_object(data, message="Triage note plan must be a JSON object")
119
+
120
+
121
+ def _validate_meaning_claim(item_id: str, raw_claim: object) -> JsonObject:
122
+ raw_claim = _json_object(
123
+ raw_claim,
124
+ message=f"note_plan_meaning_claim_missing: item {item_id} requires meaning_claim object",
125
+ )
126
+ claim: JsonObject = {}
127
+ for field in MEANING_CLAIM_REQUIRED_FIELDS:
128
+ if field not in raw_claim:
129
+ raise ValidationError(
130
+ f"note_plan_meaning_claim_missing: item {item_id} meaning_claim missing {field!r}"
131
+ )
132
+ label = _json_str_field(raw_claim, "label")
133
+ if not label:
134
+ raise ValidationError(
135
+ f"Triage note plan item {item_id} meaning_claim.label must be non-empty"
136
+ )
137
+ scope = _json_str_field(raw_claim, "scope")
138
+ if not scope:
139
+ raise ValidationError(
140
+ f"Triage note plan item {item_id} meaning_claim.scope must be non-empty"
141
+ )
142
+ boundaries_raw = _json_field(raw_claim, "boundaries")
143
+ if not isinstance(boundaries_raw, list):
144
+ raise ValidationError(
145
+ f"Triage note plan item {item_id} meaning_claim.boundaries must be a list"
146
+ )
147
+ boundaries = [str(entry).strip() for entry in boundaries_raw if str(entry).strip()]
148
+ kind = _json_str_field(raw_claim, "kind")
149
+ if kind not in MEANING_CLAIM_KINDS:
150
+ raise ValidationError(
151
+ f"Triage note plan item {item_id} meaning_claim.kind={kind!r} is not one of "
152
+ f"{sorted(MEANING_CLAIM_KINDS)}"
153
+ )
154
+ evidence_summary = _json_str_field(raw_claim, "evidence_summary")
155
+ if not evidence_summary:
156
+ raise ValidationError(
157
+ f"Triage note plan item {item_id} meaning_claim.evidence_summary must be non-empty"
158
+ )
159
+ for field, value in {
160
+ "meaning_claim.label": label,
161
+ "meaning_claim.scope": scope,
162
+ "meaning_claim.kind": kind,
163
+ "meaning_claim.evidence_summary": evidence_summary,
164
+ **{f"meaning_claim.boundaries[{idx}]": boundary for idx, boundary in enumerate(boundaries)},
165
+ }.items():
166
+ _reject_mojibake(value, field=field, item_id=item_id)
167
+ claim["label"] = label
168
+ claim["scope"] = scope
169
+ claim["boundaries"] = boundaries
170
+ claim["kind"] = kind
171
+ claim["evidence_summary"] = evidence_summary
172
+ meaning_id = _json_str_field(raw_claim, "id") or _json_str_field(raw_claim, "meaning_id")
173
+ if meaning_id:
174
+ claim["id"] = meaning_id
175
+ return claim
176
+
177
+
178
+ def _reject_mojibake(value: str, *, field: str, item_id: str) -> None:
179
+ if _MOJIBAKE_RE.search(value):
180
+ raise ValidationError(
181
+ f"Triage note plan item {item_id} {field} contains mojibake/encoding corruption: {value}"
182
+ )
183
+
184
+
185
+ def _normalized_items(items: object) -> list[JsonObject]:
186
+ if not isinstance(items, list) or not items:
187
+ raise ValidationError("Triage note plan must contain a non-empty items list")
188
+
189
+ normalized: list[JsonObject] = []
190
+ seen_ids: set[str] = set()
191
+ seen_planned_titles: dict[str, str] = {}
192
+ planned_ids: set[str] = set()
193
+ pending_attach: list[tuple[int, str, str]] = []
194
+
195
+ for index, raw_item in enumerate(items, start=1):
196
+ raw_item = _json_object(raw_item, message=f"Triage note plan item #{index} must be an object")
197
+ item_id = _json_str_field(raw_item, "id") or f"T{index:03d}"
198
+ action = _json_str_field(raw_item, "action")
199
+ if not item_id:
200
+ raise ValidationError(f"Triage note plan item #{index} missing id")
201
+ if item_id in seen_ids:
202
+ raise ValidationError(f"Triage note plan item id duplicated: {item_id}")
203
+ if action not in ALLOWED_ACTIONS:
204
+ raise ValidationError(
205
+ f"Triage note plan item {item_id} has invalid action {action!r}; "
206
+ f"expected one of {', '.join(sorted(ALLOWED_ACTIONS))}"
207
+ )
208
+
209
+ item: JsonObject = {"id": item_id, "action": action}
210
+
211
+ if action == PLANNED_MEANING_ACTION:
212
+ title = _json_str_field(raw_item, "title")
213
+ if not title:
214
+ raise ValidationError(
215
+ f"Triage note plan item {item_id} missing title"
216
+ )
217
+ staged_title = _json_str_field(raw_item, "staged_title") or title
218
+ if not staged_title:
219
+ raise ValidationError(
220
+ f"Triage note plan item {item_id} missing staged_title"
221
+ )
222
+ _reject_mojibake(title, field="title", item_id=item_id)
223
+ _reject_mojibake(staged_title, field="staged_title", item_id=item_id)
224
+ staged_key = normalize_key(staged_title)
225
+ if staged_key in seen_planned_titles:
226
+ raise ValidationError(
227
+ "note_plan_duplicate_meaning: planned_meaning title duplicated after accent/case "
228
+ f"normalization: {staged_title} conflicts with "
229
+ f"{seen_planned_titles[staged_key]}"
230
+ )
231
+ seen_planned_titles[staged_key] = staged_title
232
+ item["title"] = title
233
+ item["staged_title"] = staged_title
234
+ item["meaning_claim"] = _validate_meaning_claim(item_id, _json_field(raw_item, "meaning_claim"))
235
+ taxonomy_hint = _json_str_field(raw_item, "taxonomy_hint")
236
+ if taxonomy_hint:
237
+ _reject_mojibake(taxonomy_hint, field="taxonomy_hint", item_id=item_id)
238
+ item["taxonomy_hint"] = taxonomy_hint
239
+ aliases = _json_field(raw_item, "aliases")
240
+ if isinstance(aliases, list):
241
+ clean_aliases = [str(alias).strip() for alias in aliases if str(alias).strip()]
242
+ if clean_aliases:
243
+ for alias_idx, alias in enumerate(clean_aliases):
244
+ _reject_mojibake(alias, field=f"aliases[{alias_idx}]", item_id=item_id)
245
+ item["aliases"] = clean_aliases
246
+ planned_ids.add(item_id)
247
+ elif action == ATTACH_TO_PLANNED_MEANING_ACTION:
248
+ target = _json_str_field(raw_item, "target_item_id")
249
+ if not target:
250
+ raise ValidationError(
251
+ f"note_plan_target_item_id_missing: item {item_id} attach action missing target_item_id"
252
+ )
253
+ reason_code = _json_str_field(raw_item, "reason_code")
254
+ if reason_code not in ATTACH_REASON_CODES:
255
+ raise ValidationError(
256
+ f"note_plan_reason_code_missing: item {item_id} attach reason_code={reason_code!r} "
257
+ f"is not one of {sorted(ATTACH_REASON_CODES)}"
258
+ )
259
+ reason = _json_str_field(raw_item, "reason")
260
+ if not reason:
261
+ raise ValidationError(
262
+ f"Triage note plan item {item_id} attach action missing reason"
263
+ )
264
+ item["target_item_id"] = target
265
+ item["reason_code"] = reason_code
266
+ item["reason"] = reason
267
+ pending_attach.append((index, item_id, target))
268
+ elif action == NOT_A_NOTE_ACTION:
269
+ reason_code = _json_str_field(raw_item, "reason_code")
270
+ if reason_code not in NOT_A_NOTE_REASON_CODES:
271
+ raise ValidationError(
272
+ f"note_plan_reason_code_missing: item {item_id} not_a_note reason_code={reason_code!r} "
273
+ f"is not one of {sorted(NOT_A_NOTE_REASON_CODES)}"
274
+ )
275
+ reason = _json_str_field(raw_item, "reason")
276
+ if not reason:
277
+ raise ValidationError(
278
+ f"Triage note plan item {item_id} not_a_note missing reason"
279
+ )
280
+ item["reason_code"] = reason_code
281
+ item["reason"] = reason
282
+ elif action == NEEDS_CONTEXT_ACTION:
283
+ reason_code = _json_str_field(raw_item, "reason_code")
284
+ if reason_code not in NEEDS_CONTEXT_REASON_CODES:
285
+ raise ValidationError(
286
+ f"note_plan_reason_code_missing: item {item_id} needs_context reason_code={reason_code!r} "
287
+ f"is not one of {sorted(NEEDS_CONTEXT_REASON_CODES)}"
288
+ )
289
+ reason = _json_str_field(raw_item, "reason")
290
+ if not reason:
291
+ raise ValidationError(
292
+ f"Triage note plan item {item_id} needs_context missing reason"
293
+ )
294
+ item["reason_code"] = reason_code
295
+ item["reason"] = reason
296
+
297
+ normalized.append(item)
298
+ seen_ids.add(item_id)
299
+
300
+ for _, attach_id, target in pending_attach:
301
+ if target not in planned_ids:
302
+ raise ValidationError(
303
+ f"note_plan_target_item_id_missing: item {attach_id} attach target_item_id={target!r} "
304
+ "must reference a sibling planned_meaning item"
305
+ )
306
+
307
+ return normalized
308
+
309
+
310
+ def _reject_unsupported_schema(schema_value: str) -> None:
311
+ raise ValidationError(
312
+ f"Triage note plan schema must be {TRIAGE_NOTE_PLAN_SCHEMA}, got {schema_value!r}"
313
+ )
314
+
315
+
316
+ def normalize_triage_note_plan(data: JsonObject, raw_file: Path) -> JsonObject:
317
+ data = _json_object(data, message="Triage note plan must be a JSON object")
318
+ schema_value = _json_str_field(data, "schema")
319
+ if schema_value != TRIAGE_NOTE_PLAN_SCHEMA:
320
+ _reject_unsupported_schema(schema_value)
321
+ raw_value = _json_str_field(data, "raw_file")
322
+ if not raw_value:
323
+ raise ValidationError("Triage note plan missing raw_file")
324
+ if not _paths_match(raw_value, raw_file):
325
+ raise ValidationError(f"note_plan_raw_file_mismatch: triage note plan raw_file does not match: {raw_value}")
326
+ if _json_field(data, "exhaustive") is not True:
327
+ raise ValidationError("Triage note plan must set exhaustive: true")
328
+ normalized = {
329
+ "schema": TRIAGE_NOTE_PLAN_SCHEMA,
330
+ "raw_file": str(raw_file),
331
+ "exhaustive": True,
332
+ "items": _normalized_items(_json_field(data, "items")),
333
+ }
334
+ for key in TRIAGE_NOTE_PLAN_BATCH_KEYS:
335
+ value = clean_state_value(_json_field(data, key))
336
+ if value:
337
+ normalized[key] = value
338
+ try:
339
+ return TriageNotePlan.model_validate(normalized).to_payload()
340
+ except PydanticValidationError as exc:
341
+ raise contract_error(exc, prefix="triage_note_plan.contract_invalid") from exc
342
+
343
+
344
+ def normalize_triage_note_plan_v2(data: JsonObject, raw_file: Path) -> JsonObject:
345
+ return normalize_triage_note_plan(data, raw_file)
346
+
347
+
348
+ def load_triage_note_plan(path: Path, raw_file: Path) -> JsonObject:
349
+ return normalize_triage_note_plan(_load_json_file(path), raw_file)
350
+
351
+
352
+ def parse_triage_note_plan(value: str, raw_file: Path) -> JsonObject:
353
+ try:
354
+ data = json.loads(value)
355
+ except json.JSONDecodeError as exc:
356
+ fences = [match.group("body").strip() for match in _FENCED_JSON_RE.finditer(value)]
357
+ if len(fences) > 1:
358
+ raise ValidationError(
359
+ "note_plan_invalid: multiple fenced JSON blocks found; provide exactly one triage-note-plan object"
360
+ ) from exc
361
+ if len(fences) == 1:
362
+ try:
363
+ data = json.loads(fences[0])
364
+ except json.JSONDecodeError as fenced_exc:
365
+ raise ValidationError(
366
+ f"note_plan_invalid: invalid fenced triage note plan JSON: {fenced_exc}"
367
+ ) from fenced_exc
368
+ else:
369
+ raise ValidationError(f"Invalid triage note plan in raw frontmatter: {exc}") from exc
370
+ return normalize_triage_note_plan(
371
+ _json_object(data, message="Triage note plan in raw frontmatter must be a JSON object"),
372
+ raw_file,
373
+ )
374
+
375
+
376
+ def serialize_triage_note_plan(data: JsonObject, raw_file: Path) -> str:
377
+ normalized = normalize_triage_note_plan(data, raw_file)
378
+ return json.dumps(normalized, ensure_ascii=False, separators=(",", ":"))
379
+
380
+
381
+ def note_plan_hash(plan: JsonObject) -> str:
382
+ return artifact_json_hash(plan, exclude_keys=NOTE_PLAN_SELF_HASH_KEYS)
383
+
384
+
385
+ def planned_meaning_titles(plan: JsonObject) -> set[str]:
386
+ validated = TriageNotePlan.model_validate(plan)
387
+ return {
388
+ title
389
+ for item in validated.items
390
+ if item.action == PLANNED_MEANING_ACTION
391
+ for title in [(item.staged_title or item.title or "").strip()]
392
+ if title
393
+ }
394
+
395
+
396
+ def note_plan_summary(plan: JsonObject) -> dict[str, int]:
397
+ validated = TriageNotePlan.model_validate(plan)
398
+ counts = dict.fromkeys(ALLOWED_ACTIONS, 0)
399
+ for item in validated.items:
400
+ action = item.action
401
+ if action in counts:
402
+ counts[action] += 1
403
+ return {
404
+ "note_plan_item_count": sum(counts.values()),
405
+ "note_plan_planned_meaning_count": counts[PLANNED_MEANING_ACTION],
406
+ "note_plan_attach_count": counts[ATTACH_TO_PLANNED_MEANING_ACTION],
407
+ "note_plan_not_a_note_count": counts[NOT_A_NOTE_ACTION],
408
+ "note_plan_needs_context_count": counts[NEEDS_CONTEXT_ACTION],
409
+ }
@@ -0,0 +1,22 @@
1
+ """Shared note classification policy for Wiki workflows."""
2
+ from __future__ import annotations
3
+
4
+ from enum import StrEnum
5
+ from pathlib import Path
6
+
7
+ from mednotes.domains.wiki.capabilities.vocabulary.link_terms import is_index_note
8
+
9
+
10
+ class NoteKind(StrEnum):
11
+ MEDICAL_WIKI = "medical_wiki"
12
+ OPERATIONAL_INDEX = "operational_index"
13
+
14
+
15
+ def classify_note(path: Path, content: str) -> NoteKind:
16
+ if is_index_note(path, content):
17
+ return NoteKind.OPERATIONAL_INDEX
18
+ return NoteKind.MEDICAL_WIKI
19
+
20
+
21
+ def is_operational_index_note(path: Path, content: str) -> bool:
22
+ return classify_note(path, content) == NoteKind.OPERATIONAL_INDEX
@@ -0,0 +1,79 @@
1
+ """Deterministic style contract for Wiki_Medicina notes."""
2
+ from __future__ import annotations
3
+
4
+ from importlib import import_module
5
+ from typing import Any
6
+
7
+ from mednotes.domains.wiki.capabilities.notes.note_style.frontmatter import (
8
+ canonical_wiki_tags,
9
+ infer_title,
10
+ normalize_wiki_frontmatter,
11
+ parse_frontmatter,
12
+ raw_meta_from_file,
13
+ split_frontmatter,
14
+ wiki_frontmatter_aliases,
15
+ )
16
+ from mednotes.domains.wiki.capabilities.notes.note_style.models import (
17
+ NOTE_MODEL_CHANGE_RULE,
18
+ NOTE_MODEL_ISSUE_COVERAGE,
19
+ PREFERRED_H2_EMOJIS,
20
+ REQUIRED_SECTION_LINES,
21
+ REWRITE_REQUIRED_CODES,
22
+ STYLE_AUDIT_SCHEMA,
23
+ STYLE_FIX_SCHEMA,
24
+ STYLE_REPORT_SCHEMA,
25
+ WIKI_INDEX_LINK,
26
+ StyleIssue,
27
+ )
28
+ from mednotes.domains.wiki.capabilities.notes.note_style.prompts import rewrite_prompt
29
+ from mednotes.domains.wiki.capabilities.notes.note_style.tables import (
30
+ check_tables,
31
+ escape_wikilink_alias_pipes_in_tables,
32
+ normalize_markdown_tables,
33
+ )
34
+
35
+ _LAZY_EXPORTS = {
36
+ "fix_note_style": ("mednotes.domains.wiki.capabilities.notes.note_style.fixes", "fix_note_style"),
37
+ "index_style_report": ("mednotes.domains.wiki.capabilities.notes.note_style.validate", "index_style_report"),
38
+ "validate_note_style": ("mednotes.domains.wiki.capabilities.notes.note_style.validate", "validate_note_style"),
39
+ "validate_wiki_dir": ("mednotes.domains.wiki.capabilities.notes.note_style.validate", "validate_wiki_dir"),
40
+ }
41
+
42
+
43
+ def __getattr__(name: str) -> Any:
44
+ target = _LAZY_EXPORTS.get(name)
45
+ if target is None:
46
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
47
+ module_name, attr_name = target
48
+ value = getattr(import_module(module_name), attr_name)
49
+ globals()[name] = value
50
+ return value
51
+
52
+
53
+ __all__ = [
54
+ "NOTE_MODEL_CHANGE_RULE",
55
+ "NOTE_MODEL_ISSUE_COVERAGE",
56
+ "PREFERRED_H2_EMOJIS",
57
+ "REQUIRED_SECTION_LINES",
58
+ "REWRITE_REQUIRED_CODES",
59
+ "STYLE_AUDIT_SCHEMA",
60
+ "STYLE_FIX_SCHEMA",
61
+ "STYLE_REPORT_SCHEMA",
62
+ "StyleIssue",
63
+ "WIKI_INDEX_LINK",
64
+ "check_tables",
65
+ "canonical_wiki_tags",
66
+ "escape_wikilink_alias_pipes_in_tables",
67
+ "fix_note_style",
68
+ "infer_title",
69
+ "index_style_report",
70
+ "normalize_markdown_tables",
71
+ "normalize_wiki_frontmatter",
72
+ "parse_frontmatter",
73
+ "raw_meta_from_file",
74
+ "rewrite_prompt",
75
+ "split_frontmatter",
76
+ "validate_note_style",
77
+ "validate_wiki_dir",
78
+ "wiki_frontmatter_aliases",
79
+ ]