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,432 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from enum import StrEnum
5
+ from typing import Literal
6
+
7
+ from pydantic import ConfigDict, Field, model_validator
8
+ from pydantic.json_schema import JsonDict
9
+
10
+ from mednotes.kernel.base import ContractModel, JsonObject
11
+
12
+ _SHA256_PATTERN = re.compile(r"^sha256:[0-9a-f]{64}$")
13
+ _FORBIDDEN_MEDICAL_MODEL_TOKENS = ("flash", "lite", "nano")
14
+ _SPECIALIST_GRADE_MODEL_TOKENS = ("pro", "opus", "sonnet", "specialist")
15
+ SpecialistTaskPhase = Literal["style_rewrite", "note_merge", "medical_authoring"]
16
+ SpecialistOutputReceiptSchema = Literal[
17
+ "medical-notes-workbench.style-rewrite-output.v1",
18
+ "medical-notes-workbench.note-merge-output.v1",
19
+ "medical-notes-workbench.medical-authoring-output.v1",
20
+ ]
21
+ SpecialistOutputAttestationSchema = Literal[
22
+ "medical-notes-workbench.style-rewrite-output-attestation.v1",
23
+ "medical-notes-workbench.note-merge-output-attestation.v1",
24
+ "medical-notes-workbench.medical-authoring-output-attestation.v1",
25
+ ]
26
+
27
+ _SPECIALIST_OUTPUT_RECEIPT_SCHEMA_BY_PHASE: dict[SpecialistTaskPhase, SpecialistOutputReceiptSchema] = {
28
+ "style_rewrite": "medical-notes-workbench.style-rewrite-output.v1",
29
+ "note_merge": "medical-notes-workbench.note-merge-output.v1",
30
+ "medical_authoring": "medical-notes-workbench.medical-authoring-output.v1",
31
+ }
32
+ _SPECIALIST_OUTPUT_ATTESTATION_SCHEMA_BY_PHASE: dict[SpecialistTaskPhase, SpecialistOutputAttestationSchema] = {
33
+ "style_rewrite": "medical-notes-workbench.style-rewrite-output-attestation.v1",
34
+ "note_merge": "medical-notes-workbench.note-merge-output-attestation.v1",
35
+ "medical_authoring": "medical-notes-workbench.medical-authoring-output-attestation.v1",
36
+ }
37
+
38
+
39
+ def _completed_output_reference_json_schema(schema_id: str, phase: str) -> JsonDict:
40
+ return {
41
+ "type": "object",
42
+ "required": ["schema", "work_id", "phase", "status", "output_path", "output_sha256"],
43
+ "properties": {
44
+ "schema": {"const": schema_id},
45
+ "work_id": {"type": "string", "minLength": 1},
46
+ "phase": {"const": phase},
47
+ "status": {"const": "completed"},
48
+ "output_path": {"type": "string", "minLength": 1},
49
+ "output_sha256": {"type": "string", "pattern": r"^sha256:[0-9a-f]{64}$"},
50
+ },
51
+ }
52
+
53
+
54
+ def _completed_phase_json_schema(
55
+ phase: SpecialistTaskPhase,
56
+ receipt_schema: SpecialistOutputReceiptSchema,
57
+ attestation_schema: SpecialistOutputAttestationSchema,
58
+ ) -> JsonDict:
59
+ return {
60
+ "if": {
61
+ "properties": {
62
+ "status": {"const": "completed"},
63
+ "phase": {"const": phase},
64
+ },
65
+ "required": ["status", "phase"],
66
+ },
67
+ "then": {
68
+ "required": ["phase", "specialist_output_receipt", "specialist_output_attestation"],
69
+ "properties": {
70
+ "phase": {"const": phase},
71
+ "specialist_output_receipt": _completed_output_reference_json_schema(receipt_schema, phase),
72
+ "specialist_output_attestation": _completed_output_reference_json_schema(attestation_schema, phase),
73
+ },
74
+ },
75
+ }
76
+
77
+
78
+ _SPECIALIST_TASK_RUN_RECEIPT_JSON_SCHEMA_EXTRA: JsonDict = {
79
+ "allOf": [
80
+ {
81
+ "if": {
82
+ "properties": {"status": {"const": "completed"}},
83
+ "required": ["status"],
84
+ },
85
+ "then": {
86
+ "required": [
87
+ "input_packet_path",
88
+ "input_packet_sha256",
89
+ "phase",
90
+ "model_evidence",
91
+ "output_path",
92
+ "output_sha256",
93
+ "parent_session_id",
94
+ "receipt_attestation",
95
+ "specialist_session_id",
96
+ "specialist_output_receipt",
97
+ "specialist_output_attestation",
98
+ "transcript_artifact_path",
99
+ "transcript_artifact_sha256",
100
+ "validation_status",
101
+ "quality_review_status",
102
+ ],
103
+ "properties": {
104
+ "input_packet_path": {"type": "string", "minLength": 1},
105
+ "input_packet_sha256": {"type": "string", "pattern": r"^sha256:[0-9a-f]{64}$"},
106
+ "model_evidence": {"not": {"type": "null"}},
107
+ "phase": {"enum": ["style_rewrite", "note_merge", "medical_authoring"]},
108
+ "output_path": {"type": "string", "minLength": 1},
109
+ "output_sha256": {"type": "string", "pattern": r"^sha256:[0-9a-f]{64}$"},
110
+ "parent_session_id": {"type": "string", "minLength": 1},
111
+ "receipt_attestation": {"not": {"type": "null"}},
112
+ "specialist_session_id": {"type": "string", "minLength": 1},
113
+ "specialist_output_receipt": {"not": {"type": "null"}},
114
+ "specialist_output_attestation": {"not": {"type": "null"}},
115
+ "transcript_artifact_path": {"type": "string", "minLength": 1},
116
+ "transcript_artifact_sha256": {"type": "string", "pattern": r"^sha256:[0-9a-f]{64}$"},
117
+ "validation_status": {"const": "validated"},
118
+ "quality_review_status": {"const": "accepted"},
119
+ },
120
+ },
121
+ },
122
+ _completed_phase_json_schema(
123
+ "style_rewrite",
124
+ "medical-notes-workbench.style-rewrite-output.v1",
125
+ "medical-notes-workbench.style-rewrite-output-attestation.v1",
126
+ ),
127
+ _completed_phase_json_schema(
128
+ "note_merge",
129
+ "medical-notes-workbench.note-merge-output.v1",
130
+ "medical-notes-workbench.note-merge-output-attestation.v1",
131
+ ),
132
+ _completed_phase_json_schema(
133
+ "medical_authoring",
134
+ "medical-notes-workbench.medical-authoring-output.v1",
135
+ "medical-notes-workbench.medical-authoring-output-attestation.v1",
136
+ ),
137
+ {
138
+ "if": {
139
+ "properties": {"status": {"enum": ["waiting_external", "blocked", "failed"]}},
140
+ "required": ["status"],
141
+ },
142
+ "then": {
143
+ "required": ["next_action", "validation_status", "quality_review_status"],
144
+ "properties": {
145
+ "next_action": {"type": "string", "minLength": 1},
146
+ "validation_status": {"enum": ["not_run", "invalid"]},
147
+ "quality_review_status": {"enum": ["not_reviewed", "needs_review", "rejected"]},
148
+ "specialist_output_receipt": {"type": "null"},
149
+ "specialist_output_attestation": {"type": "null"},
150
+ },
151
+ },
152
+ },
153
+ ],
154
+ }
155
+
156
+
157
+ class SpecialistHarness(StrEnum):
158
+ GEMINI_CLI = "gemini_cli"
159
+ AGY = "agy"
160
+ OPENCODE = "opencode"
161
+ DIRECT_API = "direct_api"
162
+
163
+
164
+ class SpecialistRunStatus(StrEnum):
165
+ COMPLETED = "completed"
166
+ WAITING_EXTERNAL = "waiting_external"
167
+ BLOCKED = "blocked"
168
+ FAILED = "failed"
169
+
170
+
171
+ class SpecialistValidationStatus(StrEnum):
172
+ VALIDATED = "validated"
173
+ NOT_RUN = "not_run"
174
+ INVALID = "invalid"
175
+
176
+
177
+ class SpecialistQualityReviewStatus(StrEnum):
178
+ ACCEPTED = "accepted"
179
+ NOT_REVIEWED = "not_reviewed"
180
+ NEEDS_REVIEW = "needs_review"
181
+ REJECTED = "rejected"
182
+
183
+
184
+ class SpecialistNextApplyStep(ContractModel):
185
+ schema_id: Literal["medical-notes-workbench.specialist-next-apply-step.v1"] = Field(
186
+ default="medical-notes-workbench.specialist-next-apply-step.v1",
187
+ alias="schema",
188
+ )
189
+ command_family: Literal["apply-specialist-style-rewrite"]
190
+ arguments: list[str] = Field(min_length=9)
191
+ must_run_before: list[str] = Field(min_length=1)
192
+ agent_instruction: str = Field(min_length=1)
193
+
194
+ @model_validator(mode="after")
195
+ def _validate_arguments(self) -> SpecialistNextApplyStep:
196
+ required_flags = (
197
+ "--plan",
198
+ "--manifest",
199
+ "--work-id",
200
+ "--specialist-run-receipt",
201
+ "--json",
202
+ )
203
+ missing = [flag for flag in required_flags if flag not in self.arguments]
204
+ if missing:
205
+ raise ValueError(f"specialist next apply step missing arguments: {', '.join(missing)}")
206
+ return self
207
+
208
+
209
+ class SpecialistModelEvidence(ContractModel):
210
+ source: Literal[
211
+ "gemini_cli_agent_metadata",
212
+ "agy_transcript_metadata",
213
+ "agy_settings_snapshot",
214
+ "opencode_task_metadata",
215
+ "direct_api_response_metadata",
216
+ ]
217
+ requested_model: str = Field(min_length=1)
218
+ observed_provider_id: str = Field(min_length=1)
219
+ observed_model_id: str = Field(min_length=1)
220
+ evidence_strength: Literal["runtime_metadata", "settings_and_transcript", "api_response"]
221
+ evidence_excerpt: str = ""
222
+
223
+
224
+ class _SpecialistOutputReferenceBase(ContractModel):
225
+ work_id: str = Field(min_length=1)
226
+ phase: SpecialistTaskPhase | None = None
227
+ status: SpecialistRunStatus | None = None
228
+ output_path: str = ""
229
+ output_sha256: str = ""
230
+
231
+
232
+ class SpecialistOutputReceiptReference(_SpecialistOutputReferenceBase):
233
+ schema_id: SpecialistOutputReceiptSchema = Field(alias="schema")
234
+
235
+
236
+ class SpecialistOutputAttestationReference(_SpecialistOutputReferenceBase):
237
+ schema_id: SpecialistOutputAttestationSchema = Field(alias="schema")
238
+
239
+
240
+ class SpecialistTaskRunReceiptAttestation(ContractModel):
241
+ schema_id: Literal["medical-notes-workbench.specialist-task-run-receipt-attestation.v1"] = Field(
242
+ default="medical-notes-workbench.specialist-task-run-receipt-attestation.v1",
243
+ alias="schema",
244
+ )
245
+ attestation_kind: Literal["workbench_ed25519.v1"]
246
+ created_by: Literal["specialist-task-runner"]
247
+ receipt_schema: Literal["medical-notes-workbench.specialist-task-run-receipt.v1"]
248
+ receipt_hash: str = Field(pattern=r"^sha256:[0-9a-f]{64}$")
249
+ work_id: str = Field(min_length=1)
250
+ phase: SpecialistTaskPhase
251
+ harness: SpecialistHarness
252
+ adapter: str = Field(min_length=1)
253
+ key_id: str = Field(pattern=r"^sha256:[0-9a-f]{64}$")
254
+ nonce: str = Field(min_length=16)
255
+ issued_at: str = Field(min_length=1)
256
+ signature: str = Field(pattern=r"^ed25519:[A-Za-z0-9_-]+={0,2}$")
257
+
258
+
259
+ class SpecialistTaskRunReceipt(ContractModel):
260
+ model_config = ConfigDict(json_schema_extra=_SPECIALIST_TASK_RUN_RECEIPT_JSON_SCHEMA_EXTRA)
261
+
262
+ schema_id: Literal["medical-notes-workbench.specialist-task-run-receipt.v1"] = Field(
263
+ default="medical-notes-workbench.specialist-task-run-receipt.v1",
264
+ alias="schema",
265
+ )
266
+ work_id: str = Field(min_length=1)
267
+ phase: SpecialistTaskPhase = "style_rewrite"
268
+ harness: SpecialistHarness
269
+ adapter: str = Field(min_length=1)
270
+ requested_agent: str = Field(min_length=1)
271
+ requested_model_policy: str = Field(min_length=1)
272
+ requested_model: str = Field(min_length=1)
273
+ observed_model: str = ""
274
+ model_evidence: SpecialistModelEvidence | None = None
275
+ input_packet_path: str = Field(min_length=1)
276
+ input_packet_sha256: str = Field(pattern=r"^sha256:[0-9a-f]{64}$")
277
+ output_path: str = ""
278
+ output_sha256: str = ""
279
+ status: SpecialistRunStatus
280
+ validation_status: SpecialistValidationStatus
281
+ quality_review_status: SpecialistQualityReviewStatus
282
+ parent_session_id: str = ""
283
+ specialist_session_id: str = ""
284
+ transcript_artifact_path: str = ""
285
+ transcript_artifact_sha256: str = ""
286
+ error_context: JsonObject = Field(default_factory=dict)
287
+ next_action: str = ""
288
+ specialist_output_receipt: SpecialistOutputReceiptReference | None = None
289
+ specialist_output_attestation: SpecialistOutputAttestationReference | None = None
290
+ receipt_attestation: SpecialistTaskRunReceiptAttestation | None = None
291
+
292
+ @model_validator(mode="after")
293
+ def _validate_status_contract(self) -> SpecialistTaskRunReceipt:
294
+ match self.status:
295
+ case SpecialistRunStatus.COMPLETED:
296
+ self._validate_completed_receipt()
297
+ self._validate_medical_model_policy()
298
+ case SpecialistRunStatus.WAITING_EXTERNAL | SpecialistRunStatus.BLOCKED | SpecialistRunStatus.FAILED:
299
+ self._validate_non_completed_receipt()
300
+ return self
301
+
302
+ def _validate_completed_receipt(self) -> None:
303
+ if not self.output_path.strip() or not self.output_sha256.strip():
304
+ raise ValueError("completed specialist run requires output_path and output_sha256")
305
+ if _SHA256_PATTERN.fullmatch(self.output_sha256) is None:
306
+ raise ValueError("completed specialist run requires sha256-shaped output hash")
307
+ if not self.input_packet_path.strip() or _SHA256_PATTERN.fullmatch(self.input_packet_sha256) is None:
308
+ raise ValueError("completed specialist run requires input_packet_path and input_packet_sha256")
309
+ if not self.parent_session_id.strip():
310
+ raise ValueError("completed specialist run requires parent_session_id")
311
+ if not self.specialist_session_id.strip():
312
+ raise ValueError("completed specialist run requires specialist_session_id")
313
+ if (
314
+ not self.transcript_artifact_path.strip()
315
+ or _SHA256_PATTERN.fullmatch(self.transcript_artifact_sha256) is None
316
+ ):
317
+ raise ValueError("completed specialist run requires transcript_artifact_path and transcript_artifact_sha256")
318
+ if self.model_evidence is None:
319
+ raise ValueError("completed specialist run requires model_evidence")
320
+ if self.validation_status != SpecialistValidationStatus.VALIDATED:
321
+ raise ValueError("completed specialist run requires validation_status=validated")
322
+ if self.quality_review_status != SpecialistQualityReviewStatus.ACCEPTED:
323
+ raise ValueError("completed specialist run requires quality_review_status=accepted")
324
+ if self.specialist_output_receipt is None:
325
+ raise ValueError("completed specialist run requires specialist_output_receipt")
326
+ if self.specialist_output_attestation is None:
327
+ raise ValueError("completed specialist run requires specialist_output_attestation")
328
+ if self.receipt_attestation is None:
329
+ raise ValueError("completed specialist run requires receipt_attestation")
330
+ self._validate_completed_output_reference(
331
+ self.specialist_output_receipt,
332
+ "specialist_output_receipt",
333
+ _SPECIALIST_OUTPUT_RECEIPT_SCHEMA_BY_PHASE[self.phase],
334
+ )
335
+ self._validate_completed_output_reference(
336
+ self.specialist_output_attestation,
337
+ "specialist_output_attestation",
338
+ _SPECIALIST_OUTPUT_ATTESTATION_SCHEMA_BY_PHASE[self.phase],
339
+ )
340
+ self._validate_completed_receipt_attestation()
341
+
342
+ def _validate_completed_receipt_attestation(self) -> None:
343
+ if self.receipt_attestation is None:
344
+ raise ValueError("completed specialist run requires receipt_attestation")
345
+ if self.receipt_attestation.receipt_schema != self.schema_id:
346
+ raise ValueError("completed specialist run requires receipt_attestation.receipt_schema to match schema")
347
+ if self.receipt_attestation.work_id != self.work_id:
348
+ raise ValueError("completed specialist run requires receipt_attestation.work_id to match work_id")
349
+ if self.receipt_attestation.phase != self.phase:
350
+ raise ValueError("completed specialist run requires receipt_attestation.phase to match phase")
351
+ if self.receipt_attestation.harness != self.harness:
352
+ raise ValueError("completed specialist run requires receipt_attestation.harness to match harness")
353
+ if self.receipt_attestation.adapter != self.adapter:
354
+ raise ValueError("completed specialist run requires receipt_attestation.adapter to match adapter")
355
+
356
+ def _validate_completed_output_reference(
357
+ self,
358
+ reference: SpecialistOutputReceiptReference | SpecialistOutputAttestationReference,
359
+ field_name: str,
360
+ expected_schema: str,
361
+ ) -> None:
362
+ if reference.schema_id != expected_schema:
363
+ raise ValueError(f"completed specialist run requires {field_name}.schema={expected_schema}")
364
+ if reference.work_id != self.work_id:
365
+ raise ValueError(f"completed specialist run requires {field_name}.work_id to match work_id")
366
+ if reference.phase is None:
367
+ raise ValueError(f"completed specialist run requires {field_name}.phase")
368
+ if reference.status is None:
369
+ raise ValueError(f"completed specialist run requires {field_name}.status")
370
+ if not reference.output_path.strip() or not reference.output_sha256.strip():
371
+ raise ValueError(f"completed specialist run requires {field_name} output_path and output_sha256")
372
+ if reference.output_path != self.output_path:
373
+ raise ValueError(f"completed specialist run requires {field_name}.output_path to match output_path")
374
+ if reference.output_sha256 != self.output_sha256:
375
+ raise ValueError(f"completed specialist run requires {field_name}.output_sha256 to match output_sha256")
376
+ if reference.status != SpecialistRunStatus.COMPLETED:
377
+ raise ValueError(f"completed specialist run requires {field_name}.status=completed")
378
+ if reference.phase != self.phase:
379
+ raise ValueError(f"completed specialist run requires {field_name}.phase to match phase")
380
+
381
+ def _validate_non_completed_receipt(self) -> None:
382
+ if not self.next_action.strip():
383
+ raise ValueError(f"{self.status.value} specialist run requires next_action")
384
+ if self.validation_status == SpecialistValidationStatus.VALIDATED:
385
+ raise ValueError("non-completed specialist run cannot be validated")
386
+ if self.quality_review_status == SpecialistQualityReviewStatus.ACCEPTED:
387
+ raise ValueError("non-completed specialist run cannot be accepted")
388
+ if (
389
+ self.specialist_output_receipt is not None
390
+ or self.specialist_output_attestation is not None
391
+ or self.receipt_attestation is not None
392
+ ):
393
+ raise ValueError(
394
+ "non-completed specialist run cannot include specialist output references or receipt_attestation"
395
+ )
396
+
397
+ def _validate_medical_model_policy(self) -> None:
398
+ if self.requested_model_policy.strip().lower() != "medical_specialist_authoring.v1":
399
+ return
400
+ observed_tokens = self._observed_model_and_provider_policy_tokens()
401
+ if any(
402
+ forbidden_token in observed_token
403
+ for observed_token in observed_tokens
404
+ for forbidden_token in _FORBIDDEN_MEDICAL_MODEL_TOKENS
405
+ ):
406
+ raise ValueError("specialist model policy forbids flash/lite/nano for medical authoring")
407
+ observed_model_tokens = self._observed_model_identity_policy_tokens()
408
+ if not any(token in _SPECIALIST_GRADE_MODEL_TOKENS for token in observed_model_tokens):
409
+ raise ValueError("specialist model policy requires pro/specialist-grade observed model")
410
+
411
+ def _observed_model_and_provider_policy_tokens(self) -> tuple[str, ...]:
412
+ observed_values = [self.observed_model]
413
+ if self.model_evidence is not None:
414
+ observed_values.extend(
415
+ [
416
+ self.model_evidence.observed_provider_id,
417
+ self.model_evidence.observed_model_id,
418
+ ]
419
+ )
420
+ observed_text = " ".join(value.lower() for value in observed_values if value)
421
+ return tuple(re.findall(r"[a-z0-9]+", observed_text))
422
+
423
+ def _observed_model_identity_policy_tokens(self) -> tuple[str, ...]:
424
+ observed_values = [self.observed_model]
425
+ if self.model_evidence is not None:
426
+ observed_values.append(self.model_evidence.observed_model_id)
427
+ observed_text = " ".join(value.lower() for value in observed_values if value)
428
+ return tuple(re.findall(r"[a-z0-9]+", observed_text))
429
+
430
+ @classmethod
431
+ def from_operation_payload(cls, payload: object) -> SpecialistTaskRunReceipt:
432
+ return cls.model_validate(payload)
@@ -0,0 +1,62 @@
1
+ """Typed snapshot contract for the non-mutating Workbench status surface.
2
+
3
+ `/mednotes:status` is intentionally not a workflow FSM: it observes local
4
+ configuration and reports recovery routes. This model keeps that adapter output
5
+ typed so status fields cannot become a parallel source of truth for FSM-first
6
+ workflows.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from enum import StrEnum
11
+ from typing import Literal
12
+
13
+ from pydantic import Field, model_validator
14
+
15
+ from mednotes.kernel.base import ContractModel, JsonObject
16
+
17
+
18
+ class StatusSnapshotPhase(StrEnum):
19
+ STATUS = "status"
20
+
21
+
22
+ class StatusSnapshotStatus(StrEnum):
23
+ READY = "ready"
24
+ COMPLETED_WITH_WARNINGS = "completed_with_warnings"
25
+ BLOCKED = "blocked"
26
+
27
+
28
+ class StatusSnapshot(ContractModel):
29
+ schema_: Literal["medical-notes-workbench.status.v1"] = Field(
30
+ "medical-notes-workbench.status.v1",
31
+ alias="schema",
32
+ )
33
+ phase: StatusSnapshotPhase = StatusSnapshotPhase.STATUS
34
+ status: StatusSnapshotStatus
35
+ blocked_reason: str = ""
36
+ next_action: str = ""
37
+ required_inputs: list[str] = Field(default_factory=list)
38
+ human_decision_required: bool = False
39
+ raw_dir: str = ""
40
+ raw_dir_exists: bool = False
41
+ wiki_dir: str = ""
42
+ wiki_dir_exists: bool = False
43
+ wiki_source: str = ""
44
+ wiki_memory_path: str = ""
45
+ config_path: str = ""
46
+ catalog_path: str = ""
47
+ catalog_path_exists: bool = False
48
+ vocabulary_db_path: str = ""
49
+ vocabulary_db_exists: bool = False
50
+ warnings: list[str] = Field(default_factory=list)
51
+ path_resolution: JsonObject | None = None
52
+ environment_preflight: JsonObject = Field(default_factory=dict)
53
+ validate_environment: JsonObject = Field(default_factory=dict)
54
+
55
+ @model_validator(mode="after")
56
+ def blocked_status_has_recovery_route(self) -> StatusSnapshot:
57
+ if self.status == StatusSnapshotStatus.BLOCKED:
58
+ if not self.blocked_reason.strip():
59
+ raise ValueError("blocked status snapshot requires blocked_reason")
60
+ if not self.next_action.strip():
61
+ raise ValueError("blocked status snapshot requires next_action")
62
+ return self