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,121 @@
1
+ """Typed workflow outcome facade for Wiki domain callers.
2
+
3
+ This module deliberately does not reconstruct decisions from legacy
4
+ `status`/`blocked_reason` fields. A caller that wants a `WorkflowDecision` must
5
+ provide the typed `decision_summary` or a typed `human_decision_packet`; otherwise
6
+ the boundary fails closed and the emitting workflow must be fixed at the source.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from pydantic import Field, StrictStr
11
+ from pydantic import ValidationError as PydanticValidationError
12
+
13
+ from mednotes.kernel.base import ContractModel, JsonObject
14
+ from mednotes.kernel.workflow import (
15
+ AutomationKind,
16
+ DecisionEvidence,
17
+ DecisionKind,
18
+ HumanDecisionOption,
19
+ HumanDecisionPacket,
20
+ RejectedAutomation,
21
+ WorkflowAutomationKind,
22
+ WorkflowDecision,
23
+ WorkflowDecisionKind,
24
+ WorkflowDecisionSummary,
25
+ WorkflowOutcomeError,
26
+ attach_human_decision_packet,
27
+ )
28
+
29
+ __all__ = [
30
+ "AutomationKind",
31
+ "DecisionEvidence",
32
+ "DecisionKind",
33
+ "HumanDecisionOption",
34
+ "HumanDecisionPacket",
35
+ "RejectedAutomation",
36
+ "WorkflowAutomationKind",
37
+ "WorkflowDecision",
38
+ "WorkflowDecisionKind",
39
+ "WorkflowOutcomeError",
40
+ "attach_human_decision_packet",
41
+ "decision_payload_from_decision",
42
+ "decision_from_payload",
43
+ ]
44
+
45
+
46
+ class _WorkflowDecisionSourcePayload(ContractModel):
47
+ """Typed subset used to recover an already-declared decision from JSON."""
48
+
49
+ decision_summary: WorkflowDecisionSummary | None = None
50
+ human_decision_packet: HumanDecisionPacket | None = None
51
+ next_action: StrictStr = ""
52
+ required_inputs: list[StrictStr] = Field(default_factory=list)
53
+
54
+
55
+ def decision_from_payload(payload: JsonObject) -> WorkflowDecision:
56
+ """Validate a typed decision already present in an operational payload."""
57
+ try:
58
+ return _decision_from_payload(_decision_source_payload(payload))
59
+ except PydanticValidationError as exc:
60
+ raise WorkflowOutcomeError(f"invalid workflow decision payload: {exc}") from exc
61
+
62
+
63
+ def _decision_source_payload(payload: JsonObject) -> _WorkflowDecisionSourcePayload:
64
+ raw: dict[str, object] = {}
65
+ for key in ("decision_summary", "human_decision_packet", "next_action", "required_inputs"):
66
+ if key in payload:
67
+ raw[key] = payload[key]
68
+ return _WorkflowDecisionSourcePayload.model_validate(raw)
69
+
70
+
71
+ def _decision_from_payload(source: _WorkflowDecisionSourcePayload) -> WorkflowDecision:
72
+ packet = source.human_decision_packet
73
+ summary = source.decision_summary or (packet.decision_summary if packet is not None else None)
74
+ if summary is None:
75
+ raise WorkflowOutcomeError("decision_summary is required; legacy status fields cannot create decisions")
76
+
77
+ next_action = source.next_action or (packet.resume_action if packet is not None else "")
78
+ # Ask-human packets carry the closed option set and recommended option.
79
+ # Reconstructing an intermediate WorkflowDecision without those fields would
80
+ # trip the contract before the packet data is folded back in.
81
+ if packet is not None:
82
+ return WorkflowDecision(
83
+ kind=summary.kind,
84
+ phase=summary.phase,
85
+ reason_code=summary.reason_code,
86
+ public_summary=summary.public_summary,
87
+ developer_summary=summary.developer_summary,
88
+ evidence=summary.evidence,
89
+ rejected_automations=packet.rejected_automations,
90
+ next_action=next_action,
91
+ required_inputs=list(source.required_inputs),
92
+ human_decision_kind=packet.kind,
93
+ resume_action=packet.resume_action,
94
+ recommended_option_id=packet.recommended_option_id,
95
+ options=packet.options,
96
+ )
97
+ return WorkflowDecision(
98
+ kind=summary.kind,
99
+ phase=summary.phase,
100
+ reason_code=summary.reason_code,
101
+ public_summary=summary.public_summary,
102
+ developer_summary=summary.developer_summary,
103
+ evidence=summary.evidence,
104
+ rejected_automations=summary.rejected_automations,
105
+ next_action=next_action,
106
+ required_inputs=list(source.required_inputs),
107
+ )
108
+
109
+
110
+ def decision_payload_from_decision(decision: WorkflowDecision) -> JsonObject:
111
+ """Project a typed decision summary without manufacturing workflow state."""
112
+
113
+ payload: JsonObject = {
114
+ "decision_summary": decision.decision_summary(),
115
+ "next_action": decision.next_action,
116
+ "required_inputs": list(decision.required_inputs),
117
+ "human_decision_required": decision.kind == WorkflowDecisionKind.ASK_HUMAN,
118
+ }
119
+ if decision.kind == WorkflowDecisionKind.ASK_HUMAN:
120
+ payload["human_decision_packet"] = decision.to_human_decision_packet()
121
+ return payload
@@ -0,0 +1,100 @@
1
+ """Receipt builder for workflow outcomes."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass, field
5
+
6
+ from pydantic import ValidationError as PydanticValidationError
7
+
8
+ from mednotes.domains.wiki.contracts.workflow_outcomes import WorkflowDecision
9
+ from mednotes.kernel.base import JsonObject
10
+ from mednotes.kernel.workflow import (
11
+ HumanDecisionPacket,
12
+ VersionControlSafety,
13
+ WorkflowArtifact,
14
+ WorkflowPhaseOutcome,
15
+ WorkflowPhaseReceipt,
16
+ WorkflowReceiptPayload,
17
+ WorkflowRollback,
18
+ )
19
+
20
+
21
+ class WorkflowReceiptError(ValueError):
22
+ """Raised when a workflow receipt violates the common contract."""
23
+
24
+
25
+ @dataclass
26
+ class WorkflowReceiptBuilder:
27
+ schema: str
28
+ workflow: str
29
+ run_id: str
30
+ phase_outcomes: list[WorkflowPhaseOutcome] = field(default_factory=list)
31
+ phase_receipts: dict[str, WorkflowPhaseReceipt] = field(default_factory=dict)
32
+ artifacts: list[WorkflowArtifact] = field(default_factory=list)
33
+ changed_files: list[str] = field(default_factory=list)
34
+ rollback: WorkflowRollback | None = None
35
+ no_resource_mutation: bool = False
36
+
37
+ def add_phase(self, phase: str, *, decision: WorkflowDecision) -> None:
38
+ human_packet = (
39
+ HumanDecisionPacket.model_validate(decision.to_human_decision_packet())
40
+ if decision.kind == "ask_human"
41
+ else None
42
+ )
43
+ self.phase_outcomes.append(
44
+ WorkflowPhaseOutcome(
45
+ phase=phase,
46
+ decision_summary=decision.decision_summary(),
47
+ human_decision_packet=human_packet,
48
+ )
49
+ )
50
+
51
+ def add_phase_receipt(self, phase: str, receipt_path: str, *, status: str = "") -> None:
52
+ self.phase_receipts[phase] = WorkflowPhaseReceipt(receipt_path=receipt_path, status=status)
53
+
54
+ def add_artifact(self, kind: str, path: str) -> None:
55
+ self.artifacts.append(WorkflowArtifact(kind=kind, path=path))
56
+
57
+ def add_mutation_summary(self, *, changed_paths: list[str]) -> None:
58
+ self.changed_files.extend(changed_paths)
59
+
60
+ def add_rollback(self, strategy: str, value: str) -> None:
61
+ self.rollback = WorkflowRollback(strategy=strategy, value=value)
62
+
63
+ def finalize(self, *, status: str, next_action: str = "") -> JsonObject:
64
+ if status in {"blocked", "failed", "completed_with_warnings", "completed_with_link_blockers"} and not next_action:
65
+ raise WorkflowReceiptError(f"{status} receipt requires next_action")
66
+ mutated = bool(self.changed_files)
67
+ if mutated and not self.rollback and not self.no_resource_mutation:
68
+ raise WorkflowReceiptError("mutated receipt requires rollback or no_resource_mutation")
69
+ try:
70
+ phase_outcomes = [WorkflowPhaseOutcome.model_validate(item) for item in self.phase_outcomes]
71
+ phase_receipts = {
72
+ phase: WorkflowPhaseReceipt.model_validate(receipt) for phase, receipt in self.phase_receipts.items()
73
+ }
74
+ artifacts = [WorkflowArtifact.model_validate(item) for item in self.artifacts]
75
+ rollback = WorkflowRollback.model_validate(self.rollback) if self.rollback else None
76
+ human_packets = [outcome.human_decision_packet for outcome in phase_outcomes if outcome.human_decision_packet]
77
+ receipt = WorkflowReceiptPayload(
78
+ schema=self.schema,
79
+ workflow=self.workflow,
80
+ run_id=self.run_id,
81
+ status=status, # type: ignore[arg-type]
82
+ mutated=mutated,
83
+ next_action=next_action,
84
+ human_decision_required=bool(human_packets),
85
+ human_decision_packet=human_packets[0] if human_packets else None,
86
+ phase_outcomes=phase_outcomes,
87
+ phase_receipts=phase_receipts,
88
+ artifacts=artifacts,
89
+ changed_files=list(self.changed_files),
90
+ rollback=rollback,
91
+ version_control_safety=VersionControlSafety(
92
+ no_resource_mutation=self.no_resource_mutation,
93
+ rollback_declared=bool(self.rollback),
94
+ ),
95
+ )
96
+ except PydanticValidationError as exc:
97
+ raise WorkflowReceiptError(f"invalid receipt payload: {exc}") from exc
98
+ payload = receipt.to_payload()
99
+ payload["rollback"] = payload["rollback"] or {}
100
+ return payload
@@ -0,0 +1 @@
1
+ """enrich — o workflow público /mednotes:enrich (flow)."""
@@ -0,0 +1,4 @@
1
+ from mednotes.domains.wiki.flows.enrich.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -0,0 +1,275 @@
1
+ """CLI do enricher — toolbox de subcomandos.
2
+
3
+ Cada subcomando faz **uma** coisa e devolve **JSON na stdout** (consumível por
4
+ agente). Erros vão pra stderr com exit code != 0.
5
+
6
+ Subcomandos:
7
+ - ``sections <nota.md>``: lista headings da nota com ``section_path``, ``level``,
8
+ ``start_line``, ``end_line``.
9
+ - ``search <source> --query <q> [--visual-type T] [--top-k N]``: busca candidatas
10
+ via adapter (``wikimedia`` ou ``web_search``).
11
+ - ``insert <nota.md> --section P --image F --concept C --source S --source-url U``:
12
+ insere bloco no fim da seção e atualiza frontmatter aditivamente. ``--section``
13
+ é repetível pra paths nested (ex: ``--section "🤖 Gemini" --section Mecanismo``).
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import json
19
+ import sys
20
+ from collections.abc import Callable
21
+ from dataclasses import asdict, is_dataclass
22
+ from pathlib import Path
23
+ from typing import Protocol
24
+
25
+ from mednotes.domains.wiki.capabilities.illustrate.core import frontmatter, insert
26
+ from mednotes.domains.wiki.capabilities.illustrate.core.cache import Cache
27
+ from mednotes.domains.wiki.capabilities.illustrate.core.config import expand_path, resolve_wiki_root, wiki_memory_path
28
+ from mednotes.domains.wiki.capabilities.illustrate.core.config import load as load_config
29
+ from mednotes.domains.wiki.capabilities.illustrate.core.download import DownloadError
30
+ from mednotes.domains.wiki.capabilities.illustrate.core.download import download as download_image
31
+ from mednotes.domains.wiki.capabilities.illustrate.sources import ImageCandidate, web_search, wikimedia
32
+ from mednotes.kernel.base import JsonObject, JsonObjectAdapter, JsonValue
33
+
34
+
35
+ class _ImageSearchSource(Protocol):
36
+ """Minimal adapter contract for image candidate sources exposed by this CLI."""
37
+
38
+ def search(self, query: str, visual_type: str, *, top_k: int = 4) -> list[ImageCandidate]: ...
39
+
40
+
41
+ _SOURCE_REGISTRY: dict[str, _ImageSearchSource] = {
42
+ wikimedia.NAME: wikimedia,
43
+ web_search.NAME: web_search,
44
+ }
45
+
46
+
47
+ def _emit(obj: object) -> None:
48
+ """Serializa ``obj`` como JSON na stdout. ``ensure_ascii=False`` pra
49
+ preservar acentos e emojis nas mensagens."""
50
+ json.dump(obj, sys.stdout, ensure_ascii=False, indent=2, default=_json_default)
51
+ sys.stdout.write("\n")
52
+
53
+
54
+ def _json_default(o: object) -> JsonValue:
55
+ # Datetimes (frontmatter) e dataclasses (ImageCandidate).
56
+ if hasattr(o, "isoformat"):
57
+ return str(o.isoformat()) # type: ignore[attr-defined]
58
+ if is_dataclass(o) and not isinstance(o, type):
59
+ return JsonObjectAdapter.validate_python(asdict(o))
60
+ raise TypeError(f"sem serializador pra {type(o).__name__}")
61
+
62
+
63
+ def _guard_note_write(note: Path, *, command: str) -> int | None:
64
+ from mednotes.platform.vault_guard import VaultGuardError, require_vault_guard
65
+ try:
66
+ require_vault_guard(note, workflow="/mednotes:enrich", command=command)
67
+ except VaultGuardError as exc:
68
+ _emit(exc.to_payload())
69
+ return int(exc.exit_code)
70
+ return None
71
+
72
+
73
+ # --- subcomandos -----------------------------------------------------
74
+
75
+
76
+ def cmd_sections(args: argparse.Namespace) -> int:
77
+ text = args.note.read_text(encoding="utf-8")
78
+ _emit(insert.parse_sections(text))
79
+ return 0
80
+
81
+
82
+ def cmd_search(args: argparse.Namespace) -> int:
83
+ source = _SOURCE_REGISTRY.get(args.source)
84
+ if source is None:
85
+ print(
86
+ f"erro: source desconhecida: {args.source!r}. "
87
+ f"disponíveis: {', '.join(sorted(_SOURCE_REGISTRY))}",
88
+ file=sys.stderr,
89
+ )
90
+ return 2
91
+ candidates = source.search(args.query, args.visual_type, top_k=args.top_k)
92
+ _emit([_candidate_to_dict(c) for c in candidates])
93
+ return 0
94
+
95
+
96
+ def cmd_download(args: argparse.Namespace) -> int:
97
+ cfg = load_config(args.config)
98
+ vault = _resolve_vault(args, cfg)
99
+ if vault is None:
100
+ print(
101
+ "erro: vault_dir não definido. Passe --vault ou configure "
102
+ "[vault].path em config.toml ou [paths].wiki_dir em "
103
+ f"{wiki_memory_path()}.",
104
+ file=sys.stderr,
105
+ )
106
+ return 4
107
+ cache_path = expand_path(cfg["cache"]["path"])
108
+ try:
109
+ with Cache(cache_path) as cache:
110
+ out = download_image(
111
+ args.url,
112
+ vault_dir=vault,
113
+ cache=cache,
114
+ max_dim=args.max_dim or cfg["enrichment"]["max_image_dimension"],
115
+ webp_min_savings_pct=cfg["enrichment"]["webp_min_savings_pct"],
116
+ source=args.source,
117
+ source_url=args.source_url,
118
+ user_agent=cfg["download"]["user_agent"],
119
+ )
120
+ except DownloadError as e:
121
+ print(f"erro: {e}", file=sys.stderr)
122
+ return 5
123
+ _emit(out)
124
+ return 0
125
+
126
+
127
+ def _resolve_vault(args: argparse.Namespace, cfg: dict) -> Path | None:
128
+ if args.vault:
129
+ return args.vault
130
+ base = cfg["vault"].get("path") or ""
131
+ if not base:
132
+ wiki_root = resolve_wiki_root(args.config)
133
+ if wiki_root:
134
+ return wiki_root / cfg["vault"].get("attachments_subdir", "")
135
+ return None
136
+ return expand_path(base) / cfg["vault"].get("attachments_subdir", "")
137
+
138
+
139
+ def cmd_insert(args: argparse.Namespace) -> int:
140
+ blocked = _guard_note_write(args.note, command="enricher insert")
141
+ if blocked is not None:
142
+ return blocked
143
+ text = args.note.read_text(encoding="utf-8")
144
+ item = insert.InsertedImage(
145
+ anchor_id=args.anchor_id or "manual",
146
+ section_path=args.section,
147
+ image_filename=args.image,
148
+ concept=args.concept,
149
+ source=args.source,
150
+ source_url=args.source_url,
151
+ )
152
+ try:
153
+ new_text = insert.insert_images(text, [item])
154
+ except insert.SectionNotFound as e:
155
+ print(f"erro: {e}", file=sys.stderr)
156
+ return 3
157
+ args.note.write_text(new_text, encoding="utf-8")
158
+ meta, _ = frontmatter.read(new_text)
159
+ _emit(
160
+ {
161
+ "note": str(args.note),
162
+ "inserted": 1,
163
+ "image_count": meta.get("image_count"),
164
+ "image_sources": meta.get("image_sources"),
165
+ "images_enriched_at": meta.get("images_enriched_at"),
166
+ }
167
+ )
168
+ return 0
169
+
170
+
171
+ def _candidate_to_dict(c: ImageCandidate) -> JsonObject:
172
+ return JsonObjectAdapter.validate_python(asdict(c))
173
+
174
+
175
+ # --- parser ---------------------------------------------------------
176
+
177
+
178
+ def _build_parser() -> argparse.ArgumentParser:
179
+ p = argparse.ArgumentParser(
180
+ prog="enricher",
181
+ description=(
182
+ "Toolbox de primitivas para enriquecer notas médicas com imagens. "
183
+ "Cada subcomando devolve JSON na stdout."
184
+ ),
185
+ )
186
+ p.add_argument(
187
+ "--config",
188
+ type=Path,
189
+ default=None,
190
+ help=(
191
+ "config.toml (default: busca na árvore acima do CWD e depois em "
192
+ "~/.mednotes/config.toml)"
193
+ ),
194
+ )
195
+
196
+ sub = p.add_subparsers(dest="cmd", required=True)
197
+
198
+ s_sections = sub.add_parser("sections", help="Lista headings da nota.")
199
+ s_sections.add_argument("note", type=Path, help="Caminho da nota .md")
200
+ s_sections.set_defaults(func=cmd_sections)
201
+
202
+ s_search = sub.add_parser("search", help="Busca candidatas em uma source.")
203
+ s_search.add_argument(
204
+ "source",
205
+ choices=sorted(_SOURCE_REGISTRY),
206
+ help="Nome do adapter de fonte.",
207
+ )
208
+ s_search.add_argument("--query", required=True, help="Termo de busca.")
209
+ s_search.add_argument(
210
+ "--visual-type",
211
+ default="diagram",
212
+ help="Tipo visual desejado (diagram, histology, radiology...). "
213
+ "Aceito por uniformidade entre adapters.",
214
+ )
215
+ s_search.add_argument("--top-k", type=int, default=4, help="Máximo de candidatas.")
216
+ s_search.set_defaults(func=cmd_search)
217
+
218
+ s_download = sub.add_parser(
219
+ "download",
220
+ help="Baixa imagem, valida, redimensiona, dedupe SHA, indexa no cache.",
221
+ )
222
+ s_download.add_argument("url", help="URL direta da imagem.")
223
+ s_download.add_argument(
224
+ "--vault",
225
+ type=Path,
226
+ default=None,
227
+ help=(
228
+ "Diretório destino. Default: <[vault].path>/<[vault].attachments_subdir>; "
229
+ "fallback: <config.toml [paths].wiki_dir>/<[vault].attachments_subdir>."
230
+ ),
231
+ )
232
+ s_download.add_argument(
233
+ "--max-dim",
234
+ type=int,
235
+ default=None,
236
+ help="Maior lado em px. Default: [enrichment].max_image_dimension.",
237
+ )
238
+ s_download.add_argument(
239
+ "--source",
240
+ default="unknown",
241
+ help="Identificador da fonte (vai para o cache).",
242
+ )
243
+ s_download.add_argument(
244
+ "--source-url",
245
+ default=None,
246
+ help="URL canônica da página descritiva (rastreabilidade).",
247
+ )
248
+ s_download.set_defaults(func=cmd_download)
249
+
250
+ s_insert = sub.add_parser(
251
+ "insert",
252
+ help="Insere bloco de imagem em uma seção da nota e atualiza frontmatter.",
253
+ )
254
+ s_insert.add_argument("note", type=Path, help="Caminho da nota .md (modificada in-place).")
255
+ s_insert.add_argument(
256
+ "--section",
257
+ action="append",
258
+ required=True,
259
+ help="Heading-trail. Repetível: --section H1 --section H2 (do topo pra folha).",
260
+ )
261
+ s_insert.add_argument("--image", required=True, help="Filename do attachment já baixado.")
262
+ s_insert.add_argument("--concept", required=True, help="Conceito da figura (vai no caption).")
263
+ s_insert.add_argument("--source", required=True, help="Identificador da fonte (ex: wikimedia).")
264
+ s_insert.add_argument("--source-url", required=True, help="URL canônica da fonte.")
265
+ s_insert.add_argument("--anchor-id", default=None, help="Opcional, default 'manual'.")
266
+ s_insert.set_defaults(func=cmd_insert)
267
+
268
+ return p
269
+
270
+
271
+ def main(argv: list[str] | None = None) -> int:
272
+ parser = _build_parser()
273
+ args = parser.parse_args(argv)
274
+ func: Callable[[argparse.Namespace], int] = args.func
275
+ return func(args)
@@ -0,0 +1,2 @@
1
+ """Image enrichment workflow internals."""
2
+ from __future__ import annotations