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,190 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+ from enum import StrEnum
5
+ from typing import Literal
6
+
7
+ from pydantic import ConfigDict, Field, model_validator
8
+
9
+ from mednotes.kernel.base import ContractModel, JsonObject
10
+
11
+
12
+ class HappyPathViolationCategory(StrEnum):
13
+ ROUTE = "route"
14
+ TOOL_CONTRACT = "tool_contract"
15
+ FSM_CONTRACT = "fsm_contract"
16
+ ARTIFACT_INTEGRITY = "artifact_integrity"
17
+ PUBLIC_COMMUNICATION = "public_communication"
18
+ PERFORMANCE = "performance"
19
+ PRIMARY_OBJECTIVE = "primary_objective"
20
+ HUMAN_ATTENTION = "human_attention"
21
+
22
+
23
+ class HappyPathViolation(ContractModel):
24
+ code: str = Field(min_length=1)
25
+ category: HappyPathViolationCategory
26
+ severity: Literal["low", "medium", "high", "critical"]
27
+ message: str = Field(min_length=1)
28
+ evidence: JsonObject = Field(default_factory=dict)
29
+
30
+
31
+ class HappyPathFindingInput(ContractModel):
32
+ # Agent report findings carry richer audit fields; happy-path scoring only
33
+ # consumes the stable violation surface below.
34
+ model_config = ConfigDict(extra="ignore", populate_by_name=True, validate_assignment=True)
35
+
36
+ code: str = Field(min_length=1)
37
+ severity: Literal["low", "medium", "high", "critical"]
38
+ message: str = Field(min_length=1)
39
+ evidence: JsonObject = Field(default_factory=dict)
40
+
41
+
42
+ class HappyPathRunMetrics(ContractModel):
43
+ schema_: Literal["medical-notes-workbench.happy-path-run-metrics.v1"] = Field(
44
+ "medical-notes-workbench.happy-path-run-metrics.v1",
45
+ alias="schema",
46
+ )
47
+ workflow: str = Field(min_length=1)
48
+ run_id: str = Field(min_length=1)
49
+ status: Literal["happy", "degraded", "failed"]
50
+ score: int = Field(ge=0, le=100)
51
+ primary_objective_completed: bool
52
+ legitimate_stop_reason: str = ""
53
+ violation_count: int = Field(ge=0)
54
+ violations: list[HappyPathViolation] = Field(default_factory=list)
55
+
56
+ @model_validator(mode="after")
57
+ def _count_and_score_match(self) -> HappyPathRunMetrics:
58
+ if self.violation_count != len(self.violations):
59
+ raise ValueError("violation_count must match violations length")
60
+ if self.violation_count == 0 and self.score != 100:
61
+ raise ValueError("perfect happy path must score 100")
62
+ if self.status == "happy" and self.violation_count != 0:
63
+ raise ValueError("happy status cannot include violations")
64
+ return self
65
+
66
+
67
+ class HappyPathRoundMetrics(ContractModel):
68
+ schema_: Literal["medical-notes-workbench.happy-path-round-metrics.v1"] = Field(
69
+ "medical-notes-workbench.happy-path-round-metrics.v1",
70
+ alias="schema",
71
+ )
72
+ workflow: str = Field(min_length=1)
73
+ run_count: int = Field(ge=0)
74
+ happy_run_count: int = Field(ge=0)
75
+ happy_path_prevalence_percent: int = Field(ge=0, le=100)
76
+ target_prevalence_percent: int = 100
77
+ runs: list[HappyPathRunMetrics] = Field(default_factory=list)
78
+
79
+ @model_validator(mode="after")
80
+ def _round_counts_match_runs(self) -> HappyPathRoundMetrics:
81
+ if self.run_count != len(self.runs):
82
+ raise ValueError("run_count must match runs length")
83
+ if self.happy_run_count != sum(1 for run in self.runs if run.status == "happy"):
84
+ raise ValueError("happy_run_count must match happy runs")
85
+ expected = 100 if self.run_count == 0 else int((self.happy_run_count / self.run_count) * 100)
86
+ if self.happy_path_prevalence_percent != expected:
87
+ raise ValueError("happy_path_prevalence_percent must match run counts")
88
+ return self
89
+
90
+
91
+ _FINDING_CATEGORY_BY_CODE: dict[str, HappyPathViolationCategory] = {
92
+ "agent.public_output_internal_term_leak": HappyPathViolationCategory.PUBLIC_COMMUNICATION,
93
+ "agent.final_report_success_claim_mismatch": HappyPathViolationCategory.PUBLIC_COMMUNICATION,
94
+ "agent.final_report_progress_status_mismatch": HappyPathViolationCategory.FSM_CONTRACT,
95
+ "agent.final_report_receipt_status_mismatch": HappyPathViolationCategory.FSM_CONTRACT,
96
+ "agent.workflow_contract_contradiction": HappyPathViolationCategory.FSM_CONTRACT,
97
+ "agent.final_report_tool_deviation_omitted": HappyPathViolationCategory.ROUTE,
98
+ "agent.runtime_route_probe_observed": HappyPathViolationCategory.ROUTE,
99
+ "agent.waiting_external_continuation_attempted": HappyPathViolationCategory.ROUTE,
100
+ "agent.ready_continuation_stopped": HappyPathViolationCategory.ROUTE,
101
+ "agent.runtime_performance_bug": HappyPathViolationCategory.PERFORMANCE,
102
+ "agent.final_report_primary_objective_omitted": HappyPathViolationCategory.PRIMARY_OBJECTIVE,
103
+ "agent.process_chats_primary_objective_unresolved": HappyPathViolationCategory.PRIMARY_OBJECTIVE,
104
+ "agent.process_chats_vault_deletion_without_receipt": HappyPathViolationCategory.ARTIFACT_INTEGRITY,
105
+ "agent.specialist_model_policy_violation": HappyPathViolationCategory.ROUTE,
106
+ }
107
+
108
+
109
+ def happy_path_metrics_from_findings(
110
+ *,
111
+ workflow: str,
112
+ run_id: str,
113
+ findings: Sequence[object],
114
+ primary_objective_completed: bool,
115
+ legitimate_stop_reason: str,
116
+ ) -> HappyPathRunMetrics:
117
+ violations = [_violation_from_finding(finding) for finding in findings]
118
+ if not primary_objective_completed and not legitimate_stop_reason:
119
+ violations.append(
120
+ HappyPathViolation(
121
+ code="workflow.primary_objective_not_completed",
122
+ category=HappyPathViolationCategory.PRIMARY_OBJECTIVE,
123
+ severity="high",
124
+ message="Workflow did not complete its declared primary objective.",
125
+ )
126
+ )
127
+ score = max(0, 100 - sum(_severity_penalty(item.severity) for item in violations))
128
+ status: Literal["happy", "degraded", "failed"]
129
+ if not violations:
130
+ status = "happy"
131
+ elif any(item.severity in {"high", "critical"} for item in violations):
132
+ status = "failed"
133
+ else:
134
+ status = "degraded"
135
+ return HappyPathRunMetrics(
136
+ workflow=workflow,
137
+ run_id=run_id,
138
+ status=status,
139
+ score=score,
140
+ primary_objective_completed=primary_objective_completed,
141
+ legitimate_stop_reason=legitimate_stop_reason,
142
+ violation_count=len(violations),
143
+ violations=violations,
144
+ )
145
+
146
+
147
+ def happy_path_round_metrics(*, workflow: str, runs: list[HappyPathRunMetrics]) -> HappyPathRoundMetrics:
148
+ happy_run_count = sum(1 for run in runs if run.status == "happy")
149
+ run_count = len(runs)
150
+ prevalence = 100 if run_count == 0 else int((happy_run_count / run_count) * 100)
151
+ return HappyPathRoundMetrics(
152
+ workflow=workflow,
153
+ run_count=run_count,
154
+ happy_run_count=happy_run_count,
155
+ happy_path_prevalence_percent=prevalence,
156
+ runs=runs,
157
+ )
158
+
159
+
160
+ def _violation_from_finding(finding: object) -> HappyPathViolation:
161
+ typed = _finding_input(finding)
162
+ return HappyPathViolation(
163
+ code=typed.code,
164
+ category=_FINDING_CATEGORY_BY_CODE.get(typed.code, HappyPathViolationCategory.ROUTE),
165
+ severity=typed.severity,
166
+ message=typed.message,
167
+ evidence=typed.evidence,
168
+ )
169
+
170
+
171
+ def _finding_input(finding: object) -> HappyPathFindingInput:
172
+ if isinstance(finding, HappyPathFindingInput):
173
+ return finding
174
+ if isinstance(finding, ContractModel):
175
+ return HappyPathFindingInput.model_validate(finding.to_payload())
176
+ return HappyPathFindingInput.model_validate(finding)
177
+
178
+
179
+ def _severity_penalty(severity: str) -> int:
180
+ match severity:
181
+ case "critical":
182
+ return 100
183
+ case "high":
184
+ return 50
185
+ case "medium":
186
+ return 25
187
+ case "low":
188
+ return 10
189
+ case _:
190
+ return 25
@@ -0,0 +1,110 @@
1
+ """Typed Git/linker state contracts.
2
+
3
+ Git status, worktree diffs and persisted link-state JSON are external adapter
4
+ inputs. These models are the first trusted shape before link diagnosis decides
5
+ trigger context, stale diagnosis or redundant blocked reruns.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Literal
11
+
12
+ from pydantic import Field
13
+
14
+ from mednotes.kernel.base import ContractModel, JsonObject, JsonObjectAdapter
15
+
16
+ LINK_GIT_CONTEXT_SCHEMA = "medical-notes-workbench.link-git-context.v1"
17
+ LINK_STATE_SCHEMA = "medical-notes-workbench.link-state.v1"
18
+ LINK_STATE_SCHEMA_V2 = "medical-notes-workbench.link-state.v2"
19
+ GIT_TRIGGER_SOURCE = "/mednotes:link.git"
20
+
21
+ GitChangeType = Literal["created", "modified", "deleted", "renamed", "moved"]
22
+
23
+
24
+ def _without_empty_strings(payload: JsonObject) -> JsonObject:
25
+ """Keep public Git payloads compact while the internal model stays explicit."""
26
+
27
+ return JsonObjectAdapter.validate_python(
28
+ {key: value for key, value in payload.items() if value not in ("", None, [])}
29
+ )
30
+
31
+
32
+ class LinkGitChangeEvent(ContractModel):
33
+ change_type: GitChangeType
34
+ content_change: str = ""
35
+ path: str = ""
36
+ old_path: str = ""
37
+ title: str = ""
38
+ old_title: str = ""
39
+ replacement_path: str = ""
40
+ replacement_title: str = ""
41
+ before_hash: str = ""
42
+ after_hash: str = ""
43
+
44
+ def to_payload(self) -> JsonObject:
45
+ return _without_empty_strings(super().to_payload())
46
+
47
+
48
+ class LinkGitChangedPath(ContractModel):
49
+ change_type: GitChangeType
50
+ path: str = ""
51
+ old_path: str = ""
52
+
53
+ def to_payload(self) -> JsonObject:
54
+ return _without_empty_strings(super().to_payload())
55
+
56
+
57
+ class LinkGitContext(ContractModel):
58
+ schema_id: Literal["medical-notes-workbench.link-git-context.v1"] = Field(alias="schema")
59
+ available: bool = Field(strict=True)
60
+ repo_root: str
61
+ branch: str
62
+ head: str
63
+ previous_link_head: str
64
+ dirty: bool = Field(strict=True)
65
+ changed_note_count: int = Field(ge=0, strict=True)
66
+ changed_notes: list[LinkGitChangeEvent] = Field(default_factory=list)
67
+ changed_paths: list[LinkGitChangedPath] = Field(default_factory=list)
68
+ trigger_context_available: bool = Field(strict=True)
69
+ status_hash: str
70
+ unavailable_reason: str = ""
71
+
72
+ def to_payload(self) -> JsonObject:
73
+ payload = super().to_payload()
74
+ if not self.unavailable_reason:
75
+ payload.pop("unavailable_reason", None)
76
+ payload["changed_notes"] = [event.to_payload() for event in self.changed_notes]
77
+ payload["changed_paths"] = [event.to_payload() for event in self.changed_paths]
78
+ return JsonObjectAdapter.validate_python(payload)
79
+
80
+
81
+ class LinkTriggerContextFromGit(ContractModel):
82
+ schema_id: Literal["medical-notes-workbench.link-trigger-context.v1"] = Field(alias="schema")
83
+ source_workflow: Literal["/mednotes:link.git"]
84
+ changed_notes: list[LinkGitChangeEvent]
85
+ catalog_changed: bool = Field(default=False, strict=True)
86
+ related_notes_export_changed: bool = Field(default=False, strict=True)
87
+
88
+ def to_payload(self) -> JsonObject:
89
+ payload = super().to_payload()
90
+ payload["changed_notes"] = [event.to_payload() for event in self.changed_notes]
91
+ return JsonObjectAdapter.validate_python(payload)
92
+
93
+
94
+ class LinkState(ContractModel):
95
+ schema_id: Literal[
96
+ "medical-notes-workbench.link-state.v1",
97
+ "medical-notes-workbench.link-state.v2",
98
+ ] = Field(alias="schema")
99
+ generated_at: str = ""
100
+ snapshot_hash: str = ""
101
+ git_head: str = ""
102
+ git_status_hash: str = ""
103
+ receipt_path: str = ""
104
+ last_diagnosis_attempt: JsonObject | None = None
105
+
106
+ def to_payload(self) -> JsonObject:
107
+ payload = super().to_payload()
108
+ if self.last_diagnosis_attempt is None:
109
+ payload.pop("last_diagnosis_attempt", None)
110
+ return JsonObjectAdapter.validate_python(payload)
@@ -0,0 +1,52 @@
1
+ """Canonical `/mednotes:link` artifact boundary for parent workflows.
2
+
3
+ Lower-level link adapters may still translate raw runtime output, but a parent
4
+ workflow can only consume the public child FSM payload. Accepting `link-run.v1`
5
+ here would recreate a second source of workflow state.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Literal
11
+
12
+ from pydantic import ConfigDict, Field
13
+
14
+ from mednotes.kernel.base import ContractModel, JsonObject
15
+ from mednotes.kernel.progress import WorkflowProgressViewModel
16
+
17
+
18
+ class LinkFsmArtifact(ContractModel):
19
+ # Parent workflows consume a stable subset of the child FSM payload. The
20
+ # schema discriminator rejects legacy artifacts; extra fields are ignored so
21
+ # public child-FSM additions do not become a second parent-state contract.
22
+ model_config = ConfigDict(extra="ignore", strict=True)
23
+
24
+ schema_id: Literal["medical-notes-workbench.link-fsm-result.v1"] = Field(alias="schema")
25
+ workflow: Literal["/mednotes:link", "/mednotes:link-body"]
26
+ run_id: str = Field(min_length=1)
27
+ state_machine_snapshot: JsonObject
28
+ progress_view_model: WorkflowProgressViewModel
29
+ decision: JsonObject | None = None
30
+ human_decision_packet: JsonObject | None = None
31
+ receipt: JsonObject
32
+ reports: JsonObject
33
+ agent_directive: JsonObject
34
+ artifacts: JsonObject
35
+ version_control_safety: JsonObject
36
+ diagnostic_context: JsonObject | None = None
37
+ error_context: JsonObject
38
+
39
+ @property
40
+ def operation_status(self) -> str:
41
+ return self.progress_view_model.status.value
42
+
43
+
44
+ def normalize_link_runtime_artifact(payload: object) -> LinkFsmArtifact:
45
+ """Validate the child FSM payload before its status can affect the parent."""
46
+
47
+ if not isinstance(payload, dict):
48
+ raise ValueError("link artifact must be an object")
49
+ schema = payload["schema"] if "schema" in payload else ""
50
+ if schema != "medical-notes-workbench.link-fsm-result.v1":
51
+ raise ValueError("link artifact must be medical-notes-workbench.link-fsm-result.v1")
52
+ return LinkFsmArtifact.model_validate(payload)
@@ -0,0 +1,75 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal
4
+
5
+ from pydantic import Field, model_validator
6
+
7
+ from mednotes.domains.wiki.contracts.raw_coverage import (
8
+ CoverageAction as CoverageAction,
9
+ )
10
+ from mednotes.domains.wiki.contracts.raw_coverage import (
11
+ RawCoverage as RawCoverage,
12
+ )
13
+ from mednotes.domains.wiki.contracts.raw_coverage import (
14
+ RawCoverageItem as RawCoverageItem,
15
+ )
16
+ from mednotes.kernel.base import ContractModel
17
+
18
+ PlanAction = Literal["planned_meaning", "attach_to_planned_meaning", "not_a_note", "needs_context"]
19
+
20
+
21
+ class MeaningClaim(ContractModel):
22
+ label: str = Field(min_length=1)
23
+ scope: str = Field(min_length=1)
24
+ boundaries: list[str]
25
+ kind: Literal[
26
+ "clinical_concept",
27
+ "drug_concept",
28
+ "diagnostic_criterion",
29
+ "management_strategy",
30
+ "procedure",
31
+ "physiology_or_mechanism",
32
+ "epidemiology_or_definition",
33
+ ]
34
+ evidence_summary: str = Field(min_length=1)
35
+ id: str | None = None
36
+
37
+
38
+ class TriageNotePlanItem(ContractModel):
39
+ id: str = Field(min_length=1)
40
+ action: PlanAction
41
+ title: str | None = None
42
+ staged_title: str | None = None
43
+ meaning_claim: MeaningClaim | None = None
44
+ taxonomy_hint: str | None = None
45
+ aliases: list[str] | None = None
46
+ target_item_id: str | None = None
47
+ reason_code: str | None = None
48
+ reason: str | None = None
49
+
50
+ @model_validator(mode="after")
51
+ def validate_action_payload(self) -> TriageNotePlanItem:
52
+ if self.action == "planned_meaning":
53
+ if not self.title or not self.staged_title or self.meaning_claim is None:
54
+ raise ValueError("planned_meaning items require title, staged_title and meaning_claim")
55
+ elif self.action == "attach_to_planned_meaning":
56
+ if not self.target_item_id or not self.reason_code or not self.reason:
57
+ raise ValueError("attach_to_planned_meaning items require target_item_id, reason_code and reason")
58
+ elif self.action in {"not_a_note", "needs_context"} and (not self.reason_code or not self.reason):
59
+ raise ValueError(f"{self.action} items require reason_code and reason")
60
+ return self
61
+
62
+
63
+ class TriageNotePlan(ContractModel):
64
+ schema_id: Literal["medical-notes-workbench.triage-note-plan.v2"] = Field(alias="schema")
65
+ raw_file: str = Field(min_length=1)
66
+ exhaustive: Literal[True] = True
67
+ items: list[TriageNotePlanItem] = Field(min_length=1)
68
+ batch_id: str | None = None
69
+ run_id: str | None = None
70
+ source_artifact_hash: str | None = None
71
+
72
+
73
+ # RawCoverage lives in raw_coverage.py because it is a publish boundary, not a
74
+ # triage-plan concept. These imports remain as a stable public import path for
75
+ # existing schema/test code while the implementation has a single source.
@@ -0,0 +1,114 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Literal
5
+
6
+ from pydantic import Field, field_validator, model_validator
7
+
8
+ from mednotes.kernel.base import ContractModel, JsonObject
9
+
10
+ PathBlockerReason = Literal[
11
+ "paths.wiki_dir_missing",
12
+ "paths.raw_dir_missing",
13
+ "paths.ambiguous",
14
+ "paths.config_invalid",
15
+ ]
16
+ PathRequiredInput = Literal["wiki_dir", "raw_dir", "config_path"]
17
+ _WIKI_REASON_MAP: dict[str, PathBlockerReason] = {
18
+ "missing_wiki_dir": "paths.wiki_dir_missing",
19
+ "ambiguous_wiki_dir": "paths.ambiguous",
20
+ "invalid_wiki_dir": "paths.config_invalid",
21
+ }
22
+ _DEFAULT_REASON: PathBlockerReason = "paths.config_invalid"
23
+
24
+
25
+ class WorkbenchPathsConfig(ContractModel):
26
+ wiki_dir: Path
27
+ raw_dir: Path | None = None
28
+
29
+ @field_validator("wiki_dir")
30
+ @classmethod
31
+ def wiki_dir_must_exist(cls, value: Path) -> Path:
32
+ if not value.exists() or not value.is_dir():
33
+ raise ValueError("wiki_dir must be an existing directory")
34
+ return value
35
+
36
+ @field_validator("raw_dir")
37
+ @classmethod
38
+ def raw_dir_must_exist_when_present(cls, value: Path | None) -> Path | None:
39
+ if value is not None and (not value.exists() or not value.is_dir()):
40
+ raise ValueError("raw_dir must be an existing directory when provided")
41
+ return value
42
+
43
+
44
+ class PathResolutionBlocker(ContractModel):
45
+ blocked_reason: PathBlockerReason
46
+ legacy_blocked_reason: str = ""
47
+ next_action: str = Field(min_length=1)
48
+ required_inputs: list[PathRequiredInput] = Field(default_factory=list)
49
+ human_decision_required: bool = False
50
+ human_decision_packet: JsonObject | None = None
51
+
52
+ @model_validator(mode="after")
53
+ def human_decision_requires_packet(self) -> PathResolutionBlocker:
54
+ if self.human_decision_required and self.human_decision_packet is None:
55
+ raise ValueError("human_decision_required requires human_decision_packet")
56
+ return self
57
+
58
+
59
+ class PathResolutionResult(ContractModel):
60
+ status: Literal["ready", "blocked"]
61
+ paths: WorkbenchPathsConfig | None = None
62
+ blocker: PathResolutionBlocker | None = None
63
+
64
+ @model_validator(mode="after")
65
+ def status_matches_payload(self) -> PathResolutionResult:
66
+ if self.status == "ready" and self.blocker is not None:
67
+ raise ValueError("ready path resolution cannot include blocker")
68
+ if self.status == "ready" and self.paths is None:
69
+ raise ValueError("ready path resolution requires paths")
70
+ if self.status == "blocked" and self.blocker is None:
71
+ raise ValueError("blocked path resolution requires blocker")
72
+ return self
73
+
74
+
75
+ class WikiPathResolutionPayload(ContractModel):
76
+ schema_: Literal["medical-notes-workbench.path-resolution.v1"] = Field(
77
+ "medical-notes-workbench.path-resolution.v1",
78
+ alias="schema",
79
+ )
80
+ phase: str = Field(min_length=1)
81
+ status: Literal["completed", "blocked"]
82
+ blocked_reason: str = ""
83
+ legacy_blocked_reason: str = ""
84
+ next_action: str = ""
85
+ required_inputs: list[str] = Field(default_factory=list)
86
+ wiki_dir: str = ""
87
+ wiki_source: str = ""
88
+ wiki_dir_source: str = ""
89
+ memory_path: str = ""
90
+ config_path: str = ""
91
+ candidates: list[JsonObject] = Field(default_factory=list)
92
+ compat_warnings: list[str] = Field(default_factory=list)
93
+ human_decision_required: bool = False
94
+ human_decision_packet: JsonObject | None = None
95
+ human_decision_packets: list[JsonObject] = Field(default_factory=list)
96
+
97
+
98
+ def blocker_from_wiki_resolution(payload: dict[str, object]) -> PathResolutionBlocker:
99
+ resolution = WikiPathResolutionPayload.model_validate(payload)
100
+ raw_reason = resolution.blocked_reason
101
+ mapped_reason = _WIKI_REASON_MAP.get(raw_reason, _DEFAULT_REASON)
102
+ required_inputs: list[PathRequiredInput] = (
103
+ ["wiki_dir"]
104
+ if mapped_reason in {"paths.wiki_dir_missing", "paths.ambiguous", "paths.config_invalid"}
105
+ else []
106
+ )
107
+ return PathResolutionBlocker(
108
+ blocked_reason=mapped_reason,
109
+ legacy_blocked_reason=raw_reason,
110
+ next_action=resolution.next_action or "Configurar wiki_dir valido em /mednotes:setup.",
111
+ required_inputs=required_inputs,
112
+ human_decision_required=resolution.human_decision_required,
113
+ human_decision_packet=resolution.human_decision_packet,
114
+ )
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import Literal
5
+
6
+ from pydantic import Field, model_validator
7
+
8
+ from mednotes.kernel.base import ContractModel
9
+
10
+ WorkflowPublicObjectiveAnswer = Literal[
11
+ "yes",
12
+ "partial",
13
+ "no",
14
+ "waiting_agent",
15
+ "waiting_external",
16
+ "waiting_human",
17
+ "failed",
18
+ ]
19
+ WorkflowPublicMutationState = Literal["changed", "unchanged", "not_applicable"]
20
+
21
+ POSITIVE_FILE_COUNT_RE = re.compile(r"\b[1-9]\d*\s+arquivo")
22
+ ZERO_FILE_COUNT_RE = re.compile(r"\b0\s+arquivo")
23
+
24
+
25
+ class WorkflowPublicReportViewModel(ContractModel):
26
+ schema_: Literal["medical-notes-workbench.workflow-public-report-view-model.v1"] = Field(
27
+ "medical-notes-workbench.workflow-public-report-view-model.v1",
28
+ alias="schema",
29
+ )
30
+ workflow: str = Field(min_length=1)
31
+ run_id: str = Field(min_length=1)
32
+ objective_answer: WorkflowPublicObjectiveAnswer
33
+ headline: str = Field(min_length=1)
34
+ mutation_state: WorkflowPublicMutationState
35
+ mutation_summary: str = Field(min_length=1)
36
+ remaining_work_summary: str = Field(min_length=1)
37
+ next_step_summary: str = Field(min_length=1)
38
+ user_attention_required: bool
39
+ human_reason: str = ""
40
+ internal_terms_present: bool = False
41
+
42
+ @model_validator(mode="after")
43
+ def _public_report_is_coherent(self) -> WorkflowPublicReportViewModel:
44
+ folded_mutation = self.mutation_summary.casefold()
45
+ has_positive_file_count = POSITIVE_FILE_COUNT_RE.search(folded_mutation) is not None
46
+ has_only_zero_file_count = ZERO_FILE_COUNT_RE.search(folded_mutation) is not None and not has_positive_file_count
47
+ if self.mutation_state == "changed" and ("nada foi alterado" in folded_mutation or has_only_zero_file_count):
48
+ raise ValueError("public report mutation contradiction")
49
+ if self.user_attention_required and not self.human_reason.strip():
50
+ raise ValueError("public report user attention requires human reason")
51
+ if self.internal_terms_present:
52
+ raise ValueError("public report cannot expose internal terms")
53
+ return self