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,973 @@
1
+ """Operational StateChart for `/mednotes:setup`.
2
+
3
+ Setup is a public workflow because other workflows recover through it. The
4
+ machine therefore owns the recovery state itself: paths, Python/uv, Markdown
5
+ runtime, Obsidian readiness, vault guard and remote-policy choices are leaf
6
+ states, not `blocked + reason` projections.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from enum import StrEnum
12
+ from typing import Annotated, Literal
13
+
14
+ from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, field_validator, model_validator
15
+ from statemachine import StateChart
16
+ from statemachine.states import States
17
+
18
+ from mednotes.kernel.base import ContractModel, JsonObject
19
+ from mednotes.kernel.effect_intent import WorkflowEffect, WorkflowEffectKind
20
+ from mednotes.kernel.fsm_model import WorkflowModel
21
+ from mednotes.kernel.fsm_transition_result import WorkflowTransitionResult
22
+ from mednotes.kernel.state_machine import WorkflowStateCategory
23
+ from mednotes.kernel.workflow import (
24
+ DecisionEvidence,
25
+ HumanDecisionOption,
26
+ HumanDecisionPacket,
27
+ RejectedAutomation,
28
+ WorkflowDecision,
29
+ )
30
+
31
+ SETUP_WORKFLOW: Literal["/mednotes:setup"] = "/mednotes:setup"
32
+
33
+
34
+ class SetupState(StrEnum):
35
+ CHECKING_ENVIRONMENT = "checking_environment"
36
+ PATHS_REQUIRED = "paths_required"
37
+ PATHS_CONFIGURED = "paths_configured"
38
+ CONFIG_VALIDATION_RUNNING = "config_validation_running"
39
+ CONFIG_ENCODING_REQUIRED = "config_encoding_required"
40
+ PYTHON_ENV_REQUIRED = "python_env_required"
41
+ PYTHON_ENV_READY = "python_env_ready"
42
+ OBSIDIAN_NOT_READY = "obsidian_not_ready"
43
+ MARKDOWN_RUNTIME_REQUIRED = "markdown_runtime_required"
44
+ MARKDOWN_INDEX_REQUIRED = "markdown_index_required"
45
+ MARKDOWN_RUNTIME_READY = "markdown_runtime_ready"
46
+ VAULT_GUARD_REQUIRED = "vault_guard_required"
47
+ VAULT_LOCAL_READY = "vault_local_ready"
48
+ LOCAL_READY_GITHUB_PENDING = "local_ready_github_pending"
49
+ GITHUB_LOGIN_REQUIRED = "github_login_required"
50
+ GITHUB_REMOTE_CONFIRMATION_REQUIRED = "github_remote_confirmation_required"
51
+ GITHUB_REMOTE_AMBIGUOUS = "github_remote_ambiguous"
52
+ BRANCH_CONFIRMATION_REQUIRED = "branch_confirmation_required"
53
+ POLICY_DECISION_REQUIRED = "policy_decision_required"
54
+ READY = "ready"
55
+ FAILED = "failed"
56
+
57
+
58
+ class SetupVaultOutcome(StrEnum):
59
+ """Canonical outcome for the private vault setup adapter boundary."""
60
+
61
+ READY = "ready"
62
+ LOCAL_READY_GITHUB_PENDING = "local_ready_github_pending"
63
+ GITHUB_LOGIN_REQUIRED = "github_login_required"
64
+ GITHUB_REMOTE_CONFIRMATION_REQUIRED = "github_remote_confirmation_required"
65
+ GITHUB_REMOTE_AMBIGUOUS = "github_remote_ambiguous"
66
+ BRANCH_CONFIRMATION_REQUIRED = "branch_confirmation_required"
67
+ PYTHON_ENV_BLOCKED = "python_env_blocked"
68
+ UNSUPPORTED_OR_POLICY_GAP = "unsupported_or_policy_gap"
69
+
70
+
71
+ class SetupVaultDecisionPacket(BaseModel):
72
+ """Typed lens over the vault adapter's private human-decision payload."""
73
+
74
+ model_config = ConfigDict(extra="ignore", strict=True)
75
+
76
+ kind: str = ""
77
+ resume_action: str = ""
78
+ current_branch: str = ""
79
+
80
+
81
+ class SetupVaultAdapterPayload(BaseModel):
82
+ """Normalize `vault_git.py setup` output into a closed setup outcome.
83
+
84
+ The vault script owns its private JSON shape; the setup StateChart owns the
85
+ canonical states. This boundary is the only place where adapter strings are
86
+ translated into a `SetupVaultOutcome`, and ignored extras cannot influence
87
+ workflow policy.
88
+ """
89
+
90
+ model_config = ConfigDict(extra="ignore", strict=True)
91
+
92
+ status_text: str = Field(default="", alias="status")
93
+ blocker_text: str = Field(default="", alias="blocked_reason")
94
+ outcome: SetupVaultOutcome = SetupVaultOutcome.UNSUPPORTED_OR_POLICY_GAP
95
+ summary_text: str = ""
96
+ human_message: str = ""
97
+ local_ready: bool = False
98
+ github_ready: bool = False
99
+ human_decision_required: bool = False
100
+ human_decision_packet: SetupVaultDecisionPacket | None = None
101
+ current_branch: str = ""
102
+
103
+ @model_validator(mode="after")
104
+ def _derive_outcome(self) -> SetupVaultAdapterPayload:
105
+ status_text = self.status_text
106
+ blocker_text = self.blocker_text
107
+ decision_kind = self.human_decision_packet.kind if self.human_decision_packet is not None else ""
108
+ resume_action = self.human_decision_packet.resume_action if self.human_decision_packet is not None else ""
109
+ object.__setattr__(self, "summary_text", self.human_message or status_text or "Setup do vault avaliado.")
110
+ if status_text == "ready" or (self.local_ready and self.github_ready):
111
+ object.__setattr__(self, "outcome", SetupVaultOutcome.READY)
112
+ elif (
113
+ status_text == "blocked_branch_confirmation_required"
114
+ or blocker_text == "non_main_branch"
115
+ or decision_kind == "confirm_main_branch"
116
+ ):
117
+ object.__setattr__(self, "outcome", SetupVaultOutcome.BRANCH_CONFIRMATION_REQUIRED)
118
+ elif blocker_text == "github_login_required" or resume_action == "--start-github-login":
119
+ object.__setattr__(self, "outcome", SetupVaultOutcome.GITHUB_LOGIN_REQUIRED)
120
+ elif status_text == "awaiting_remote_confirmation":
121
+ object.__setattr__(self, "outcome", SetupVaultOutcome.GITHUB_REMOTE_CONFIRMATION_REQUIRED)
122
+ elif self.human_decision_required and self.local_ready:
123
+ object.__setattr__(self, "outcome", SetupVaultOutcome.GITHUB_REMOTE_AMBIGUOUS)
124
+ elif status_text == "local_ready_github_pending" or self.local_ready:
125
+ object.__setattr__(self, "outcome", SetupVaultOutcome.LOCAL_READY_GITHUB_PENDING)
126
+ elif status_text == "blocked_missing_git" or blocker_text in {
127
+ "missing_git",
128
+ "git_missing",
129
+ "environment_blocker.windows_path_or_venv",
130
+ }:
131
+ object.__setattr__(self, "outcome", SetupVaultOutcome.PYTHON_ENV_BLOCKED)
132
+ else:
133
+ object.__setattr__(self, "outcome", SetupVaultOutcome.UNSUPPORTED_OR_POLICY_GAP)
134
+ return self
135
+
136
+
137
+ class SetupEvent(ContractModel):
138
+ """Base event accepted by the setup StateChart."""
139
+
140
+ workflow: str = SETUP_WORKFLOW
141
+ run_id: str = Field(min_length=1)
142
+ current_state: str = Field(min_length=1)
143
+ audit_evidence: JsonObject = Field(default_factory=dict)
144
+
145
+ @field_validator("workflow")
146
+ @classmethod
147
+ def _workflow_must_be_setup(cls, value: str) -> str:
148
+ if value != SETUP_WORKFLOW:
149
+ raise ValueError(f"setup event workflow must be {SETUP_WORKFLOW}")
150
+ return value
151
+
152
+
153
+ def _event_name(event: SetupEvent) -> str:
154
+ """Return the concrete Literal discriminator declared by each event class."""
155
+
156
+ name = getattr(event, "name", "")
157
+ if not isinstance(name, str) or not name.strip():
158
+ raise ValueError("setup events must declare a name discriminator")
159
+ return name
160
+
161
+
162
+ class PathsMissingEvent(SetupEvent):
163
+ name: Literal["paths_missing"] = "paths_missing"
164
+ reason_code: Literal["paths_missing", "wiki_dir_missing"]
165
+ missing_path_kind: Literal["wiki_dir", "raw_dir", "both"]
166
+
167
+
168
+ class PathsOkEvent(SetupEvent):
169
+ name: Literal["paths_ok"] = "paths_ok"
170
+ config_path: str = Field(min_length=1)
171
+
172
+
173
+ class PathsConfiguredEvent(SetupEvent):
174
+ name: Literal["paths_configured"] = "paths_configured"
175
+ config_path: str = Field(min_length=1)
176
+
177
+
178
+ class PythonOrUvInvalidEvent(SetupEvent):
179
+ name: Literal["python_or_uv_invalid"] = "python_or_uv_invalid"
180
+ reason_code: Literal["environment_blocker.windows_path_or_venv"]
181
+
182
+
183
+ class PythonEnvBootstrappedEvent(SetupEvent):
184
+ name: Literal["python_env_bootstrapped"] = "python_env_bootstrapped"
185
+ summary: str = Field(min_length=1)
186
+
187
+
188
+ class ConfigEncodingInvalidEvent(SetupEvent):
189
+ name: Literal["config_encoding_invalid"] = "config_encoding_invalid"
190
+ reason_code: Literal["config_encoding_invalid"]
191
+
192
+
193
+ class ConfigValidationCompletedEvent(SetupEvent):
194
+ name: Literal["config_validation_completed"] = "config_validation_completed"
195
+ config_path: str = Field(min_length=1)
196
+
197
+
198
+ class ConfigValidationBlockedEvent(SetupEvent):
199
+ name: Literal["config_validation_blocked"] = "config_validation_blocked"
200
+ reason_code: Literal["config_encoding_invalid"]
201
+ config_path: str = Field(min_length=1)
202
+
203
+
204
+ class ConfigRepairedEvent(SetupEvent):
205
+ name: Literal["config_repaired"] = "config_repaired"
206
+ config_path: str = Field(min_length=1)
207
+
208
+
209
+ class ObsidianNotReadyEvent(SetupEvent):
210
+ name: Literal["obsidian_not_ready"] = "obsidian_not_ready"
211
+ reason_code: Literal["obsidian_not_ready"]
212
+
213
+
214
+ class ObsidianReadyEvent(SetupEvent):
215
+ name: Literal["obsidian_ready"] = "obsidian_ready"
216
+ summary: str = Field(min_length=1)
217
+
218
+
219
+ class MarkdownRuntimeMissingEvent(SetupEvent):
220
+ name: Literal["markdown_runtime_missing"] = "markdown_runtime_missing"
221
+ reason_code: Literal[
222
+ "markdown_runtime_missing",
223
+ "markdown_runtime_stale",
224
+ "node_runtime_missing",
225
+ "node_runtime_stale",
226
+ ]
227
+
228
+
229
+ class MarkdownRuntimeOkEvent(SetupEvent):
230
+ name: Literal["markdown_runtime_ok"] = "markdown_runtime_ok"
231
+ summary: str = Field(min_length=1)
232
+
233
+
234
+ class MarkdownRuntimeRebuiltEvent(SetupEvent):
235
+ name: Literal["markdown_runtime_rebuilt"] = "markdown_runtime_rebuilt"
236
+ summary: str = Field(min_length=1)
237
+
238
+
239
+ class MarkdownIndexMissingEvent(SetupEvent):
240
+ name: Literal["markdown_index_missing"] = "markdown_index_missing"
241
+ reason_code: Literal["markdown_index_missing", "markdown_index_stale"]
242
+
243
+
244
+ class MarkdownIndexRebuiltEvent(SetupEvent):
245
+ name: Literal["markdown_index_rebuilt"] = "markdown_index_rebuilt"
246
+ summary: str = Field(min_length=1)
247
+
248
+
249
+ class VaultGuardMissingEvent(SetupEvent):
250
+ name: Literal["vault_guard_missing"] = "vault_guard_missing"
251
+ reason_code: Literal["vault_guard_required"]
252
+
253
+
254
+ class VaultGuardOkEvent(SetupEvent):
255
+ name: Literal["vault_guard_ok"] = "vault_guard_ok"
256
+ summary: str = Field(min_length=1)
257
+
258
+
259
+ class VaultGuardConfiguredEvent(SetupEvent):
260
+ name: Literal["vault_guard_configured"] = "vault_guard_configured"
261
+ summary: str = Field(min_length=1)
262
+
263
+
264
+ class GithubRemoteDecisionRequiredEvent(SetupEvent):
265
+ name: Literal["github_remote_decision_required"] = "github_remote_decision_required"
266
+ reason_code: Literal["github_remote_missing", "github_remote_ambiguous"]
267
+
268
+
269
+ class GithubLoginRequiredEvent(SetupEvent):
270
+ name: Literal["github_login_required"] = "github_login_required"
271
+ reason_code: Literal["github_login_required"]
272
+
273
+
274
+ class GithubRemotePendingEvent(SetupEvent):
275
+ name: Literal["github_remote_pending"] = "github_remote_pending"
276
+ reason_code: Literal["github_remote_missing", "github_cli_missing"]
277
+
278
+
279
+ class BranchConfirmationRequiredEvent(SetupEvent):
280
+ name: Literal["branch_confirmation_required"] = "branch_confirmation_required"
281
+ reason_code: Literal["blocked_branch_confirmation_required", "non_main_branch"]
282
+
283
+
284
+ class BranchConfirmedEvent(SetupEvent):
285
+ name: Literal["branch_confirmed"] = "branch_confirmed"
286
+ confirmed_by: str = Field(min_length=1)
287
+
288
+
289
+ class GithubRemoteConfirmedEvent(SetupEvent):
290
+ name: Literal["github_remote_confirmed"] = "github_remote_confirmed"
291
+ confirmed_by: str = Field(min_length=1)
292
+
293
+
294
+ class LocalOnlyAcceptedEvent(SetupEvent):
295
+ name: Literal["local_only_accepted"] = "local_only_accepted"
296
+ accepted_by: str = Field(min_length=1)
297
+
298
+
299
+ class LocalReadyEvent(SetupEvent):
300
+ name: Literal["local_ready"] = "local_ready"
301
+ summary: str = Field(min_length=1)
302
+
303
+
304
+ class UnsupportedHostOrPolicyGapEvent(SetupEvent):
305
+ name: Literal["unsupported_host_or_policy_gap"] = "unsupported_host_or_policy_gap"
306
+ reason_code: Literal["unsupported_host_or_policy_gap"]
307
+
308
+
309
+ class PolicyExceptionConfiguredEvent(SetupEvent):
310
+ name: Literal["policy_exception_configured"] = "policy_exception_configured"
311
+ summary: str = Field(min_length=1)
312
+
313
+
314
+ class UnsupportedByPolicyEvent(SetupEvent):
315
+ name: Literal["unsupported_by_policy"] = "unsupported_by_policy"
316
+ reason_code: Literal["unsupported_by_policy"]
317
+
318
+
319
+ SetupBoundaryEvent = Annotated[
320
+ PathsMissingEvent
321
+ | PathsOkEvent
322
+ | PathsConfiguredEvent
323
+ | PythonOrUvInvalidEvent
324
+ | PythonEnvBootstrappedEvent
325
+ | ConfigEncodingInvalidEvent
326
+ | ConfigValidationCompletedEvent
327
+ | ConfigValidationBlockedEvent
328
+ | ConfigRepairedEvent
329
+ | ObsidianNotReadyEvent
330
+ | ObsidianReadyEvent
331
+ | MarkdownRuntimeMissingEvent
332
+ | MarkdownRuntimeOkEvent
333
+ | MarkdownRuntimeRebuiltEvent
334
+ | MarkdownIndexMissingEvent
335
+ | MarkdownIndexRebuiltEvent
336
+ | VaultGuardMissingEvent
337
+ | VaultGuardOkEvent
338
+ | VaultGuardConfiguredEvent
339
+ | GithubRemoteDecisionRequiredEvent
340
+ | GithubLoginRequiredEvent
341
+ | GithubRemotePendingEvent
342
+ | BranchConfirmationRequiredEvent
343
+ | BranchConfirmedEvent
344
+ | GithubRemoteConfirmedEvent
345
+ | LocalOnlyAcceptedEvent
346
+ | LocalReadyEvent
347
+ | UnsupportedHostOrPolicyGapEvent
348
+ | PolicyExceptionConfiguredEvent
349
+ | UnsupportedByPolicyEvent,
350
+ Field(discriminator="name"),
351
+ ]
352
+ SetupBoundaryEventAdapter = TypeAdapter(SetupBoundaryEvent)
353
+
354
+
355
+ def setup_event_from_vault_adapter_payload(
356
+ payload: SetupVaultAdapterPayload,
357
+ *,
358
+ run_id: str,
359
+ ) -> tuple[SetupState, SetupBoundaryEvent]:
360
+ """Convert the typed vault boundary outcome into a setup StateChart event."""
361
+
362
+ summary = payload.summary_text
363
+ match payload.outcome:
364
+ case SetupVaultOutcome.READY:
365
+ state = SetupState.VAULT_LOCAL_READY
366
+ return (
367
+ state,
368
+ LocalReadyEvent(
369
+ workflow=SETUP_WORKFLOW,
370
+ run_id=run_id,
371
+ current_state=state.value,
372
+ summary=summary,
373
+ ),
374
+ )
375
+ case SetupVaultOutcome.LOCAL_READY_GITHUB_PENDING:
376
+ state = SetupState.VAULT_LOCAL_READY
377
+ return (
378
+ state,
379
+ GithubRemotePendingEvent(
380
+ workflow=SETUP_WORKFLOW,
381
+ run_id=run_id,
382
+ current_state=state.value,
383
+ reason_code="github_remote_missing",
384
+ ),
385
+ )
386
+ case SetupVaultOutcome.GITHUB_LOGIN_REQUIRED:
387
+ state = SetupState.VAULT_LOCAL_READY
388
+ return (
389
+ state,
390
+ GithubLoginRequiredEvent(
391
+ workflow=SETUP_WORKFLOW,
392
+ run_id=run_id,
393
+ current_state=state.value,
394
+ reason_code="github_login_required",
395
+ ),
396
+ )
397
+ case SetupVaultOutcome.GITHUB_REMOTE_CONFIRMATION_REQUIRED:
398
+ state = SetupState.VAULT_LOCAL_READY
399
+ return (
400
+ state,
401
+ GithubRemoteDecisionRequiredEvent(
402
+ workflow=SETUP_WORKFLOW,
403
+ run_id=run_id,
404
+ current_state=state.value,
405
+ reason_code="github_remote_missing",
406
+ ),
407
+ )
408
+ case SetupVaultOutcome.GITHUB_REMOTE_AMBIGUOUS:
409
+ state = SetupState.VAULT_LOCAL_READY
410
+ return (
411
+ state,
412
+ GithubRemoteDecisionRequiredEvent(
413
+ workflow=SETUP_WORKFLOW,
414
+ run_id=run_id,
415
+ current_state=state.value,
416
+ reason_code="github_remote_ambiguous",
417
+ ),
418
+ )
419
+ case SetupVaultOutcome.BRANCH_CONFIRMATION_REQUIRED:
420
+ state = SetupState.CHECKING_ENVIRONMENT
421
+ return (
422
+ state,
423
+ BranchConfirmationRequiredEvent(
424
+ workflow=SETUP_WORKFLOW,
425
+ run_id=run_id,
426
+ current_state=state.value,
427
+ reason_code="blocked_branch_confirmation_required",
428
+ ),
429
+ )
430
+ case SetupVaultOutcome.PYTHON_ENV_BLOCKED:
431
+ state = SetupState.CHECKING_ENVIRONMENT
432
+ return (
433
+ state,
434
+ PythonOrUvInvalidEvent(
435
+ workflow=SETUP_WORKFLOW,
436
+ run_id=run_id,
437
+ current_state=state.value,
438
+ reason_code="environment_blocker.windows_path_or_venv",
439
+ audit_evidence={
440
+ "vault_status": payload.status_text,
441
+ "vault_blocker": payload.blocker_text,
442
+ },
443
+ ),
444
+ )
445
+ case SetupVaultOutcome.UNSUPPORTED_OR_POLICY_GAP:
446
+ state = SetupState.CHECKING_ENVIRONMENT
447
+ return (
448
+ state,
449
+ UnsupportedHostOrPolicyGapEvent(
450
+ workflow=SETUP_WORKFLOW,
451
+ run_id=run_id,
452
+ current_state=state.value,
453
+ reason_code="unsupported_host_or_policy_gap",
454
+ audit_evidence={
455
+ "vault_status": payload.status_text,
456
+ "vault_blocker": payload.blocker_text,
457
+ },
458
+ ),
459
+ )
460
+
461
+
462
+ class SetupMachine(StateChart[WorkflowModel]):
463
+ """Pure domain setup StateChart; effects describe work, never execute it."""
464
+
465
+ allow_event_without_transition = False
466
+ catch_errors_as_events = False
467
+ states = States.from_enum(
468
+ SetupState,
469
+ initial=SetupState.CHECKING_ENVIRONMENT,
470
+ final={SetupState.LOCAL_READY_GITHUB_PENDING, SetupState.READY, SetupState.FAILED},
471
+ use_enum_instance=False,
472
+ )
473
+
474
+ paths_missing = (
475
+ states.CHECKING_ENVIRONMENT.to(states.PATHS_REQUIRED, on="_on_human_required")
476
+ | states.PYTHON_ENV_READY.to(states.PATHS_REQUIRED, on="_on_human_required")
477
+ )
478
+ paths_ok = (
479
+ states.CHECKING_ENVIRONMENT.to(states.CONFIG_VALIDATION_RUNNING, on="_on_agent_required")
480
+ | states.PYTHON_ENV_READY.to(states.CONFIG_VALIDATION_RUNNING, on="_on_agent_required")
481
+ )
482
+ paths_configured = states.PATHS_REQUIRED.to(states.CONFIG_VALIDATION_RUNNING, on="_on_agent_required")
483
+
484
+ python_or_uv_invalid = states.CHECKING_ENVIRONMENT.to(states.PYTHON_ENV_REQUIRED, on="_on_agent_required")
485
+ python_env_bootstrapped = states.PYTHON_ENV_REQUIRED.to(states.PYTHON_ENV_READY, on="_on_transition")
486
+
487
+ config_encoding_invalid = states.CHECKING_ENVIRONMENT.to(
488
+ states.CONFIG_ENCODING_REQUIRED,
489
+ on="_on_agent_required",
490
+ )
491
+ config_validation_completed = states.CONFIG_VALIDATION_RUNNING.to(states.PATHS_CONFIGURED, on="_on_transition")
492
+ config_validation_blocked = states.CONFIG_VALIDATION_RUNNING.to(
493
+ states.CONFIG_ENCODING_REQUIRED,
494
+ on="_on_agent_required",
495
+ )
496
+ config_repaired = states.CONFIG_ENCODING_REQUIRED.to(states.CONFIG_VALIDATION_RUNNING, on="_on_agent_required")
497
+
498
+ obsidian_not_ready = states.PATHS_CONFIGURED.to(states.OBSIDIAN_NOT_READY, on="_on_wait_external")
499
+ obsidian_ready = states.OBSIDIAN_NOT_READY.to(states.PATHS_CONFIGURED, on="_on_transition")
500
+
501
+ markdown_runtime_missing = states.PATHS_CONFIGURED.to(
502
+ states.MARKDOWN_RUNTIME_REQUIRED,
503
+ on="_on_agent_required",
504
+ )
505
+ markdown_runtime_ok = states.PATHS_CONFIGURED.to(states.MARKDOWN_RUNTIME_READY, on="_on_transition")
506
+ markdown_runtime_rebuilt = states.MARKDOWN_RUNTIME_REQUIRED.to(
507
+ states.MARKDOWN_RUNTIME_READY,
508
+ on="_on_transition",
509
+ )
510
+ markdown_index_missing = states.PATHS_CONFIGURED.to(states.MARKDOWN_INDEX_REQUIRED, on="_on_agent_required")
511
+ markdown_index_rebuilt = states.MARKDOWN_INDEX_REQUIRED.to(
512
+ states.MARKDOWN_RUNTIME_READY,
513
+ on="_on_transition",
514
+ )
515
+
516
+ vault_guard_missing = states.MARKDOWN_RUNTIME_READY.to(states.VAULT_GUARD_REQUIRED, on="_on_agent_required")
517
+ vault_guard_ok = states.MARKDOWN_RUNTIME_READY.to(states.VAULT_LOCAL_READY, on="_on_transition")
518
+ vault_guard_configured = states.VAULT_GUARD_REQUIRED.to(states.VAULT_LOCAL_READY, on="_on_transition")
519
+
520
+ github_remote_decision_required = (
521
+ states.VAULT_LOCAL_READY.to(
522
+ states.GITHUB_REMOTE_CONFIRMATION_REQUIRED,
523
+ cond="_is_github_remote_missing",
524
+ on="_on_human_required",
525
+ )
526
+ | states.VAULT_LOCAL_READY.to(
527
+ states.GITHUB_REMOTE_AMBIGUOUS,
528
+ cond="_is_github_remote_ambiguous",
529
+ on="_on_human_required",
530
+ )
531
+ )
532
+ github_login_required = states.VAULT_LOCAL_READY.to(states.GITHUB_LOGIN_REQUIRED, on="_on_human_required")
533
+ github_remote_pending = states.VAULT_LOCAL_READY.to(
534
+ states.LOCAL_READY_GITHUB_PENDING,
535
+ on="_on_transition",
536
+ )
537
+ branch_confirmation_required = states.CHECKING_ENVIRONMENT.to(
538
+ states.BRANCH_CONFIRMATION_REQUIRED,
539
+ on="_on_human_required",
540
+ )
541
+ branch_confirmed = states.BRANCH_CONFIRMATION_REQUIRED.to(states.CHECKING_ENVIRONMENT, on="_on_transition")
542
+ github_remote_confirmed = (
543
+ states.GITHUB_REMOTE_CONFIRMATION_REQUIRED.to(states.READY, on="_on_transition")
544
+ | states.GITHUB_REMOTE_AMBIGUOUS.to(states.READY, on="_on_transition")
545
+ )
546
+ local_only_accepted = (
547
+ states.GITHUB_REMOTE_CONFIRMATION_REQUIRED.to(states.READY, on="_on_transition")
548
+ | states.GITHUB_REMOTE_AMBIGUOUS.to(states.READY, on="_on_transition")
549
+ | states.GITHUB_LOGIN_REQUIRED.to(states.LOCAL_READY_GITHUB_PENDING, on="_on_transition")
550
+ )
551
+ local_ready = states.VAULT_LOCAL_READY.to(states.READY, on="_on_transition")
552
+
553
+ unsupported_host_or_policy_gap = states.CHECKING_ENVIRONMENT.to(
554
+ states.POLICY_DECISION_REQUIRED,
555
+ on="_on_human_required",
556
+ )
557
+ policy_exception_configured = states.POLICY_DECISION_REQUIRED.to(
558
+ states.CHECKING_ENVIRONMENT,
559
+ on="_on_transition",
560
+ )
561
+ unsupported_by_policy = states.POLICY_DECISION_REQUIRED.to(states.FAILED, on="_on_failed")
562
+
563
+ def category_for_state(self, state: str) -> WorkflowStateCategory:
564
+ return category_for_setup_state(SetupState(state))
565
+
566
+ def _on_transition(self, workflow_event: SetupEvent, target: object) -> WorkflowTransitionResult:
567
+ to_state = _target_state(target)
568
+ return _transition(workflow_event, to_state)
569
+
570
+ def _on_agent_required(self, workflow_event: SetupEvent, target: object) -> WorkflowTransitionResult:
571
+ to_state = _target_state(target)
572
+ return _transition(
573
+ workflow_event,
574
+ to_state,
575
+ reason_code=str(getattr(workflow_event, "reason_code", to_state.value)),
576
+ effects=[_setup_effect(workflow_event, to_state)],
577
+ )
578
+
579
+ def _on_wait_external(self, workflow_event: SetupEvent, target: object) -> WorkflowTransitionResult:
580
+ to_state = _target_state(target)
581
+ resume_action = resume_action_for_setup_state(to_state)
582
+ return _transition(
583
+ workflow_event,
584
+ to_state,
585
+ reason_code=str(getattr(workflow_event, "reason_code", to_state.value)),
586
+ effects=[_wait_external_effect(workflow_event, to_state)],
587
+ resume_action=resume_action,
588
+ )
589
+
590
+ def _on_human_required(self, workflow_event: SetupEvent, target: object) -> WorkflowTransitionResult:
591
+ to_state = _target_state(target)
592
+ reason_code = str(getattr(workflow_event, "reason_code", to_state.value))
593
+ return _human_transition(workflow_event, to_state, reason_code=reason_code)
594
+
595
+ def _on_failed(self, workflow_event: SetupEvent, target: object) -> WorkflowTransitionResult:
596
+ to_state = _target_state(target)
597
+ reason_code = str(getattr(workflow_event, "reason_code", to_state.value))
598
+ return _transition(
599
+ workflow_event,
600
+ to_state,
601
+ reason_code=reason_code,
602
+ decision=_decision(kind="failed", phase=to_state.value, reason_code=reason_code),
603
+ )
604
+
605
+ def _is_github_remote_missing(self, workflow_event: GithubRemoteDecisionRequiredEvent) -> bool:
606
+ return workflow_event.reason_code == "github_remote_missing"
607
+
608
+ def _is_github_remote_ambiguous(self, workflow_event: GithubRemoteDecisionRequiredEvent) -> bool:
609
+ return workflow_event.reason_code == "github_remote_ambiguous"
610
+
611
+
612
+ def _transition(
613
+ workflow_event: SetupEvent,
614
+ to_state: SetupState,
615
+ *,
616
+ reason_code: str | None = None,
617
+ effects: list[WorkflowEffect] | None = None,
618
+ decision: WorkflowDecision | None = None,
619
+ human_decision_packet: HumanDecisionPacket | None = None,
620
+ resume_action: str = "",
621
+ ) -> WorkflowTransitionResult:
622
+ return WorkflowTransitionResult(
623
+ workflow=workflow_event.workflow,
624
+ run_id=workflow_event.run_id,
625
+ from_state=workflow_event.current_state,
626
+ to_state=to_state.value,
627
+ trigger=_event_name(workflow_event),
628
+ reason_code=reason_code or str(getattr(workflow_event, "reason_code", _event_name(workflow_event))),
629
+ effects=list(effects or []),
630
+ decision=decision,
631
+ human_decision_packet=human_decision_packet,
632
+ resume_action=resume_action,
633
+ )
634
+
635
+
636
+ def _target_state(target: object) -> SetupState:
637
+ """Read the python-statemachine transition target without touching IO."""
638
+
639
+ value = getattr(target, "value", target)
640
+ return SetupState(str(value))
641
+
642
+
643
+ def _setup_effect(workflow_event: SetupEvent, origin_state: SetupState) -> WorkflowEffect:
644
+ target, payload_kind = _effect_contract_for_state(origin_state)
645
+ return WorkflowEffect(
646
+ workflow=workflow_event.workflow,
647
+ run_id=workflow_event.run_id,
648
+ effect_id=f"setup-{origin_state.value.replace('_', '-')}",
649
+ origin_state=origin_state.value,
650
+ kind=WorkflowEffectKind.RUN_SUBWORKFLOW,
651
+ target=target,
652
+ payload={"kind": payload_kind, "resume_action": resume_action_for_setup_state(origin_state)},
653
+ requires_receipt=False,
654
+ no_resource_mutation=True,
655
+ )
656
+
657
+
658
+ def _wait_external_effect(workflow_event: SetupEvent, origin_state: SetupState) -> WorkflowEffect:
659
+ resume_action = resume_action_for_setup_state(origin_state)
660
+ return WorkflowEffect(
661
+ workflow=workflow_event.workflow,
662
+ run_id=workflow_event.run_id,
663
+ effect_id=f"setup-{origin_state.value.replace('_', '-')}-wait",
664
+ origin_state=origin_state.value,
665
+ kind=WorkflowEffectKind.WAIT_EXTERNAL,
666
+ target="obsidian.plugin",
667
+ payload={
668
+ "schema": "medical-notes-workbench.wait-external-effect-payload.v1",
669
+ "kind": "wait_external",
670
+ "wait_target": "obsidian.plugin",
671
+ "blocked_reason": "obsidian_not_ready",
672
+ "next_action": resume_action,
673
+ "resume_supported": True,
674
+ },
675
+ requires_receipt=False,
676
+ no_resource_mutation=True,
677
+ resume_action=resume_action,
678
+ )
679
+
680
+
681
+ def _human_transition(
682
+ workflow_event: SetupEvent,
683
+ to_state: SetupState,
684
+ *,
685
+ reason_code: str,
686
+ ) -> WorkflowTransitionResult:
687
+ decision = _decision(kind="ask_human", phase=to_state.value, reason_code=reason_code)
688
+ packet = HumanDecisionPacket.model_validate(decision.to_human_decision_packet())
689
+ effect = WorkflowEffect(
690
+ workflow=workflow_event.workflow,
691
+ run_id=workflow_event.run_id,
692
+ effect_id=f"setup-{to_state.value.replace('_', '-')}-human-decision",
693
+ origin_state=to_state.value,
694
+ kind=WorkflowEffectKind.ASK_HUMAN,
695
+ target="human.setup_decision",
696
+ payload={"kind": "setup_human_decision", "reason_code": reason_code},
697
+ requires_receipt=False,
698
+ no_resource_mutation=True,
699
+ )
700
+ return _transition(
701
+ workflow_event,
702
+ to_state,
703
+ reason_code=reason_code,
704
+ effects=[effect],
705
+ decision=decision,
706
+ human_decision_packet=packet,
707
+ resume_action=decision.resume_action,
708
+ )
709
+
710
+
711
+ def _effect_contract_for_state(state: SetupState) -> tuple[str, str]:
712
+ """Map recoverable setup states to the adapter command they require."""
713
+
714
+ match state:
715
+ case SetupState.PYTHON_ENV_REQUIRED:
716
+ return "setup:bootstrap-python", "bootstrap_python"
717
+ case SetupState.CONFIG_VALIDATION_RUNNING:
718
+ return "setup:validate-config", "validate_config"
719
+ case SetupState.CONFIG_ENCODING_REQUIRED:
720
+ return "setup:repair-config", "repair_config"
721
+ case SetupState.MARKDOWN_RUNTIME_REQUIRED:
722
+ return "setup:rebuild-markdown-runtime", "rebuild_markdown_runtime"
723
+ case SetupState.MARKDOWN_INDEX_REQUIRED:
724
+ return "setup:rebuild-markdown-index", "rebuild_markdown_index"
725
+ case SetupState.VAULT_GUARD_REQUIRED:
726
+ return "setup:vault-guard", "vault_guard"
727
+ case _:
728
+ raise AssertionError(f"state does not emit setup effect: {state.value}")
729
+
730
+
731
+ def category_for_setup_state(state: SetupState) -> WorkflowStateCategory:
732
+ """Map setup leaf states to public workflow categories."""
733
+
734
+ match state:
735
+ case (
736
+ SetupState.CHECKING_ENVIRONMENT
737
+ | SetupState.PYTHON_ENV_READY
738
+ | SetupState.PATHS_CONFIGURED
739
+ | SetupState.MARKDOWN_RUNTIME_READY
740
+ | SetupState.VAULT_LOCAL_READY
741
+ ):
742
+ return WorkflowStateCategory.RUNNING
743
+ case (
744
+ SetupState.PATHS_REQUIRED
745
+ | SetupState.GITHUB_LOGIN_REQUIRED
746
+ | SetupState.GITHUB_REMOTE_CONFIRMATION_REQUIRED
747
+ | SetupState.GITHUB_REMOTE_AMBIGUOUS
748
+ | SetupState.BRANCH_CONFIRMATION_REQUIRED
749
+ | SetupState.POLICY_DECISION_REQUIRED
750
+ ):
751
+ return WorkflowStateCategory.WAITING_HUMAN
752
+ case (
753
+ SetupState.CONFIG_VALIDATION_RUNNING
754
+ | SetupState.CONFIG_ENCODING_REQUIRED
755
+ | SetupState.PYTHON_ENV_REQUIRED
756
+ | SetupState.MARKDOWN_RUNTIME_REQUIRED
757
+ | SetupState.MARKDOWN_INDEX_REQUIRED
758
+ | SetupState.VAULT_GUARD_REQUIRED
759
+ ):
760
+ return WorkflowStateCategory.WAITING_AGENT
761
+ case SetupState.OBSIDIAN_NOT_READY:
762
+ return WorkflowStateCategory.WAITING_EXTERNAL
763
+ case SetupState.LOCAL_READY_GITHUB_PENDING:
764
+ return WorkflowStateCategory.COMPLETED_WITH_WARNINGS
765
+ case SetupState.READY:
766
+ return WorkflowStateCategory.COMPLETED
767
+ case SetupState.FAILED:
768
+ return WorkflowStateCategory.FAILED
769
+
770
+
771
+ def resume_action_for_setup_state(state: SetupState) -> str:
772
+ """Return the user/agent-visible recovery action for a setup leaf state."""
773
+
774
+ match state:
775
+ case SetupState.PATHS_REQUIRED:
776
+ return "setup:set-paths"
777
+ case SetupState.PYTHON_ENV_REQUIRED:
778
+ return "setup:bootstrap-python"
779
+ case SetupState.CONFIG_VALIDATION_RUNNING:
780
+ return "setup:validate-config"
781
+ case SetupState.CONFIG_ENCODING_REQUIRED:
782
+ return "setup:repair-config"
783
+ case SetupState.MARKDOWN_RUNTIME_REQUIRED:
784
+ return "setup:rebuild-markdown-runtime"
785
+ case SetupState.MARKDOWN_INDEX_REQUIRED:
786
+ return "setup:rebuild-markdown-index"
787
+ case SetupState.VAULT_GUARD_REQUIRED:
788
+ return "setup:vault-guard"
789
+ case SetupState.OBSIDIAN_NOT_READY:
790
+ return "setup:wait-obsidian"
791
+ case SetupState.GITHUB_LOGIN_REQUIRED:
792
+ return "setup:start-github-login"
793
+ case SetupState.GITHUB_REMOTE_CONFIRMATION_REQUIRED:
794
+ return "setup:confirm-github-remote"
795
+ case SetupState.GITHUB_REMOTE_AMBIGUOUS:
796
+ return "setup:resolve-ambiguous-remote"
797
+ case SetupState.BRANCH_CONFIRMATION_REQUIRED:
798
+ return "setup:confirm-main-branch"
799
+ case SetupState.LOCAL_READY_GITHUB_PENDING:
800
+ return "setup:choose-local-only"
801
+ case SetupState.POLICY_DECISION_REQUIRED:
802
+ return "setup:resolve-policy"
803
+ case _:
804
+ return "/mednotes:setup"
805
+
806
+
807
+ def _decision(
808
+ *,
809
+ kind: Literal["ask_human", "failed"],
810
+ phase: str,
811
+ reason_code: str,
812
+ ) -> WorkflowDecision:
813
+ resume_action = resume_action_for_setup_state(SetupState(phase)) if phase in {state.value for state in SetupState} else ""
814
+ evidence = [
815
+ DecisionEvidence(
816
+ summary=f"setup StateChart reached {phase}.",
817
+ technical_code=reason_code,
818
+ source="setup_machine",
819
+ )
820
+ ]
821
+ base: JsonObject = {
822
+ "kind": kind,
823
+ "phase": phase,
824
+ "reason_code": reason_code,
825
+ "public_summary": "O setup precisa parar nesta etapa.",
826
+ "developer_summary": f"StateChart transition stopped at {phase}:{reason_code}.",
827
+ "evidence": evidence,
828
+ "next_action": resume_action or "/mednotes:setup",
829
+ "resume_action": resume_action or "/mednotes:setup",
830
+ }
831
+ if kind == "ask_human":
832
+ base.update(
833
+ {
834
+ "public_summary": _human_question(SetupState(phase)),
835
+ "human_decision_kind": reason_code,
836
+ "recommended_option_id": _recommended_option_id_for_state(SetupState(phase)),
837
+ "options": _human_options_for_state(SetupState(phase)),
838
+ "rejected_automations": _rejected_automations(reason_code),
839
+ }
840
+ )
841
+ return WorkflowDecision(**base)
842
+
843
+
844
+ def _recommended_option_id_for_state(state: SetupState) -> str:
845
+ match state:
846
+ case SetupState.GITHUB_REMOTE_AMBIGUOUS:
847
+ return "configure_remote"
848
+ case _:
849
+ return "continue"
850
+
851
+
852
+ def _human_question(state: SetupState) -> str:
853
+ match state:
854
+ case SetupState.PATHS_REQUIRED:
855
+ return "Preciso que voce escolha ou confirme os caminhos da Wiki antes de continuar."
856
+ case SetupState.GITHUB_LOGIN_REQUIRED:
857
+ return "Preciso que voce confirme se devo abrir o login do GitHub para ativar backup online."
858
+ case SetupState.GITHUB_REMOTE_CONFIRMATION_REQUIRED:
859
+ return "Preciso que voce confirme se devo criar o repositorio privado de backup."
860
+ case SetupState.GITHUB_REMOTE_AMBIGUOUS:
861
+ return "Preciso que voce escolha qual remote GitHub deve ser usado."
862
+ case SetupState.BRANCH_CONFIRMATION_REQUIRED:
863
+ return "Preciso que voce confirme se posso ajustar a branch principal do vault."
864
+ case SetupState.POLICY_DECISION_REQUIRED:
865
+ return "Preciso que voce escolha uma rota suportada para este ambiente."
866
+ case _:
867
+ return "Preciso de uma decisao sua antes de continuar."
868
+
869
+
870
+ def _human_options_for_state(state: SetupState) -> list[HumanDecisionOption]:
871
+ match state:
872
+ case SetupState.PATHS_REQUIRED:
873
+ return [
874
+ HumanDecisionOption(
875
+ id="continue",
876
+ label="Configurar caminhos",
877
+ description="Abre a rota oficial para definir Wiki e raw chats.",
878
+ ),
879
+ HumanDecisionOption(
880
+ id="cancel",
881
+ label="Parar",
882
+ description="Mantem o workflow bloqueado ate os caminhos serem definidos.",
883
+ ),
884
+ ]
885
+ case SetupState.GITHUB_LOGIN_REQUIRED:
886
+ return [
887
+ HumanDecisionOption(
888
+ id="continue",
889
+ label="Entrar no GitHub",
890
+ description="Abre a rota oficial de login antes de tentar backup online.",
891
+ ),
892
+ HumanDecisionOption(
893
+ id="local_only",
894
+ label="Seguir local-only",
895
+ description="Mantem a protecao local pronta e deixa backup online pendente.",
896
+ ),
897
+ ]
898
+ case SetupState.GITHUB_REMOTE_CONFIRMATION_REQUIRED:
899
+ return [
900
+ HumanDecisionOption(
901
+ id="continue",
902
+ label="Criar backup privado",
903
+ description="Cria o remote privado proposto e finaliza o setup online.",
904
+ ),
905
+ HumanDecisionOption(
906
+ id="local_only",
907
+ label="Seguir local-only",
908
+ description="Libera workflows locais e deixa backup online pendente.",
909
+ ),
910
+ ]
911
+ case SetupState.GITHUB_REMOTE_AMBIGUOUS:
912
+ return [
913
+ HumanDecisionOption(
914
+ id="configure_remote",
915
+ label="Escolher remote",
916
+ description="Resolve qual remote GitHub deve ser usado pelo backup.",
917
+ ),
918
+ HumanDecisionOption(
919
+ id="local_only",
920
+ label="Seguir local-only",
921
+ description="Libera workflows locais e deixa backup online pendente.",
922
+ ),
923
+ ]
924
+ case SetupState.BRANCH_CONFIRMATION_REQUIRED:
925
+ return [
926
+ HumanDecisionOption(
927
+ id="continue",
928
+ label="Ajustar branch",
929
+ description="Renomeia a branch atual para main pela rota oficial.",
930
+ ),
931
+ HumanDecisionOption(
932
+ id="cancel",
933
+ label="Parar",
934
+ description="Mantem a branch atual e deixa o setup bloqueado.",
935
+ ),
936
+ ]
937
+ case SetupState.POLICY_DECISION_REQUIRED:
938
+ return [
939
+ HumanDecisionOption(
940
+ id="continue",
941
+ label="Corrigir ambiente",
942
+ description="Retoma o setup depois de mover para uma rota suportada.",
943
+ ),
944
+ HumanDecisionOption(
945
+ id="cancel",
946
+ label="Abortar",
947
+ description="Encerra o setup sem liberar workflows dependentes.",
948
+ ),
949
+ ]
950
+ case _:
951
+ return [
952
+ HumanDecisionOption(
953
+ id="continue",
954
+ label="Continuar",
955
+ description="Retoma pela rota oficial depois da confirmacao.",
956
+ )
957
+ ]
958
+
959
+
960
+ def _rejected_automations(reason_code: str) -> list[RejectedAutomation]:
961
+ return [
962
+ RejectedAutomation(kind="auto_fix", reason_code=reason_code, reason="Requer confirmacao humana."),
963
+ RejectedAutomation(
964
+ kind="auto_defer",
965
+ reason_code=reason_code,
966
+ reason="Adiar deixaria o setup sem rota de retomada clara.",
967
+ ),
968
+ RejectedAutomation(
969
+ kind="auto_plan",
970
+ reason_code=reason_code,
971
+ reason="Planejar sem escolha humana nao resolve o bloqueio.",
972
+ ),
973
+ ]