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,619 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import StrEnum
4
+ from typing import Literal, Self
5
+
6
+ from pydantic import Field, StrictStr, ValidationInfo, field_validator, model_validator
7
+ from pydantic import ValidationError as PydanticValidationError
8
+
9
+ from mednotes.kernel.base import ContractModel, JsonObject, JsonObjectAdapter, JsonValue
10
+
11
+ # Framework schema id. Product-specific registries may export this contract
12
+ # under their own target ids, but the kernel must stay domain-neutral.
13
+ HUMAN_DECISION_PACKET_SCHEMA = "workflow.human-decision-packet.v1"
14
+
15
+
16
+ class WorkflowDecisionKind(StrEnum):
17
+ AUTO_FIX = "auto_fix"
18
+ AUTO_DEFER = "auto_defer"
19
+ AUTO_PLAN = "auto_plan"
20
+ ASK_HUMAN = "ask_human"
21
+ HARD_BLOCK = "hard_block"
22
+ FAILED = "failed"
23
+
24
+ @classmethod
25
+ def coerce(cls, value: str | WorkflowDecisionKind) -> WorkflowDecisionKind:
26
+ try:
27
+ return cls(str(value))
28
+ except ValueError as exc:
29
+ raise WorkflowOutcomeError(f"unknown workflow decision kind: {value}") from exc
30
+
31
+
32
+ class WorkflowAutomationKind(StrEnum):
33
+ AUTO_FIX = "auto_fix"
34
+ AUTO_DEFER = "auto_defer"
35
+ AUTO_PLAN = "auto_plan"
36
+
37
+ @classmethod
38
+ def coerce(cls, value: str | WorkflowAutomationKind) -> WorkflowAutomationKind:
39
+ try:
40
+ return cls(str(value))
41
+ except ValueError as exc:
42
+ raise WorkflowOutcomeError(f"unknown automation kind: {value}") from exc
43
+
44
+
45
+ DecisionKind = (
46
+ WorkflowDecisionKind | Literal["auto_fix", "auto_defer", "auto_plan", "ask_human", "hard_block", "failed"]
47
+ )
48
+ AutomationKind = WorkflowAutomationKind | Literal["auto_fix", "auto_defer", "auto_plan"]
49
+ ReceiptStatus = Literal[
50
+ "running",
51
+ "completed",
52
+ "completed_with_warnings",
53
+ "waiting_agent",
54
+ "waiting_external",
55
+ "waiting_human",
56
+ "blocked",
57
+ "failed",
58
+ ]
59
+
60
+
61
+ class WorkflowOutcomeError(ValueError):
62
+ """Raised when a workflow decision violates the outcome contract."""
63
+
64
+
65
+ EXECUTABLE_DIAGNOSTIC_CONTEXT_KEYS = frozenset(
66
+ {
67
+ "action_directives",
68
+ "agent_directive",
69
+ "continuation_plan",
70
+ "human_decision_packet",
71
+ "human_decision_required",
72
+ "next_action",
73
+ "next_command",
74
+ "pending_effects",
75
+ "required_inputs",
76
+ "resume_action",
77
+ "resume_after_resolution",
78
+ "resume_command",
79
+ }
80
+ )
81
+
82
+
83
+ EXECUTABLE_DIAGNOSTIC_CONTEXT_KEY_SUFFIXES = ("_next_action", "_command")
84
+ EXECUTABLE_DIAGNOSTIC_CONTEXT_KEY_PREFIXES = ("resume_", "continuation_")
85
+
86
+
87
+ def assert_diagnostic_context_evidence_only(diagnostic_context: object) -> None:
88
+ """Keep diagnostic_context as evidence, never as a second workflow API."""
89
+
90
+ offending_path = _executable_diagnostic_path(diagnostic_context, "diagnostic_context")
91
+ if offending_path:
92
+ raise ValueError(f"diagnostic_context must not carry executable routes: {offending_path}")
93
+
94
+
95
+ def diagnostic_context_evidence_only(diagnostic_context: object) -> JsonObject:
96
+ """Return diagnostic evidence with executable route fields recursively removed."""
97
+
98
+ cleaned = _remove_executable_diagnostic_routes(diagnostic_context)
99
+ if isinstance(cleaned, dict):
100
+ return JsonObjectAdapter.validate_python(cleaned)
101
+ return {}
102
+
103
+
104
+ def _executable_diagnostic_path(value: object, path: str) -> str:
105
+ if isinstance(value, dict):
106
+ for key, child in value.items():
107
+ key_text = str(key)
108
+ child_path = f"{path}.{key_text}"
109
+ if _is_executable_diagnostic_context_key(key_text):
110
+ return child_path
111
+ nested_path = _executable_diagnostic_path(child, child_path)
112
+ if nested_path:
113
+ return nested_path
114
+ elif isinstance(value, list):
115
+ for index, item in enumerate(value):
116
+ nested_path = _executable_diagnostic_path(item, f"{path}[{index}]")
117
+ if nested_path:
118
+ return nested_path
119
+ return ""
120
+
121
+
122
+ def _remove_executable_diagnostic_routes(value: object) -> object:
123
+ if isinstance(value, dict):
124
+ cleaned: dict[str, object] = {}
125
+ for key, child in value.items():
126
+ key_text = str(key)
127
+ if _is_executable_diagnostic_context_key(key_text):
128
+ continue
129
+ cleaned[key_text] = _remove_executable_diagnostic_routes(child)
130
+ return cleaned
131
+ if isinstance(value, list):
132
+ return [_remove_executable_diagnostic_routes(item) for item in value]
133
+ return value
134
+
135
+
136
+ def _is_executable_diagnostic_context_key(key: str) -> bool:
137
+ """Detect continuation aliases so diagnostics cannot grow a shadow API."""
138
+
139
+ normalized = key.strip().casefold()
140
+ return (
141
+ normalized in EXECUTABLE_DIAGNOSTIC_CONTEXT_KEYS
142
+ or "next_action" in normalized
143
+ or any(normalized.endswith(suffix) for suffix in EXECUTABLE_DIAGNOSTIC_CONTEXT_KEY_SUFFIXES)
144
+ or any(normalized.startswith(prefix) for prefix in EXECUTABLE_DIAGNOSTIC_CONTEXT_KEY_PREFIXES)
145
+ )
146
+
147
+
148
+ def _non_empty(value: str, field_name: str) -> str:
149
+ cleaned = value.strip()
150
+ if not cleaned:
151
+ raise ValueError(f"{field_name} must be non-empty")
152
+ return cleaned
153
+
154
+
155
+ class DecisionEvidence(ContractModel):
156
+ summary: str
157
+ technical_code: str
158
+ source: str
159
+ affected_items: list[str] = Field(default_factory=list)
160
+ candidates: list[JsonObject] = Field(default_factory=list)
161
+ confidence: str | float = ""
162
+ risk: str = ""
163
+
164
+ @field_validator("summary", "technical_code", "source")
165
+ @classmethod
166
+ def _required_text(cls, value: str, info: ValidationInfo) -> str:
167
+ return _non_empty(value, str(info.field_name))
168
+
169
+ def as_dict(self) -> JsonObject:
170
+ payload: JsonObject = {
171
+ "summary": self.summary,
172
+ "technical_code": self.technical_code,
173
+ "source": self.source,
174
+ }
175
+ if self.affected_items:
176
+ payload["affected_items"] = list(self.affected_items)
177
+ if self.candidates:
178
+ payload["candidates"] = [dict(item) for item in self.candidates]
179
+ if self.confidence != "":
180
+ payload["confidence"] = self.confidence
181
+ if self.risk:
182
+ payload["risk"] = self.risk
183
+ return _json_object(payload)
184
+
185
+
186
+ class RejectedAutomation(ContractModel):
187
+ kind: WorkflowAutomationKind
188
+ reason_code: str
189
+ reason: str
190
+ safe: bool = False
191
+ evidence_refs: list[str] = Field(default_factory=list)
192
+
193
+ def __init__(self, **data: object) -> None:
194
+ if "kind" in data:
195
+ data["kind"] = WorkflowAutomationKind.coerce(str(data["kind"]))
196
+ super().__init__(**data)
197
+
198
+ @field_validator("reason_code", "reason")
199
+ @classmethod
200
+ def _required_text(cls, value: str, info: ValidationInfo) -> str:
201
+ return _non_empty(value, str(info.field_name))
202
+
203
+ @model_validator(mode="after")
204
+ def _rejection_must_be_unsafe(self) -> RejectedAutomation:
205
+ if self.safe:
206
+ raise ValueError("rejected automation safe must be false")
207
+ return self
208
+
209
+ def as_dict(self) -> JsonObject:
210
+ payload: JsonObject = {
211
+ "kind": str(self.kind),
212
+ "safe": False,
213
+ "reason_code": self.reason_code,
214
+ "reason": self.reason,
215
+ }
216
+ if self.evidence_refs:
217
+ payload["evidence_refs"] = list(self.evidence_refs)
218
+ return _json_object(payload)
219
+
220
+
221
+ class HumanDecisionOption(ContractModel):
222
+ id: str
223
+ label: str = ""
224
+ value: str = ""
225
+ description: str = ""
226
+ consequence: str = ""
227
+ safety: str = ""
228
+
229
+ @model_validator(mode="after")
230
+ def _requires_closed_label(self) -> HumanDecisionOption:
231
+ object.__setattr__(self, "id", _non_empty(self.id, "id"))
232
+ label = str(self.label or self.value or "").strip()
233
+ if not label:
234
+ raise ValueError("label must be non-empty")
235
+ object.__setattr__(self, "label", label)
236
+ return self
237
+
238
+
239
+ class WorkflowDecisionSummary(ContractModel):
240
+ kind: WorkflowDecisionKind
241
+ phase: str
242
+ reason_code: str
243
+ public_summary: str
244
+ developer_summary: str
245
+ rejected_automations: list[RejectedAutomation] = Field(default_factory=list)
246
+ evidence: list[DecisionEvidence] = Field(default_factory=list)
247
+
248
+ def __init__(self, **data: object) -> None:
249
+ if "kind" in data:
250
+ data["kind"] = WorkflowDecisionKind.coerce(str(data["kind"]))
251
+ super().__init__(**data)
252
+
253
+
254
+ class HumanDecisionPacket(ContractModel):
255
+ schema_id: Literal["workflow.human-decision-packet.v1"] = Field(
256
+ default=HUMAN_DECISION_PACKET_SCHEMA,
257
+ alias="schema",
258
+ )
259
+ kind: str
260
+ type: str = ""
261
+ status: str
262
+ phase: str = ""
263
+ blocked_reason: str = ""
264
+ question: str
265
+ why_this_needs_you: str = ""
266
+ recommended_option_id: str
267
+ options: list[HumanDecisionOption]
268
+ context: JsonObject = Field(default_factory=dict)
269
+ evidence_summary: str = ""
270
+ rejected_automations: list[RejectedAutomation]
271
+ decision_summary: WorkflowDecisionSummary
272
+ resume_action: str
273
+ id: str = ""
274
+ target_kind: str = ""
275
+ target_key: str = ""
276
+ path: str = ""
277
+ target: str = ""
278
+ line: int | str | None = None
279
+ public_summary: str = ""
280
+ continue_after_choice: str = ""
281
+
282
+ @model_validator(mode="after")
283
+ def _validate_packet_contract(self) -> HumanDecisionPacket:
284
+ object.__setattr__(self, "kind", _non_empty(self.kind or self.type, "kind"))
285
+ if not self.type:
286
+ object.__setattr__(self, "type", self.kind)
287
+ object.__setattr__(self, "status", _non_empty(self.status, "status"))
288
+ object.__setattr__(self, "question", _non_empty(self.question, "question"))
289
+ object.__setattr__(self, "resume_action", _non_empty(self.resume_action, "resume_action"))
290
+ if self.decision_summary.kind != WorkflowDecisionKind.ASK_HUMAN:
291
+ raise ValueError("decision_summary must come from ask_human decision")
292
+ option_ids = {option.id for option in self.options}
293
+ if not option_ids:
294
+ raise ValueError("options must contain at least one item")
295
+ recommended = _non_empty(self.recommended_option_id, "recommended_option_id")
296
+ if recommended not in option_ids:
297
+ raise ValueError("recommended_option_id must match an option id")
298
+ rejected = {item.kind for item in self.rejected_automations}
299
+ missing = [kind for kind in WorkflowAutomationKind if kind not in rejected]
300
+ if missing:
301
+ raise ValueError("missing rejected automation evidence for: " + ", ".join(str(kind) for kind in missing))
302
+ return self
303
+
304
+
305
+ class WorkflowDecision(ContractModel):
306
+ kind: WorkflowDecisionKind
307
+ phase: str
308
+ reason_code: str
309
+ public_summary: str
310
+ developer_summary: str
311
+ evidence: list[DecisionEvidence]
312
+ next_action: str
313
+ rejected_automations: list[RejectedAutomation] = Field(default_factory=list)
314
+ required_inputs: list[str] = Field(default_factory=list)
315
+ resume_action: str = ""
316
+ mutates: bool = False
317
+ artifacts: list[dict[str, str]] = Field(default_factory=list)
318
+ recommended_option_id: str = ""
319
+ options: list[HumanDecisionOption] = Field(default_factory=list)
320
+ human_decision_kind: str = ""
321
+
322
+ def __init__(self, **data: object) -> None:
323
+ if "kind" in data:
324
+ data["kind"] = WorkflowDecisionKind.coerce(str(data["kind"]))
325
+ kind = data.get("kind")
326
+ options = data.get("options")
327
+ if kind == WorkflowDecisionKind.ASK_HUMAN and options is not None:
328
+ _closed_option_ids(options, error_prefix="ask_human closed options")
329
+ super().__init__(**data)
330
+ self._validate_decision_contract()
331
+
332
+ @classmethod
333
+ def model_validate(
334
+ cls,
335
+ obj: object,
336
+ *,
337
+ strict: bool | None = None,
338
+ extra: Literal["allow", "forbid", "ignore"] | None = None,
339
+ from_attributes: bool | None = None,
340
+ context: object | None = None,
341
+ by_alias: bool | None = None,
342
+ by_name: bool | None = None,
343
+ ) -> Self:
344
+ decision = super().model_validate(
345
+ obj,
346
+ strict=strict,
347
+ extra=extra,
348
+ from_attributes=from_attributes,
349
+ context=context,
350
+ by_alias=by_alias,
351
+ by_name=by_name,
352
+ )
353
+ decision._validate_decision_contract()
354
+ return decision
355
+
356
+ @field_validator("phase", "reason_code", "public_summary", "developer_summary")
357
+ @classmethod
358
+ def _required_text(cls, value: str, info: ValidationInfo) -> str:
359
+ return _non_empty(value, str(info.field_name))
360
+
361
+ @field_validator("evidence")
362
+ @classmethod
363
+ def _requires_evidence(cls, value: list[DecisionEvidence]) -> list[DecisionEvidence]:
364
+ if not value:
365
+ raise ValueError("evidence must contain at least one item")
366
+ return value
367
+
368
+ def _validate_decision_contract(self) -> None:
369
+ if self.kind == WorkflowDecisionKind.ASK_HUMAN:
370
+ rejected = {item.kind for item in self.rejected_automations}
371
+ missing = [kind for kind in WorkflowAutomationKind if kind not in rejected]
372
+ if missing:
373
+ raise WorkflowOutcomeError(
374
+ "ask_human requires rejected automation evidence for: " + ", ".join(str(kind) for kind in missing)
375
+ )
376
+ option_ids = {option.id for option in self.options}
377
+ if not option_ids:
378
+ raise WorkflowOutcomeError("ask_human closed options requires options")
379
+ if not self.recommended_option_id:
380
+ raise WorkflowOutcomeError("ask_human requires recommended_option_id")
381
+ if self.recommended_option_id not in option_ids:
382
+ raise WorkflowOutcomeError("recommended_option_id must match an option id")
383
+ if (
384
+ self.kind
385
+ in {
386
+ WorkflowDecisionKind.HARD_BLOCK,
387
+ WorkflowDecisionKind.FAILED,
388
+ WorkflowDecisionKind.ASK_HUMAN,
389
+ }
390
+ and not self.next_action
391
+ ):
392
+ raise WorkflowOutcomeError(f"{self.kind} requires next_action")
393
+
394
+ def decision_summary(self) -> JsonObject:
395
+ return _json_object(
396
+ {
397
+ "kind": str(self.kind),
398
+ "phase": self.phase,
399
+ "reason_code": self.reason_code,
400
+ "public_summary": self.public_summary,
401
+ "developer_summary": self.developer_summary,
402
+ "rejected_automations": [item.as_dict() for item in self.rejected_automations],
403
+ "evidence": [item.as_dict() for item in self.evidence],
404
+ }
405
+ )
406
+
407
+ def to_human_decision_packet(self) -> JsonObject:
408
+ if self.kind != WorkflowDecisionKind.ASK_HUMAN:
409
+ raise WorkflowOutcomeError("only ask_human decisions produce human_decision_packet")
410
+ packet_kind = self.human_decision_kind or self.reason_code
411
+ return HumanDecisionPacket(
412
+ schema=HUMAN_DECISION_PACKET_SCHEMA,
413
+ kind=packet_kind,
414
+ type=packet_kind,
415
+ status="pending",
416
+ phase=self.phase,
417
+ blocked_reason=self.reason_code,
418
+ question=self.public_summary,
419
+ why_this_needs_you=self.developer_summary,
420
+ recommended_option_id=self.recommended_option_id,
421
+ options=self.options,
422
+ context={"evidence": [item.as_dict() for item in self.evidence]},
423
+ evidence_summary=self.public_summary,
424
+ rejected_automations=self.rejected_automations,
425
+ decision_summary=self.decision_summary(),
426
+ resume_action=self.resume_action or self.next_action,
427
+ ).to_payload()
428
+
429
+
430
+ class WorkflowPhaseOutcome(ContractModel):
431
+ phase: StrictStr
432
+ decision_summary: JsonObject
433
+ human_decision_packet: HumanDecisionPacket | None = None
434
+
435
+
436
+ class WorkflowPhaseReceipt(ContractModel):
437
+ receipt_path: StrictStr
438
+ status: StrictStr = ""
439
+
440
+
441
+ class WorkflowArtifact(ContractModel):
442
+ kind: StrictStr
443
+ path: StrictStr
444
+
445
+
446
+ class WorkflowRollback(ContractModel):
447
+ strategy: StrictStr
448
+ value: StrictStr
449
+
450
+
451
+ class VersionControlSafety(ContractModel):
452
+ no_resource_mutation: bool = Field(strict=True)
453
+ rollback_declared: bool = Field(strict=True)
454
+ resource_guard_active: bool = Field(default=False, strict=True)
455
+ run_start_seen: bool = Field(default=False, strict=True)
456
+ run_finish_seen: bool = Field(default=False, strict=True)
457
+ restore_point_before: bool | str = False
458
+ restore_point_after: bool | str = False
459
+ sync_status: str = ""
460
+ backup_online: str = ""
461
+ direct_mutation_forbidden: bool = Field(default=True, strict=True)
462
+ mutation_without_guard: bool = Field(default=False, strict=True)
463
+ changed_file_count: int = Field(default=0, ge=0, strict=True)
464
+ agent_instruction: str = ""
465
+
466
+
467
+ from mednotes.kernel.progress import ( # noqa: E402
468
+ WorkflowProgressState,
469
+ WorkflowProgressStatus,
470
+ WorkflowProgressViewModel,
471
+ build_progress_view_model,
472
+ )
473
+ from mednotes.kernel.state_machine import WorkflowStateMachineSnapshot # noqa: E402
474
+
475
+
476
+ class WorkflowReceiptPayload(ContractModel):
477
+ schema_id: str = Field(alias="schema")
478
+ workflow: str
479
+ run_id: str
480
+ status: ReceiptStatus
481
+ mutated: bool
482
+ next_action: str = ""
483
+ human_decision_required: bool = False
484
+ human_decision_packet: HumanDecisionPacket | None = None
485
+ phase_outcomes: list[WorkflowPhaseOutcome] = Field(default_factory=list)
486
+ phase_receipts: dict[str, WorkflowPhaseReceipt] = Field(default_factory=dict)
487
+ artifacts: list[WorkflowArtifact] = Field(default_factory=list)
488
+ changed_files: list[str] = Field(default_factory=list)
489
+ rollback: WorkflowRollback | None = None
490
+ version_control_safety: VersionControlSafety
491
+ progress_state: WorkflowProgressState | None = None
492
+ progress_view_model: WorkflowProgressViewModel | None = None
493
+ state_machine_snapshot: WorkflowStateMachineSnapshot | None = None
494
+
495
+ @model_validator(mode="after")
496
+ def _validate_receipt_contract(self) -> WorkflowReceiptPayload:
497
+ needs_next_action = {
498
+ "blocked",
499
+ "failed",
500
+ "completed_with_warnings",
501
+ "waiting_agent",
502
+ "waiting_external",
503
+ "waiting_human",
504
+ }
505
+ if self.status in needs_next_action and not self.next_action.strip():
506
+ raise ValueError(f"{self.status} receipt requires next_action")
507
+ if self.status == "waiting_human":
508
+ object.__setattr__(self, "human_decision_required", True)
509
+ if self.human_decision_required and self.human_decision_packet is None:
510
+ raise ValueError("human_decision_required receipt requires human_decision_packet")
511
+ if self.human_decision_packet is not None:
512
+ _validate_ask_human_packet(self.human_decision_packet)
513
+ if self.status == "waiting_external":
514
+ self._validate_waiting_external_progress()
515
+ self._validate_progress_ownership()
516
+ return self
517
+
518
+ def _validate_waiting_external_progress(self) -> None:
519
+ if self.progress_state is None:
520
+ raise ValueError("waiting_external receipt requires progress_state")
521
+ if self.progress_state.status != WorkflowProgressStatus.WAITING_EXTERNAL:
522
+ raise ValueError("waiting_external receipt requires waiting_external progress_state")
523
+ if not self.progress_state.resume_action.strip():
524
+ raise ValueError("waiting_external receipt requires progress_state.resume_action")
525
+ if self.progress_state.can_continue_now:
526
+ raise ValueError("waiting_external receipt requires can_continue_now=false")
527
+
528
+ def _validate_progress_ownership(self) -> None:
529
+ for field_name in ("progress_state", "progress_view_model", "state_machine_snapshot"):
530
+ embedded = getattr(self, field_name)
531
+ if embedded is None:
532
+ continue
533
+ _require_same_receipt_identity(
534
+ field_name=field_name,
535
+ receipt_workflow=self.workflow,
536
+ receipt_run_id=self.run_id,
537
+ embedded_workflow=embedded.workflow,
538
+ embedded_run_id=embedded.run_id,
539
+ )
540
+ if self.progress_state is not None and self.progress_view_model is not None:
541
+ _require_progress_view_model_matches_state(self.progress_view_model, self.progress_state)
542
+ if self.progress_state is not None and self.state_machine_snapshot is not None:
543
+ if self.state_machine_snapshot.current_state != self.progress_state.state:
544
+ raise ValueError("state_machine_snapshot current_state must match progress_state state")
545
+
546
+
547
+ def _require_same_receipt_identity(
548
+ *,
549
+ field_name: str,
550
+ receipt_workflow: str,
551
+ receipt_run_id: str,
552
+ embedded_workflow: str,
553
+ embedded_run_id: str,
554
+ ) -> None:
555
+ if embedded_workflow != receipt_workflow:
556
+ raise ValueError(f"{field_name}.workflow must match receipt workflow")
557
+ if embedded_run_id != receipt_run_id:
558
+ raise ValueError(f"{field_name}.run_id must match receipt run_id")
559
+
560
+
561
+ def _require_progress_view_model_matches_state(
562
+ view_model: WorkflowProgressViewModel,
563
+ state: WorkflowProgressState,
564
+ ) -> None:
565
+ expected_payload = build_progress_view_model(state).to_payload()
566
+ actual_payload = view_model.to_payload()
567
+ for field_name, expected_value in expected_payload.items():
568
+ if actual_payload.get(field_name) != expected_value:
569
+ raise ValueError(f"progress_view_model.{field_name} must match canonical progress_state projection")
570
+
571
+
572
+ def attach_human_decision_packet(
573
+ payload: JsonObject,
574
+ *,
575
+ packet: HumanDecisionPacket | JsonObject,
576
+ ) -> JsonObject:
577
+ """Attach an ask_human packet only when it carries the decision contract."""
578
+ typed = _validate_ask_human_packet(packet)
579
+ packet_payload = typed.to_payload()
580
+ enriched: JsonObject = dict(payload)
581
+ enriched["human_decision_required"] = True
582
+ enriched["human_decision_packet"] = packet_payload
583
+ enriched["human_decision_packets"] = [packet_payload]
584
+ enriched["decision_summary"] = typed.decision_summary.to_payload()
585
+ return _json_object(enriched)
586
+
587
+
588
+ def _validate_ask_human_packet(packet: HumanDecisionPacket | JsonObject) -> HumanDecisionPacket:
589
+ if isinstance(packet, HumanDecisionPacket):
590
+ return packet
591
+ try:
592
+ return HumanDecisionPacket.model_validate(packet)
593
+ except PydanticValidationError as exc:
594
+ raise WorkflowOutcomeError(f"human_decision_packet invalid: {exc}") from exc
595
+
596
+
597
+ def _closed_option_ids(options: object, *, error_prefix: str) -> set[str]:
598
+ if not isinstance(options, list) or not options:
599
+ raise WorkflowOutcomeError(f"{error_prefix} requires options")
600
+ option_ids: set[str] = set()
601
+ for index, option in enumerate(options):
602
+ if isinstance(option, HumanDecisionOption):
603
+ option_ids.add(option.id)
604
+ continue
605
+ if not isinstance(option, dict):
606
+ raise WorkflowOutcomeError(f"{error_prefix} options[{index}] must be an object")
607
+ try:
608
+ option_ids.add(HumanDecisionOption.model_validate(option).id)
609
+ except PydanticValidationError as exc:
610
+ raise WorkflowOutcomeError(f"{error_prefix} options[{index}] invalid: {exc}") from exc
611
+ return option_ids
612
+
613
+
614
+ def _json_object(payload: object) -> JsonObject:
615
+ return JsonObjectAdapter.validate_python(payload)
616
+
617
+
618
+ def _json_field(source: JsonObject, key: str, default: JsonValue = None) -> JsonValue:
619
+ return source.get(key, default)
@@ -0,0 +1,5 @@
1
+ """MedNotes platform — infra compartilhada (não-domínio, não-kernel).
2
+
3
+ paths, feedback, config, markdown: o substrato que os bounded contexts usam.
4
+ Ver docs/migration/0005-arvore-ddd-spec.md.
5
+ """