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,852 @@
1
+ """Public FSM projection for `/mednotes:history`.
2
+
3
+ `HistoryMachine` owns the operational state. This module only projects that
4
+ state into the public workflow contract used by hooks, agents, and human-facing
5
+ reports; it does not execute restore IO or infer policy from adapter text.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from enum import StrEnum
11
+ from typing import Literal
12
+
13
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
14
+ from pydantic.json_schema import SkipJsonSchema
15
+
16
+ from mednotes.domains.history.history_machine import (
17
+ HistoryBlockedEvent,
18
+ HistoryFailedEvent,
19
+ HistoryMachine,
20
+ HistoryState,
21
+ PreviewRequiresConfirmationEvent,
22
+ RestoreAppliedEvent,
23
+ RestorePointsListedEvent,
24
+ StaleRestorePointDetectedEvent,
25
+ category_for_history_state,
26
+ )
27
+ from mednotes.kernel.agent_directive import (
28
+ AgentDirective,
29
+ agent_directive_from_progress_view_model,
30
+ assert_agent_directive_matches_progress,
31
+ )
32
+ from mednotes.kernel.base import ContractModel, JsonObject, JsonObjectAdapter
33
+ from mednotes.kernel.effects import WorkflowEffectKind
34
+ from mednotes.kernel.fsm_event import WorkflowEventLike
35
+ from mednotes.kernel.fsm_model import WorkflowModel
36
+ from mednotes.kernel.fsm_transition_result import WorkflowTransitionResult
37
+ from mednotes.kernel.progress import (
38
+ WorkflowProgressCounts,
39
+ WorkflowProgressEventType,
40
+ WorkflowProgressState,
41
+ WorkflowProgressStatus,
42
+ WorkflowProgressViewModel,
43
+ build_progress_view_model,
44
+ progress_state_from_view_model,
45
+ )
46
+ from mednotes.kernel.public_report import (
47
+ WorkflowPrimaryObjectiveSummary,
48
+ WorkflowPublicReport,
49
+ WorkflowReports,
50
+ assert_public_report_matches_progress,
51
+ public_progress_followup_line,
52
+ )
53
+ from mednotes.kernel.state_machine import (
54
+ WorkflowStateCategory,
55
+ WorkflowStateMachineSnapshot,
56
+ WorkflowTransition,
57
+ send_workflow_event,
58
+ )
59
+ from mednotes.kernel.workflow import (
60
+ HumanDecisionPacket,
61
+ VersionControlSafety,
62
+ WorkflowDecision,
63
+ assert_diagnostic_context_evidence_only,
64
+ diagnostic_context_evidence_only,
65
+ )
66
+
67
+ HISTORY_WORKFLOW = "/mednotes:history"
68
+ HISTORY_FSM_SCHEMA = "medical-notes-workbench.history-fsm-result.v1"
69
+ HISTORY_AGENT_DIRECTIVE_FIELD = "agent_directive"
70
+
71
+
72
+ class _HistoryMachineEventEvidence(ContractModel):
73
+ """Typed lens over persisted history machine event evidence."""
74
+
75
+ model_config = ConfigDict(extra="ignore")
76
+
77
+ audit_evidence: JsonObject = Field(default_factory=dict)
78
+
79
+
80
+ HISTORY_ALLOWED_ROOT_KEYS = frozenset(
81
+ {
82
+ "schema",
83
+ "workflow",
84
+ "run_id",
85
+ "state_machine_snapshot",
86
+ "progress_view_model",
87
+ "decision",
88
+ "human_decision_packet",
89
+ "receipt",
90
+ "reports",
91
+ "agent_directive",
92
+ "artifacts",
93
+ "version_control_safety",
94
+ "diagnostic_context",
95
+ "error_context",
96
+ }
97
+ )
98
+ HISTORY_FORBIDDEN_ROOT_KEYS = frozenset(
99
+ {
100
+ "status",
101
+ "phase",
102
+ "blocked_reason",
103
+ "next_action",
104
+ "required_inputs",
105
+ "human_decision_required",
106
+ "workflow_exit_code",
107
+ }
108
+ )
109
+
110
+
111
+ class HistoryVaultOutcome(StrEnum):
112
+ """Canonical outcome for the vault history/restore adapter boundary."""
113
+
114
+ RESTORE_POINTS_LISTED = "restore_points_listed"
115
+ PREVIEW_READY = "preview_ready"
116
+ RESTORE_APPLIED = "restore_applied"
117
+ STALE_RESTORE_POINT = "stale_restore_point"
118
+ BLOCKED = "blocked"
119
+ FAILED = "failed"
120
+
121
+
122
+ class HistoryReceipt(ContractModel):
123
+ """Receipt projection for history states, including non-terminal running states."""
124
+
125
+ schema_id: Literal["medical-notes-workbench.history-receipt.v1"] = Field(
126
+ default="medical-notes-workbench.history-receipt.v1",
127
+ alias="schema",
128
+ )
129
+ workflow: Literal["/mednotes:history"] = HISTORY_WORKFLOW
130
+ run_id: str = Field(min_length=1)
131
+ status: str = Field(min_length=1)
132
+ mutated: bool = False
133
+ next_action: str = ""
134
+ human_decision_required: bool = False
135
+ restored_file_count: int = Field(default=0, ge=0)
136
+ version_control_safety: VersionControlSafety
137
+
138
+
139
+ class HistoryFsmResult(ContractModel):
140
+ schema_id: Literal["medical-notes-workbench.history-fsm-result.v1"] = Field(
141
+ default=HISTORY_FSM_SCHEMA,
142
+ alias="schema",
143
+ )
144
+ workflow: Literal["/mednotes:history"] = HISTORY_WORKFLOW
145
+ run_id: str = Field(min_length=1)
146
+ state_machine_snapshot: WorkflowStateMachineSnapshot
147
+ progress_state: SkipJsonSchema[WorkflowProgressState]
148
+ progress_view_model: WorkflowProgressViewModel
149
+ decision: WorkflowDecision | None = None
150
+ human_decision_packet: HumanDecisionPacket | None = None
151
+ receipt: HistoryReceipt
152
+ reports: WorkflowReports
153
+ agent_directive: JsonObject
154
+ artifacts: JsonObject = Field(default_factory=dict)
155
+ version_control_safety: VersionControlSafety
156
+ diagnostic_context: JsonObject = Field(default_factory=dict)
157
+ error_context: JsonObject = Field(default_factory=dict)
158
+
159
+ @model_validator(mode="before")
160
+ @classmethod
161
+ def _hydrate_progress_state_from_public_payload(cls, value: object) -> object:
162
+ """Accept public payloads where progress_state is intentionally hidden."""
163
+
164
+ if not isinstance(value, dict) or "progress_state" in value or "progress_view_model" not in value:
165
+ return value
166
+ hydrated = dict(value)
167
+ progress_view = WorkflowProgressViewModel.model_validate(value["progress_view_model"])
168
+ hydrated["progress_state"] = progress_state_from_view_model(progress_view).to_payload()
169
+ return hydrated
170
+
171
+ @model_validator(mode="after")
172
+ def _progress_view_model_matches_state(self) -> HistoryFsmResult:
173
+ expected = build_progress_view_model(self.progress_state).to_payload()
174
+ if self.progress_view_model.to_payload() != expected:
175
+ raise ValueError("progress_view_model must match progress_state")
176
+ return self
177
+
178
+ def to_payload(self) -> JsonObject:
179
+ payload: JsonObject = {
180
+ "schema": self.schema_id,
181
+ "workflow": self.workflow,
182
+ "run_id": self.run_id,
183
+ "state_machine_snapshot": self.state_machine_snapshot.to_payload(),
184
+ "progress_view_model": self.progress_view_model.to_payload(),
185
+ "decision": self.decision.to_payload() if self.decision is not None else None,
186
+ "human_decision_packet": self.human_decision_packet.to_payload()
187
+ if self.human_decision_packet is not None
188
+ else None,
189
+ "receipt": self.receipt.to_payload(),
190
+ "reports": self.reports.to_payload(),
191
+ "agent_directive": dict(self.agent_directive),
192
+ "artifacts": dict(self.artifacts),
193
+ "version_control_safety": self.version_control_safety.to_payload(),
194
+ "error_context": dict(self.error_context),
195
+ }
196
+ if self.diagnostic_context:
197
+ payload["diagnostic_context"] = dict(self.diagnostic_context)
198
+ payload = JsonObjectAdapter.validate_python(payload)
199
+ assert_history_fsm_payload(payload)
200
+ return payload
201
+
202
+
203
+ def assert_history_fsm_payload(payload: JsonObject) -> None:
204
+ """Gate the public history FSM payload against legacy root truth."""
205
+
206
+ payload = JsonObjectAdapter.validate_python(payload)
207
+ legacy_keys = set(payload) & HISTORY_FORBIDDEN_ROOT_KEYS
208
+ if legacy_keys:
209
+ raise ValueError(f"history FSM payload contains legacy root keys: {sorted(legacy_keys)}")
210
+ required_keys = HISTORY_ALLOWED_ROOT_KEYS - {"diagnostic_context"}
211
+ missing_keys = required_keys - set(payload)
212
+ if missing_keys:
213
+ raise ValueError(f"history FSM payload missing canonical root keys: {sorted(missing_keys)}")
214
+ unexpected_keys = set(payload) - HISTORY_ALLOWED_ROOT_KEYS
215
+ if unexpected_keys:
216
+ raise ValueError(f"history FSM payload contains unexpected root keys: {sorted(unexpected_keys)}")
217
+ diagnostic_context = payload["diagnostic_context"] if "diagnostic_context" in payload else {}
218
+ assert_diagnostic_context_evidence_only(diagnostic_context)
219
+ if isinstance(diagnostic_context, dict) and "agent_directive" in diagnostic_context:
220
+ raise ValueError("history FSM diagnostic_context must not contain agent_directive")
221
+ reports_payload = JsonObjectAdapter.validate_python(payload["reports"])
222
+ if "human" in reports_payload:
223
+ raise ValueError("history FSM reports must not expose legacy human report text")
224
+ reports = WorkflowReports.model_validate(reports_payload)
225
+ public_report = reports.public_report
226
+ snapshot = WorkflowStateMachineSnapshot.model_validate(payload["state_machine_snapshot"])
227
+ progress_view_model = WorkflowProgressViewModel.model_validate(payload["progress_view_model"])
228
+ receipt = HistoryReceipt.model_validate(payload["receipt"])
229
+ if progress_view_model.status != snapshot.current_category.value:
230
+ raise ValueError("history FSM status must match state_machine_snapshot category")
231
+ if receipt.status != progress_view_model.status:
232
+ raise ValueError("history FSM receipt status must match progress view status")
233
+ assert_public_report_matches_progress(
234
+ public_report,
235
+ workflow=HISTORY_WORKFLOW,
236
+ run_id=str(payload["run_id"]),
237
+ progress_view_model=progress_view_model,
238
+ label="history FSM",
239
+ )
240
+ assert_agent_directive_matches_progress(
241
+ AgentDirective.model_validate(payload[HISTORY_AGENT_DIRECTIVE_FIELD]),
242
+ workflow=HISTORY_WORKFLOW,
243
+ run_id=str(payload["run_id"]),
244
+ progress_view_model=progress_view_model,
245
+ snapshot=snapshot,
246
+ allowed_effect_kinds=_allowed_agent_effect_kinds_for_category(snapshot.current_category),
247
+ label="history FSM",
248
+ )
249
+
250
+
251
+ def _allowed_agent_effect_kinds_for_category(category: WorkflowStateCategory) -> set[WorkflowEffectKind]:
252
+ """History has no hidden executable adapter effect in its public directive."""
253
+
254
+ match category:
255
+ case WorkflowStateCategory.RUNNING:
256
+ return {WorkflowEffectKind.RUN_SUBWORKFLOW}
257
+ case WorkflowStateCategory.WAITING_EXTERNAL:
258
+ return {WorkflowEffectKind.WAIT_EXTERNAL}
259
+ case WorkflowStateCategory.WAITING_HUMAN:
260
+ return {WorkflowEffectKind.ASK_HUMAN}
261
+ case _:
262
+ return set()
263
+
264
+
265
+ def history_fsm_payload_from_model(
266
+ model: WorkflowModel,
267
+ *,
268
+ version_control_safety: VersionControlSafety | dict[str, object],
269
+ ) -> JsonObject:
270
+ """JSON boundary for the machine-driven history FSM projection."""
271
+
272
+ return build_history_fsm_result_from_model(
273
+ model,
274
+ version_control_safety=version_control_safety,
275
+ ).to_payload()
276
+
277
+
278
+ class _VaultHistoryPayload(BaseModel):
279
+ """Typed adapter lens for vault history/restore payloads."""
280
+
281
+ model_config = ConfigDict(extra="ignore", strict=True)
282
+
283
+ schema_id: str = Field(default="", alias="schema")
284
+ adapter_status: str = Field(default="", alias="status")
285
+ adapter_outcome: HistoryVaultOutcome = HistoryVaultOutcome.FAILED
286
+ reason_code: str = ""
287
+ restore_mutated: bool = False
288
+ count: int = Field(default=0, ge=0)
289
+ plan_path: str = ""
290
+ affected_files: list[str] = Field(default_factory=list)
291
+ restored_file_count: int = Field(default=0, ge=0)
292
+ next_action: str = ""
293
+ human_message: str = ""
294
+
295
+ @model_validator(mode="after")
296
+ def _derive_adapter_outcome(self) -> _VaultHistoryPayload:
297
+ status = self.adapter_status
298
+ object.__setattr__(self, "reason_code", status or "history_failed")
299
+ object.__setattr__(
300
+ self,
301
+ "restore_mutated",
302
+ self.schema_id == "medical-notes-workbench.vault-restore-apply.v1" and status == "restored",
303
+ )
304
+ if self.schema_id == "medical-notes-workbench.vault-timeline.v1" and status == "completed":
305
+ object.__setattr__(self, "adapter_outcome", HistoryVaultOutcome.RESTORE_POINTS_LISTED)
306
+ elif self.schema_id == "medical-notes-workbench.vault-restore-plan.v1" and status == "preview_ready":
307
+ object.__setattr__(self, "adapter_outcome", HistoryVaultOutcome.PREVIEW_READY)
308
+ elif self.schema_id == "medical-notes-workbench.vault-restore-apply.v1" and status in {
309
+ "restored",
310
+ "no_changes",
311
+ }:
312
+ object.__setattr__(self, "adapter_outcome", HistoryVaultOutcome.RESTORE_APPLIED)
313
+ elif status == "blocked_stale_preview":
314
+ object.__setattr__(self, "adapter_outcome", HistoryVaultOutcome.STALE_RESTORE_POINT)
315
+ object.__setattr__(self, "reason_code", "stale_restore_point")
316
+ elif status.startswith("blocked"):
317
+ object.__setattr__(self, "adapter_outcome", HistoryVaultOutcome.BLOCKED)
318
+ else:
319
+ object.__setattr__(self, "adapter_outcome", HistoryVaultOutcome.FAILED)
320
+ return self
321
+
322
+
323
+ def history_fsm_payload_from_vault_payload(
324
+ payload: object,
325
+ *,
326
+ run_id: str = "history-vault",
327
+ version_control_safety: VersionControlSafety | dict[str, object] | None = None,
328
+ ) -> JsonObject:
329
+ """Convert a vault history adapter result into the public history FSM payload."""
330
+
331
+ raw = _VaultHistoryPayload.model_validate(payload)
332
+ initial_state, event = _history_event_from_vault_payload(raw, run_id=run_id)
333
+ model = WorkflowModel.start(workflow=HISTORY_WORKFLOW, run_id=run_id, initial_state=initial_state.value)
334
+ send_workflow_event(HistoryMachine(model=model, state_field=WorkflowModel.STATECHART_STATE_FIELD), event)
335
+ return build_history_fsm_result_from_model(
336
+ model,
337
+ version_control_safety=version_control_safety or {"no_resource_mutation": not raw.restore_mutated},
338
+ ).to_payload()
339
+
340
+
341
+ def _history_event_from_vault_payload(
342
+ payload: _VaultHistoryPayload,
343
+ *,
344
+ run_id: str,
345
+ ) -> tuple[HistoryState, WorkflowEventLike]:
346
+ if payload.adapter_outcome == HistoryVaultOutcome.RESTORE_POINTS_LISTED:
347
+ state = HistoryState.LISTING_RESTORE_POINTS
348
+ return (
349
+ state,
350
+ RestorePointsListedEvent(
351
+ workflow=HISTORY_WORKFLOW,
352
+ run_id=run_id,
353
+ current_state=state.value,
354
+ restore_point_count=payload.count,
355
+ ),
356
+ )
357
+ if payload.adapter_outcome == HistoryVaultOutcome.PREVIEW_READY:
358
+ state = HistoryState.PREVIEW_READY
359
+ return (
360
+ state,
361
+ PreviewRequiresConfirmationEvent(
362
+ workflow=HISTORY_WORKFLOW,
363
+ run_id=run_id,
364
+ current_state=state.value,
365
+ restore_preview_path=payload.plan_path or "vault-restore-plan",
366
+ affected_file_count=max(1, len(payload.affected_files)),
367
+ ),
368
+ )
369
+ if payload.adapter_outcome == HistoryVaultOutcome.RESTORE_APPLIED:
370
+ state = HistoryState.APPLYING_RESTORE
371
+ return (
372
+ state,
373
+ RestoreAppliedEvent(
374
+ workflow=HISTORY_WORKFLOW,
375
+ run_id=run_id,
376
+ current_state=state.value,
377
+ restored_file_count=payload.restored_file_count or len(payload.affected_files),
378
+ ),
379
+ )
380
+ if payload.adapter_outcome == HistoryVaultOutcome.STALE_RESTORE_POINT:
381
+ state = HistoryState.APPLYING_RESTORE
382
+ return (
383
+ state,
384
+ StaleRestorePointDetectedEvent(
385
+ workflow=HISTORY_WORKFLOW,
386
+ run_id=run_id,
387
+ current_state=state.value,
388
+ next_action="history:refresh-restore-point",
389
+ ),
390
+ )
391
+ state = HistoryState.LISTING_RESTORE_POINTS
392
+ if payload.adapter_outcome == HistoryVaultOutcome.BLOCKED:
393
+ return (
394
+ state,
395
+ HistoryBlockedEvent(
396
+ workflow=HISTORY_WORKFLOW,
397
+ run_id=run_id,
398
+ current_state=state.value,
399
+ reason_code=payload.reason_code or "history_blocked",
400
+ next_action=payload.next_action or payload.human_message or "Gerar nova prévia pela rota oficial.",
401
+ ),
402
+ )
403
+ return (
404
+ state,
405
+ HistoryFailedEvent(
406
+ workflow=HISTORY_WORKFLOW,
407
+ run_id=run_id,
408
+ current_state=state.value,
409
+ reason_code=payload.reason_code or "history_failed",
410
+ next_action=payload.next_action or payload.human_message or "Repetir /mednotes:history pela rota oficial.",
411
+ ),
412
+ )
413
+
414
+
415
+ def build_history_fsm_result_from_model(
416
+ model: WorkflowModel,
417
+ *,
418
+ version_control_safety: VersionControlSafety | dict[str, object],
419
+ ) -> HistoryFsmResult:
420
+ """Project a real HistoryMachine model into the public workflow contract."""
421
+
422
+ _validate_history_machine_model(model)
423
+ state = HistoryState(model.state)
424
+ category = category_for_history_state(state)
425
+ progress_state = _progress_state_from_model(model, state, category)
426
+ progress_view_model = build_progress_view_model(progress_state)
427
+ snapshot = _snapshot_from_model(model, state, category)
428
+ safety = _version_control_safety(version_control_safety)
429
+ reports = _reports_from_model(state, progress_state)
430
+ public_report = reports.public_report
431
+ agent_directive = agent_directive_from_progress_view_model(
432
+ progress_view_model,
433
+ schema="medical-notes-workbench.agent-directive.v1",
434
+ reason=_machine_reason_code(model, state),
435
+ effects=model.pending_effects,
436
+ blockers=_machine_blockers(category, model, state),
437
+ resume=progress_state.resume_action,
438
+ report_requires=["primary_objective", "restore_preview", "restore_apply"],
439
+ summary=public_report.summary_text(),
440
+ instructions=_machine_agent_instructions(category),
441
+ ).to_payload()
442
+ return HistoryFsmResult(
443
+ run_id=model.run_id,
444
+ state_machine_snapshot=snapshot,
445
+ progress_state=progress_state,
446
+ progress_view_model=progress_view_model,
447
+ decision=model.last_transition.decision if model.last_transition is not None else None,
448
+ human_decision_packet=model.last_transition.human_decision_packet if model.last_transition is not None else None,
449
+ receipt=_receipt_from_model(
450
+ model,
451
+ progress_state=progress_state,
452
+ version_control_safety=safety,
453
+ ),
454
+ reports=reports,
455
+ agent_directive=JsonObjectAdapter.validate_python(agent_directive),
456
+ version_control_safety=safety,
457
+ diagnostic_context=_diagnostic_context_from_model(model, state, category),
458
+ error_context=_error_context_from_model(model, state, category),
459
+ )
460
+
461
+
462
+ def _validate_history_machine_model(model: WorkflowModel) -> None:
463
+ if model.workflow != HISTORY_WORKFLOW:
464
+ raise ValueError(f"history FSM projector requires workflow={HISTORY_WORKFLOW}")
465
+ HistoryState(model.state)
466
+
467
+
468
+ def _progress_state_from_model(
469
+ model: WorkflowModel,
470
+ state: HistoryState,
471
+ category: WorkflowStateCategory,
472
+ ) -> WorkflowProgressState:
473
+ status = _machine_progress_status(category)
474
+ affected = _last_event_int(model, "affected_file_count")
475
+ restored = _last_event_int(model, "restored_file_count")
476
+ total = max(affected, restored)
477
+ current = restored if state == HistoryState.COMPLETED else 0
478
+ return WorkflowProgressState(
479
+ workflow=HISTORY_WORKFLOW,
480
+ run_id=model.run_id,
481
+ state=state.value,
482
+ phase=_machine_phase_for_state(state),
483
+ event_type=_machine_event_type(status),
484
+ message=_machine_message_for_state(state),
485
+ status=status,
486
+ current=current,
487
+ total=total,
488
+ counts=WorkflowProgressCounts(
489
+ planned_items=total,
490
+ processed_items=current,
491
+ mutated_files=restored,
492
+ written_files=restored,
493
+ remaining_items=max(total - current, 0),
494
+ blocked_items=total if status in {WorkflowProgressStatus.BLOCKED, WorkflowProgressStatus.FAILED} else 0,
495
+ ),
496
+ resume_action=_machine_resume_action(model, state),
497
+ resume_supported=status
498
+ in {
499
+ WorkflowProgressStatus.RUNNING,
500
+ WorkflowProgressStatus.WAITING_HUMAN,
501
+ WorkflowProgressStatus.BLOCKED,
502
+ },
503
+ can_continue_now=status == WorkflowProgressStatus.RUNNING,
504
+ decision=model.last_transition.decision.decision_summary()
505
+ if model.last_transition is not None and model.last_transition.decision is not None
506
+ else None,
507
+ technical_context={
508
+ "reason": _machine_reason_code(model, state),
509
+ "category": category.value,
510
+ "source": "HistoryMachine",
511
+ "affected_file_count": affected,
512
+ "restored_file_count": restored,
513
+ },
514
+ )
515
+
516
+
517
+ def _snapshot_from_model(
518
+ model: WorkflowModel,
519
+ state: HistoryState,
520
+ category: WorkflowStateCategory,
521
+ ) -> WorkflowStateMachineSnapshot:
522
+ return WorkflowStateMachineSnapshot(
523
+ workflow=HISTORY_WORKFLOW,
524
+ run_id=model.run_id,
525
+ current_state=state.value,
526
+ current_category=category,
527
+ transitions=[_machine_snapshot_transition(transition) for transition in model.transition_log],
528
+ metadata={"reason": _machine_reason_code(model, state), "source": "HistoryMachine"},
529
+ )
530
+
531
+
532
+ def _machine_snapshot_transition(transition: WorkflowTransitionResult) -> WorkflowTransition:
533
+ return WorkflowTransition(
534
+ workflow=transition.workflow,
535
+ from_state=transition.from_state,
536
+ to_state=transition.to_state,
537
+ to_category=category_for_history_state(HistoryState(transition.to_state)),
538
+ trigger=transition.trigger,
539
+ effects=list(transition.effects),
540
+ decision=transition.decision,
541
+ resume_action=transition.resume_action,
542
+ )
543
+
544
+
545
+ def _receipt_from_model(
546
+ model: WorkflowModel,
547
+ *,
548
+ progress_state: WorkflowProgressState,
549
+ version_control_safety: VersionControlSafety,
550
+ ) -> HistoryReceipt:
551
+ return HistoryReceipt(
552
+ run_id=model.run_id,
553
+ status=progress_state.status.value,
554
+ mutated=version_control_safety.changed_file_count > 0,
555
+ next_action="" if progress_state.status == WorkflowProgressStatus.COMPLETED else progress_state.resume_action,
556
+ human_decision_required=progress_state.status == WorkflowProgressStatus.WAITING_HUMAN,
557
+ restored_file_count=_last_event_int(model, "restored_file_count"),
558
+ version_control_safety=version_control_safety,
559
+ )
560
+
561
+
562
+ def _reports_from_model(state: HistoryState, progress_state: WorkflowProgressState) -> WorkflowReports:
563
+ summary = _machine_message_for_state(state)
564
+ public_lines = [summary]
565
+ followup_line = public_progress_followup_line(progress_state)
566
+ if followup_line:
567
+ public_lines.append(followup_line)
568
+ public_report = WorkflowPublicReport(
569
+ workflow=HISTORY_WORKFLOW,
570
+ run_id=progress_state.run_id,
571
+ headline=summary,
572
+ lines=public_lines,
573
+ )
574
+ return WorkflowReports(
575
+ summary=summary,
576
+ public_report=public_report,
577
+ details={
578
+ "primary_objective_summary": _history_primary_objective_summary(
579
+ state=state,
580
+ progress_state=progress_state,
581
+ ).to_payload()
582
+ },
583
+ )
584
+
585
+
586
+ def _history_primary_objective_summary(
587
+ *,
588
+ state: HistoryState,
589
+ progress_state: WorkflowProgressState,
590
+ ) -> WorkflowPrimaryObjectiveSummary:
591
+ """State-owned answer to whether history listed/restored as requested."""
592
+
593
+ completed = state in {HistoryState.RESTORE_POINTS_LISTED, HistoryState.COMPLETED}
594
+ restored_count = progress_state.counts.written_files
595
+ return WorkflowPrimaryObjectiveSummary(
596
+ workflow=HISTORY_WORKFLOW,
597
+ run_id=progress_state.run_id,
598
+ objective="Listar pontos de restauração e aplicar restauração somente após prévia/decisão.",
599
+ completed=completed,
600
+ status=state.value,
601
+ mutation_state="changed" if restored_count > 0 else "unchanged",
602
+ mutation_summary=_history_mutation_summary(restored_count),
603
+ remaining_work_summary=_history_remaining_work_summary(state, completed),
604
+ next_step_summary=_history_next_step_summary(progress_state, completed),
605
+ blocked_reason="" if completed else state.value,
606
+ )
607
+
608
+
609
+ def _history_mutation_summary(restored_count: int) -> str:
610
+ if restored_count > 0:
611
+ return f"{restored_count} arquivo(s) foram restaurados."
612
+ return "Nenhuma restauração foi aplicada nesta etapa."
613
+
614
+
615
+ def _history_remaining_work_summary(state: HistoryState, completed: bool) -> str:
616
+ if completed and state == HistoryState.RESTORE_POINTS_LISTED:
617
+ return "Pontos de restauração listados; nenhuma restauração foi solicitada."
618
+ if completed:
619
+ return "Restauração aplicada e conferida."
620
+ return _machine_message_for_state(state)
621
+
622
+
623
+ def _history_next_step_summary(progress_state: WorkflowProgressState, completed: bool) -> str:
624
+ if completed:
625
+ return "Nenhuma ação pendente para o histórico nesta rota."
626
+ return progress_state.resume_action or "Retomar /mednotes:history pela rota oficial."
627
+
628
+
629
+ def _diagnostic_context_from_model(
630
+ model: WorkflowModel,
631
+ state: HistoryState,
632
+ category: WorkflowStateCategory,
633
+ ) -> JsonObject:
634
+ if category == WorkflowStateCategory.COMPLETED:
635
+ return {}
636
+ context: JsonObject = {
637
+ "schema": "medical-notes-workbench.history-fsm-diagnostic-context.v1",
638
+ "state": state.value,
639
+ "category": category.value,
640
+ "reason": _machine_reason_code(model, state),
641
+ "source": "HistoryMachine",
642
+ }
643
+ evidence = _machine_audit_evidence(model)
644
+ for key, value in evidence.items():
645
+ if key not in context:
646
+ context[key] = value
647
+ return diagnostic_context_evidence_only(context)
648
+
649
+
650
+ def _error_context_from_model(
651
+ model: WorkflowModel,
652
+ state: HistoryState,
653
+ category: WorkflowStateCategory,
654
+ ) -> JsonObject:
655
+ """Expose a typed recovery route for blocked/failed history states."""
656
+
657
+ if category not in {WorkflowStateCategory.BLOCKED, WorkflowStateCategory.FAILED}:
658
+ return {}
659
+ reason = _machine_reason_code(model, state)
660
+ next_action = _machine_resume_action(model, state) or "history:timeline"
661
+ return JsonObjectAdapter.validate_python(
662
+ {
663
+ "schema": "medical-notes-workbench.error-context.v1",
664
+ "phase": _machine_phase_for_state(state),
665
+ "blocked_reason": reason,
666
+ "root_cause": reason,
667
+ "affected_artifact": "vault_restore",
668
+ "error_summary": _machine_message_for_state(state),
669
+ "suggested_fix": next_action,
670
+ "next_action": next_action,
671
+ "retry_scope": "history_restore_workflow",
672
+ "missing_inputs": [],
673
+ "human_decision_required": category == WorkflowStateCategory.WAITING_HUMAN,
674
+ "version_control_safety": "preserve_vault_restore_point_before_retry",
675
+ }
676
+ )
677
+
678
+
679
+ def _machine_audit_evidence(model: WorkflowModel) -> JsonObject:
680
+ if not model.event_log:
681
+ return {}
682
+ event = _HistoryMachineEventEvidence.model_validate(model.event_log[-1])
683
+ return JsonObjectAdapter.validate_python(event.audit_evidence)
684
+
685
+
686
+ def _machine_progress_status(category: WorkflowStateCategory) -> WorkflowProgressStatus:
687
+ match category:
688
+ case WorkflowStateCategory.PREPARING | WorkflowStateCategory.RUNNING:
689
+ return WorkflowProgressStatus.RUNNING
690
+ case WorkflowStateCategory.WAITING_AGENT:
691
+ return WorkflowProgressStatus.WAITING_AGENT
692
+ case WorkflowStateCategory.WAITING_EXTERNAL:
693
+ return WorkflowProgressStatus.WAITING_EXTERNAL
694
+ case WorkflowStateCategory.WAITING_HUMAN:
695
+ return WorkflowProgressStatus.WAITING_HUMAN
696
+ case WorkflowStateCategory.BLOCKED:
697
+ return WorkflowProgressStatus.BLOCKED
698
+ case WorkflowStateCategory.FAILED:
699
+ return WorkflowProgressStatus.FAILED
700
+ case WorkflowStateCategory.COMPLETED:
701
+ return WorkflowProgressStatus.COMPLETED
702
+ case WorkflowStateCategory.COMPLETED_WITH_WARNINGS:
703
+ return WorkflowProgressStatus.COMPLETED_WITH_WARNINGS
704
+
705
+
706
+ def _machine_event_type(status: WorkflowProgressStatus) -> WorkflowProgressEventType:
707
+ match status:
708
+ case WorkflowProgressStatus.COMPLETED | WorkflowProgressStatus.COMPLETED_WITH_WARNINGS:
709
+ return WorkflowProgressEventType.WORKFLOW_COMPLETED
710
+ case WorkflowProgressStatus.FAILED:
711
+ return WorkflowProgressEventType.WORKFLOW_FAILED
712
+ case WorkflowProgressStatus.WAITING_HUMAN | WorkflowProgressStatus.BLOCKED:
713
+ return WorkflowProgressEventType.DECISION_EMITTED
714
+ case _:
715
+ return WorkflowProgressEventType.STATE_ENTERED
716
+
717
+
718
+ def _machine_phase_for_state(state: HistoryState) -> str:
719
+ match state:
720
+ case HistoryState.LISTING_RESTORE_POINTS:
721
+ return "history_list"
722
+ case HistoryState.RESTORE_POINTS_LISTED:
723
+ return "history_list"
724
+ case HistoryState.PREVIEW_READY | HistoryState.WAITING_HUMAN_CONFIRMATION:
725
+ return "history_preview"
726
+ case HistoryState.APPLYING_RESTORE | HistoryState.COMPLETED:
727
+ return "history_restore"
728
+ case HistoryState.STALE_RESTORE_POINT:
729
+ return "history_stale_restore_point"
730
+ case HistoryState.RESTORE_CONFLICT:
731
+ return "history_restore_conflict"
732
+ case HistoryState.RESTORE_CANCELLED:
733
+ return "history_restore_cancelled"
734
+ case HistoryState.RESTORE_POINT_LIST_BLOCKED:
735
+ return "restore_point_list_blocked"
736
+ case HistoryState.RESTORE_PREVIEW_BLOCKED:
737
+ return "restore_preview_blocked"
738
+ case HistoryState.RESTORE_CONFIRMATION_BLOCKED:
739
+ return "restore_confirmation_blocked"
740
+ case HistoryState.RESTORE_APPLY_BLOCKED:
741
+ return "restore_apply_blocked"
742
+ case HistoryState.FAILED:
743
+ return "history_failed"
744
+
745
+
746
+ def _machine_message_for_state(state: HistoryState) -> str:
747
+ match state:
748
+ case HistoryState.PREVIEW_READY:
749
+ return "Restauração aguardando geração de prévia."
750
+ case HistoryState.WAITING_HUMAN_CONFIRMATION:
751
+ return "Prévia de restauração aguardando confirmação humana."
752
+ case HistoryState.APPLYING_RESTORE:
753
+ return "Restauração aguardando aplicação pelo adapter oficial."
754
+ case HistoryState.STALE_RESTORE_POINT:
755
+ return "Ponto de restauração ficou desatualizado."
756
+ case HistoryState.RESTORE_CONFLICT:
757
+ return "Restauração encontrou conflito antes de aplicar."
758
+ case HistoryState.COMPLETED:
759
+ return "Restauração aplicada e conferida."
760
+ case HistoryState.RESTORE_POINTS_LISTED:
761
+ return "Histórico de pontos de restauração listado."
762
+ case HistoryState.RESTORE_CANCELLED:
763
+ return "Restauração cancelada antes de alterar o vault."
764
+ case HistoryState.RESTORE_POINT_LIST_BLOCKED:
765
+ return "Listagem de pontos de restauração bloqueada."
766
+ case HistoryState.RESTORE_PREVIEW_BLOCKED:
767
+ return "Prévia de restauração bloqueada."
768
+ case HistoryState.RESTORE_CONFIRMATION_BLOCKED:
769
+ return "Confirmação da restauração bloqueada."
770
+ case HistoryState.RESTORE_APPLY_BLOCKED:
771
+ return "Aplicação da restauração bloqueada."
772
+ case HistoryState.FAILED:
773
+ return "Histórico falhou antes de concluir."
774
+ case _:
775
+ return "Histórico em andamento."
776
+
777
+
778
+ def _machine_resume_action(model: WorkflowModel, state: HistoryState) -> str:
779
+ if state in {HistoryState.COMPLETED, HistoryState.RESTORE_POINTS_LISTED}:
780
+ return ""
781
+ if model.last_transition is not None and model.last_transition.resume_action:
782
+ return model.last_transition.resume_action
783
+ match state:
784
+ case HistoryState.PREVIEW_READY:
785
+ return "history:preview"
786
+ case HistoryState.WAITING_HUMAN_CONFIRMATION:
787
+ return "history:confirm-restore"
788
+ case HistoryState.APPLYING_RESTORE:
789
+ return "history:apply"
790
+ case HistoryState.STALE_RESTORE_POINT:
791
+ return "history:refresh-restore-point"
792
+ case HistoryState.RESTORE_CONFLICT:
793
+ return "history:resolve-conflict"
794
+ case (
795
+ HistoryState.RESTORE_CANCELLED
796
+ | HistoryState.RESTORE_POINT_LIST_BLOCKED
797
+ | HistoryState.RESTORE_PREVIEW_BLOCKED
798
+ | HistoryState.RESTORE_CONFIRMATION_BLOCKED
799
+ | HistoryState.RESTORE_APPLY_BLOCKED
800
+ | HistoryState.FAILED
801
+ ):
802
+ return "history:timeline"
803
+ case _:
804
+ return ""
805
+
806
+
807
+ def _machine_reason_code(model: WorkflowModel, state: HistoryState) -> str:
808
+ if model.last_transition is not None:
809
+ return model.last_transition.reason_code
810
+ return state.value
811
+
812
+
813
+ def _machine_blockers(
814
+ category: WorkflowStateCategory,
815
+ model: WorkflowModel,
816
+ state: HistoryState,
817
+ ) -> list[str]:
818
+ if category in {
819
+ WorkflowStateCategory.WAITING_AGENT,
820
+ WorkflowStateCategory.WAITING_EXTERNAL,
821
+ WorkflowStateCategory.WAITING_HUMAN,
822
+ WorkflowStateCategory.BLOCKED,
823
+ WorkflowStateCategory.FAILED,
824
+ }:
825
+ return [_machine_reason_code(model, state)]
826
+ return []
827
+
828
+
829
+ def _machine_agent_instructions(category: WorkflowStateCategory) -> list[str]:
830
+ if category == WorkflowStateCategory.WAITING_HUMAN:
831
+ return ["Peça confirmação humana fechada antes de aplicar restauração."]
832
+ if category in {WorkflowStateCategory.BLOCKED, WorkflowStateCategory.FAILED}:
833
+ return ["Use a decisão e o resume_action da FSM para recuperar /mednotes:history."]
834
+ return ["Execute somente os efeitos em agent_directive.control.effects e retome /mednotes:history pelo resultado tipado."]
835
+
836
+
837
+ def _last_event_int(model: WorkflowModel, field_name: str) -> int:
838
+ if not model.event_log:
839
+ return 0
840
+ event = model.event_log[-1]
841
+ value = event[field_name] if field_name in event else 0
842
+ if isinstance(value, bool):
843
+ return 0
844
+ if isinstance(value, int) and value >= 0:
845
+ return value
846
+ return 0
847
+
848
+
849
+ def _version_control_safety(value: VersionControlSafety | dict[str, object]) -> VersionControlSafety:
850
+ if isinstance(value, VersionControlSafety):
851
+ return value
852
+ return VersionControlSafety.model_validate(value)