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,28 @@
1
+ """Primary objective projection for `/mednotes:process-chats`.
2
+
3
+ The FSM payload is the only source of truth. This module intentionally does not
4
+ reconstruct success from old root fields such as `status`, `phase`,
5
+ `blocked_reason`, `created_count` or `linker_applied`; those fields are legacy
6
+ run-record evidence and must not fabricate a primary objective result.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from mednotes.domains.wiki.contracts.agent_report import ProcessChatsPrimaryObjectiveSummary
11
+ from mednotes.domains.wiki.flows.process_chats.process_chats_fsm import PROCESS_CHATS_SCHEMA
12
+ from mednotes.kernel.base import JsonObject, JsonObjectAdapter
13
+
14
+
15
+ def process_chats_primary_objective_summary(payload: JsonObject) -> ProcessChatsPrimaryObjectiveSummary | None:
16
+ """Return the FSM-authored primary objective summary, if this is process-chats."""
17
+ normalized = JsonObjectAdapter.validate_python(payload)
18
+ schema = str(normalized["schema"]) if "schema" in normalized else ""
19
+ workflow = str(normalized["workflow"]) if "workflow" in normalized else ""
20
+ if schema != PROCESS_CHATS_SCHEMA or workflow != "/mednotes:process-chats":
21
+ return None
22
+
23
+ reports = JsonObjectAdapter.validate_python(normalized["reports"] if "reports" in normalized else {})
24
+ details = JsonObjectAdapter.validate_python(reports["details"] if "details" in reports else {})
25
+ if "primary_objective_summary" not in details:
26
+ return None
27
+ summary = JsonObjectAdapter.validate_python(details["primary_objective_summary"])
28
+ return ProcessChatsPrimaryObjectiveSummary.model_validate(summary)
@@ -0,0 +1,185 @@
1
+ """Typed runtime boundary for `/mednotes:process-chats`.
2
+
3
+ `publish-batch` still returns an operational JSON payload. This module owns the
4
+ translation from that payload into one canonical `ProcessChatsMachine` event so
5
+ the public projector in `process_chats_fsm.py` does not classify runtime status.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from mednotes.domains.wiki.flows.process_chats.process_chats_fsm import (
11
+ ProcessChatsFsmFacts,
12
+ ProcessChatsLinkerDiagnostic,
13
+ ProcessChatsOperationalSummary,
14
+ ProcessChatsPublishDiagnostic,
15
+ ProcessChatsPublishOperationResult,
16
+ build_process_chats_fsm_result,
17
+ )
18
+ from mednotes.domains.wiki.flows.process_chats.process_chats_machine import (
19
+ PROCESS_CHATS_WORKFLOW,
20
+ ProcessChatsErrorContext,
21
+ ProcessChatsPublishRuntimeObservation,
22
+ ProcessChatsPublishRuntimeObservedEvent,
23
+ ProcessChatsState,
24
+ )
25
+ from mednotes.kernel.base import JsonObject
26
+ from mednotes.kernel.workflow import VersionControlSafety
27
+
28
+
29
+ def process_chats_fsm_payload_from_publish_result(
30
+ result: JsonObject,
31
+ *,
32
+ run_id: str,
33
+ version_control_safety: VersionControlSafety | dict[str, object],
34
+ ) -> JsonObject:
35
+ return build_process_chats_fsm_result(
36
+ process_chats_fsm_facts_from_publish_result(
37
+ result,
38
+ run_id=run_id,
39
+ version_control_safety=version_control_safety,
40
+ )
41
+ ).to_payload()
42
+
43
+
44
+ def process_chats_fsm_facts_from_publish_result(
45
+ result: JsonObject,
46
+ *,
47
+ run_id: str,
48
+ version_control_safety: VersionControlSafety | dict[str, object],
49
+ ) -> ProcessChatsFsmFacts:
50
+ typed_result = ProcessChatsPublishOperationResult.model_validate(result)
51
+ observation = _publish_observation_from_result(typed_result)
52
+ initial_state = _publish_observation_source_state(observation)
53
+ event = ProcessChatsPublishRuntimeObservedEvent(
54
+ workflow=PROCESS_CHATS_WORKFLOW,
55
+ run_id=run_id,
56
+ current_state=initial_state.value,
57
+ observation=observation,
58
+ audit_evidence=_publish_observation_audit_evidence(typed_result),
59
+ )
60
+ event_error_context = observation.error_context
61
+ error_context = typed_result.error_context
62
+ if not error_context and isinstance(event_error_context, ProcessChatsErrorContext):
63
+ error_context = event_error_context.to_payload()
64
+ return ProcessChatsFsmFacts(
65
+ run_id=run_id,
66
+ initial_state=initial_state,
67
+ event=event,
68
+ operational_summary=_operational_summary_from_publish_result(typed_result),
69
+ version_control_safety=version_control_safety,
70
+ error_context=error_context,
71
+ )
72
+
73
+
74
+ def _publish_observation_from_result(result: ProcessChatsPublishOperationResult) -> ProcessChatsPublishRuntimeObservation:
75
+ """Return the canonical facts emitted by the publish/link producer."""
76
+
77
+ return result.runtime_observation
78
+
79
+
80
+ def _publish_observation_source_state(observation: ProcessChatsPublishRuntimeObservation) -> ProcessChatsState:
81
+ """Use the validated entry state supplied with the canonical observation."""
82
+
83
+ return observation.source_state
84
+
85
+
86
+ def _operational_summary_from_publish_result(
87
+ result: ProcessChatsPublishOperationResult,
88
+ ) -> ProcessChatsOperationalSummary:
89
+ """Build non-authoritative counts and diagnostics for reports only."""
90
+
91
+ linker = result.linker
92
+ return ProcessChatsOperationalSummary(
93
+ note_count=_note_count(result),
94
+ raw_count=_raw_count(result),
95
+ coverage_raw_count=_coverage_raw_count(result),
96
+ planned_note_count=_planned_note_count(result),
97
+ mutated=_mutated(result),
98
+ changed_files=_changed_files(result),
99
+ blocked_item_count=max(1, linker.blocker_count) if linker is not None and linker.blocker_count else 0,
100
+ next_action=result.next_action,
101
+ publish=ProcessChatsPublishDiagnostic(
102
+ status=result.status,
103
+ receipt_status=result.publish_receipt.status if result.publish_receipt is not None else "",
104
+ dry_run=result.dry_run,
105
+ manifest=result.manifest,
106
+ dry_run_receipt=result.dry_run_receipt,
107
+ new_taxonomy_leaf_authorization=result.new_taxonomy_leaf_authorization,
108
+ ),
109
+ linker=ProcessChatsLinkerDiagnostic(
110
+ status=linker.status if linker is not None else "",
111
+ next_action=linker.next_action if linker is not None else "",
112
+ diagnosis_status=linker.diagnosis_status if linker is not None else "",
113
+ applied=_linker_applied(result),
114
+ skipped_reason=result.linker_skipped_reason or (linker.linker_skipped_reason if linker is not None else ""),
115
+ blocker_count=linker.blocker_count if linker is not None else 0,
116
+ ),
117
+ artifacts=_artifacts(result),
118
+ )
119
+
120
+
121
+ def _note_count(payload: ProcessChatsPublishOperationResult) -> int:
122
+ return payload.created_count or (payload.publish_receipt.published_count if payload.publish_receipt else 0) or len(payload.created)
123
+
124
+
125
+ def _raw_count(payload: ProcessChatsPublishOperationResult) -> int:
126
+ return payload.processed_raw_count or len(payload.raw_updates)
127
+
128
+
129
+ def _coverage_raw_count(payload: ProcessChatsPublishOperationResult) -> int:
130
+ coverage = payload.coverage_summary or payload.coverage
131
+ count = (coverage.raw_file_count if coverage is not None else 0) or (coverage.covered_count if coverage is not None else 0)
132
+ if count:
133
+ return count
134
+ total = 0
135
+ for batch in payload.planned_batches:
136
+ raw_files = batch.raw_files
137
+ total += len(raw_files)
138
+ if not raw_files and batch.raw_file:
139
+ total += 1
140
+ return total
141
+
142
+
143
+ def _planned_note_count(payload: ProcessChatsPublishOperationResult) -> int:
144
+ return sum(len(batch.notes) for batch in payload.planned_batches)
145
+
146
+
147
+ def _linker_applied(payload: ProcessChatsPublishOperationResult) -> bool:
148
+ return bool(payload.linker_applied or (payload.linker is not None and payload.linker.linker_applied))
149
+
150
+
151
+ def _changed_files(payload: ProcessChatsPublishOperationResult) -> list[str]:
152
+ return [item for item in payload.created if item.strip()]
153
+
154
+
155
+ def _mutated(payload: ProcessChatsPublishOperationResult) -> bool:
156
+ return not payload.dry_run and bool(_changed_files(payload) or _raw_count(payload))
157
+
158
+
159
+ def _artifacts(publish_result: ProcessChatsPublishOperationResult) -> JsonObject:
160
+ artifacts: JsonObject = {}
161
+ if publish_result.manifest:
162
+ artifacts["manifest"] = publish_result.manifest
163
+ if publish_result.link_trigger_context_path:
164
+ artifacts["link_trigger_context_path"] = publish_result.link_trigger_context_path
165
+ if publish_result.linker_diagnosis_path:
166
+ artifacts["linker_diagnosis_path"] = publish_result.linker_diagnosis_path
167
+ if publish_result.linker_receipt_path:
168
+ artifacts["linker_receipt_path"] = publish_result.linker_receipt_path
169
+ if publish_result.dry_run_receipt is not None and publish_result.dry_run_receipt.path:
170
+ artifacts["dry_run_receipt_manifest"] = publish_result.dry_run_receipt.path
171
+ return artifacts
172
+
173
+
174
+ def _publish_observation_audit_evidence(result: ProcessChatsPublishOperationResult) -> JsonObject:
175
+ return {
176
+ "adapter_schema": result.schema_id or "",
177
+ "adapter_phase": result.phase,
178
+ "adapter_status": result.status,
179
+ "adapter_reason": result.blocked_reason,
180
+ "counts": {
181
+ "created_count": result.created_count,
182
+ "processed_raw_count": result.processed_raw_count,
183
+ "changed_file_count": len(_changed_files(result)),
184
+ },
185
+ }
@@ -0,0 +1,97 @@
1
+ """Small cooperative pacing helpers for long interactive wiki workflows."""
2
+ from __future__ import annotations
3
+
4
+ import time
5
+ from collections.abc import Callable, Iterator
6
+ from contextlib import contextmanager
7
+ from contextvars import ContextVar
8
+ from dataclasses import dataclass
9
+
10
+ SleepFn = Callable[[float], None]
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class CooperativeCpuYieldSettings:
15
+ enabled: bool
16
+ every: int
17
+ seconds: float
18
+
19
+
20
+ _CPU_YIELD_ENABLED: ContextVar[bool] = ContextVar("mednotes_cpu_yield_enabled", default=False)
21
+ _CPU_YIELD_EVERY: ContextVar[int] = ContextVar("mednotes_cpu_yield_every", default=32)
22
+ _CPU_YIELD_SECONDS: ContextVar[float] = ContextVar("mednotes_cpu_yield_seconds", default=0.0)
23
+ _CPU_YIELD_SLEEP: ContextVar[SleepFn] = ContextVar("mednotes_cpu_yield_sleep", default=time.sleep)
24
+
25
+
26
+ def _positive_int_from_env(name: str, default: int) -> int:
27
+ import os
28
+
29
+ raw = os.environ.get(name)
30
+ if raw is None:
31
+ return default
32
+ try:
33
+ value = int(raw)
34
+ except ValueError:
35
+ return default
36
+ return max(1, value)
37
+
38
+
39
+ def _non_negative_float_from_env(name: str, default: float) -> float:
40
+ import os
41
+
42
+ raw = os.environ.get(name)
43
+ if raw is None:
44
+ return default
45
+ try:
46
+ value = float(raw)
47
+ except ValueError:
48
+ return default
49
+ return max(0.0, value)
50
+
51
+
52
+ def cooperative_cpu_yield_settings_from_env(
53
+ *,
54
+ default_enabled: bool,
55
+ default_every: int = 2,
56
+ default_seconds: float = 0.0025,
57
+ ) -> CooperativeCpuYieldSettings:
58
+ import os
59
+
60
+ enabled = default_enabled and os.environ.get("MEDNOTES_CPU_YIELD_DISABLED") != "1"
61
+ return CooperativeCpuYieldSettings(
62
+ enabled=enabled,
63
+ every=_positive_int_from_env("MEDNOTES_CPU_YIELD_EVERY", default_every),
64
+ seconds=_non_negative_float_from_env("MEDNOTES_CPU_YIELD_SECONDS", default_seconds),
65
+ )
66
+
67
+
68
+ @contextmanager
69
+ def cooperative_cpu_yield_scope(
70
+ *,
71
+ enabled: bool,
72
+ every: int = 32,
73
+ seconds: float = 0.005,
74
+ sleep: SleepFn = time.sleep,
75
+ ) -> Iterator[None]:
76
+ enabled_token = _CPU_YIELD_ENABLED.set(enabled)
77
+ every_token = _CPU_YIELD_EVERY.set(max(1, int(every or 1)))
78
+ seconds_token = _CPU_YIELD_SECONDS.set(max(0.0, float(seconds or 0.0)))
79
+ sleep_token = _CPU_YIELD_SLEEP.set(sleep)
80
+ try:
81
+ yield
82
+ finally:
83
+ _CPU_YIELD_SLEEP.reset(sleep_token)
84
+ _CPU_YIELD_SECONDS.reset(seconds_token)
85
+ _CPU_YIELD_EVERY.reset(every_token)
86
+ _CPU_YIELD_ENABLED.reset(enabled_token)
87
+
88
+
89
+ def cooperative_cpu_yield(index: int) -> None:
90
+ if not _CPU_YIELD_ENABLED.get():
91
+ return
92
+ seconds = _CPU_YIELD_SECONDS.get()
93
+ if seconds <= 0:
94
+ return
95
+ every = _CPU_YIELD_EVERY.get()
96
+ if index > 0 and index % every == 0:
97
+ _CPU_YIELD_SLEEP.get()(seconds)
@@ -0,0 +1,6 @@
1
+ """Shared kernel — generic workflow/FSM building blocks (DDD shared kernel).
2
+
3
+ Domain-free: no bounded-context concept lives here. The kernel is the low layer;
4
+ domains import the kernel, never the reverse.
5
+ Enforced by tools/audit/import_layering.py.
6
+ """
@@ -0,0 +1,336 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Sequence
4
+ from typing import Literal
5
+
6
+ from pydantic import Field, ValidationInfo, field_validator, model_validator
7
+
8
+ from mednotes.kernel.base import ContractModel, JsonObject, JsonObjectAdapter
9
+ from mednotes.kernel.effects import WorkflowEffect, WorkflowEffectKind
10
+ from mednotes.kernel.progress import WorkflowProgressStatus, WorkflowProgressViewModel
11
+ from mednotes.kernel.state_machine import WorkflowStateMachineSnapshot
12
+
13
+ AgentDirectiveStatus = Literal[
14
+ "running",
15
+ "waiting_agent",
16
+ "waiting_external",
17
+ "waiting_human",
18
+ "blocked",
19
+ "failed",
20
+ "completed",
21
+ "completed_with_warnings",
22
+ ]
23
+
24
+ AGENT_DIRECTIVE_SCHEMA = "agent-directive.v1"
25
+
26
+
27
+ class AgentCapabilities(ContractModel):
28
+ continue_: bool = Field(False, alias="continue")
29
+ final_report: bool = False
30
+
31
+
32
+ class AgentLimits(ContractModel):
33
+ raw_content: bool = False
34
+ absolute_paths: bool = False
35
+ ad_hoc_scripts: bool = False
36
+
37
+
38
+ class AgentEffect(ContractModel):
39
+ """Redacted executable effect projection for hooks and agent automation.
40
+
41
+ The directive is the public FSM -> agent contract. It exposes enough typed
42
+ effect identity to continue safely, without making hooks reconstruct work
43
+ from diagnostic evidence or opaque adapter payload fields.
44
+ """
45
+
46
+ effect_id: str = ""
47
+ kind: WorkflowEffectKind
48
+ target: str = ""
49
+ origin_state: str = ""
50
+ resume_action: str = ""
51
+ mutates_resources: bool = False
52
+ no_resource_mutation: bool = False
53
+ rollback_declared: bool = False
54
+ requires_receipt: bool = True
55
+ requires_attestation: bool = False
56
+ model_policy: JsonObject = Field(default_factory=dict)
57
+ metadata: JsonObject = Field(default_factory=dict)
58
+ payload_schema: str = ""
59
+ payload: JsonObject = Field(default_factory=dict)
60
+
61
+
62
+ class AgentReportDirective(ContractModel):
63
+ requires: list[str] = Field(default_factory=list)
64
+
65
+
66
+ class AgentDirectiveControl(ContractModel):
67
+ status: AgentDirectiveStatus
68
+ state: str = Field(min_length=1, pattern=r"\S")
69
+ reason: str = ""
70
+ phase: str = ""
71
+ capabilities: AgentCapabilities = Field(default_factory=AgentCapabilities)
72
+ effects: list[AgentEffect] = Field(default_factory=list)
73
+ blockers: list[str] = Field(default_factory=list)
74
+ resume: str = ""
75
+ report: AgentReportDirective = Field(default_factory=AgentReportDirective)
76
+ limits: AgentLimits = Field(default_factory=AgentLimits)
77
+
78
+ @field_validator("state")
79
+ @classmethod
80
+ def _state_must_be_identity_text(cls, value: str, info: ValidationInfo) -> str:
81
+ cleaned = value.strip()
82
+ if not cleaned:
83
+ raise ValueError(f"{info.field_name} must be non-empty")
84
+ return cleaned
85
+
86
+ @field_validator("blockers")
87
+ @classmethod
88
+ def _blockers_must_be_text(cls, value: list[str]) -> list[str]:
89
+ return [item.strip() for item in value if item.strip()]
90
+
91
+ @model_validator(mode="after")
92
+ def _status_shape(self) -> AgentDirectiveControl:
93
+ if self.status == "waiting_agent":
94
+ if not self.capabilities.continue_:
95
+ raise ValueError("waiting_agent requires control.capabilities.continue=true")
96
+ if self.capabilities.final_report:
97
+ raise ValueError("waiting_agent requires control.capabilities.final_report=false")
98
+ if not self.effects:
99
+ raise ValueError("waiting_agent requires agent_directive.control.effects")
100
+ if self.status in {"completed", "completed_with_warnings"}:
101
+ if not self.capabilities.final_report:
102
+ raise ValueError("completed directive requires control.capabilities.final_report=true")
103
+ if self.status in {"waiting_human", "waiting_external", "blocked", "failed"}:
104
+ if not self.blockers and not self.resume.strip():
105
+ raise ValueError(f"{self.status} directive requires blockers or resume")
106
+ return self
107
+
108
+
109
+ class AgentDirective(ContractModel):
110
+ # The kernel is framework-only, so its default schema is neutral. Product
111
+ # workflows must pass their public schema explicitly at the projection edge.
112
+ schema_: str = Field(AGENT_DIRECTIVE_SCHEMA, alias="schema")
113
+ workflow: str = Field(min_length=1, pattern=r"\S")
114
+ run_id: str = Field(min_length=1, pattern=r"\S")
115
+ control: AgentDirectiveControl
116
+ summary: str = ""
117
+ instructions: list[str] = Field(default_factory=list)
118
+
119
+ @field_validator("workflow", "run_id")
120
+ @classmethod
121
+ def _identity_fields_must_be_text(cls, value: str, info: ValidationInfo) -> str:
122
+ cleaned = value.strip()
123
+ if not cleaned:
124
+ raise ValueError(f"{info.field_name} must be non-empty")
125
+ return cleaned
126
+
127
+ @field_validator("instructions")
128
+ @classmethod
129
+ def _instructions_are_plain_text(cls, value: list[str]) -> list[str]:
130
+ cleaned: list[str] = []
131
+ for line in value:
132
+ text = line.strip()
133
+ if not text:
134
+ continue
135
+ if text.casefold().startswith("agent_instruction:"):
136
+ raise ValueError("AgentDirective instructions must not include agent_instruction prefix")
137
+ cleaned.append(text)
138
+ return cleaned
139
+
140
+
141
+ def _json_str_field(payload: JsonObject, key: str) -> str:
142
+ value = payload[key] if key in payload else ""
143
+ return value if isinstance(value, str) else ""
144
+
145
+
146
+ def assert_agent_directive_matches_progress(
147
+ directive: AgentDirective,
148
+ *,
149
+ workflow: str,
150
+ run_id: str,
151
+ progress_view_model: WorkflowProgressViewModel,
152
+ snapshot: WorkflowStateMachineSnapshot,
153
+ allowed_effect_kinds: set[WorkflowEffectKind],
154
+ label: str,
155
+ ) -> None:
156
+ """Assert the agent-facing route is a projection of the current FSM state.
157
+
158
+ The directive is executable by hooks/subagents, so it cannot be validated as
159
+ a standalone object. It must agree with the current StateChart leaf, progress
160
+ model and effect policy; otherwise it becomes a parallel state channel.
161
+ """
162
+
163
+ if directive.workflow != workflow:
164
+ raise ValueError(f"{label} agent_directive workflow must match workflow")
165
+ if directive.run_id != run_id or directive.run_id != snapshot.run_id or directive.run_id != progress_view_model.run_id:
166
+ raise ValueError(f"{label} agent_directive run_id must match progress and snapshot")
167
+ if progress_view_model.workflow != workflow or snapshot.workflow != workflow:
168
+ raise ValueError(f"{label} progress and snapshot workflow must match workflow")
169
+ if directive.control.status != progress_view_model.status.value:
170
+ raise ValueError(f"{label} agent_directive status must match progress view status")
171
+ if progress_view_model.status.value != snapshot.current_category.value:
172
+ raise ValueError(f"{label} progress status must match state_machine_snapshot category")
173
+ if directive.control.state != snapshot.current_state:
174
+ raise ValueError(f"{label} agent_directive state must match current StateChart state")
175
+ if progress_view_model.state != snapshot.current_state:
176
+ raise ValueError(f"{label} progress state must match current StateChart state")
177
+ if directive.control.capabilities.continue_ != progress_view_model.can_continue_now:
178
+ raise ValueError(f"{label} agent_directive continue capability must match progress view")
179
+ final_report = progress_view_model.status in {
180
+ WorkflowProgressStatus.COMPLETED,
181
+ WorkflowProgressStatus.COMPLETED_WITH_WARNINGS,
182
+ }
183
+ if directive.control.capabilities.final_report != final_report:
184
+ raise ValueError(f"{label} agent_directive final_report capability must match progress status")
185
+ for effect in directive.control.effects:
186
+ if effect.origin_state != snapshot.current_state:
187
+ raise ValueError(f"{label} agent_directive effect origin_state must match current state")
188
+ if effect.kind not in allowed_effect_kinds:
189
+ raise ValueError(f"{label} agent_directive effect kind is not allowed for current state")
190
+
191
+
192
+ def agent_directive_from_progress_view_model(
193
+ view_model: WorkflowProgressViewModel,
194
+ *,
195
+ schema: str = AGENT_DIRECTIVE_SCHEMA,
196
+ reason: str,
197
+ report_requires: list[str],
198
+ summary: str,
199
+ effects: Sequence[WorkflowEffect | AgentEffect | JsonObject] | None = None,
200
+ blockers: list[str] | None = None,
201
+ resume: str = "",
202
+ instructions: list[str] | None = None,
203
+ effect_payload_projector: Callable[[object], JsonObject] | None = None,
204
+ ) -> AgentDirective:
205
+ status = _directive_status(view_model.status)
206
+ final_report = status in {"completed", "completed_with_warnings"}
207
+ required_reason = _required_directive_text(reason, field_name="reason")
208
+ required_summary = _required_directive_text(summary, field_name="summary")
209
+ required_report_requires = _required_directive_report_requires(report_requires)
210
+ control = {
211
+ "status": status,
212
+ "state": view_model.state,
213
+ "phase": view_model.phase,
214
+ "reason": required_reason,
215
+ "capabilities": {
216
+ "continue": view_model.can_continue_now,
217
+ "final_report": final_report,
218
+ },
219
+ "effects": [
220
+ _project_agent_effect(
221
+ effect,
222
+ effect_payload_projector=effect_payload_projector or _default_effect_payload_projector,
223
+ ).to_payload()
224
+ for effect in effects or []
225
+ ],
226
+ "blockers": blockers or [],
227
+ "resume": resume or view_model.resume_action or "",
228
+ "report": {"requires": required_report_requires},
229
+ "limits": {
230
+ "raw_content": False,
231
+ "absolute_paths": False,
232
+ "ad_hoc_scripts": False,
233
+ },
234
+ }
235
+ return AgentDirective.model_validate(
236
+ {
237
+ "schema": schema,
238
+ "workflow": view_model.workflow,
239
+ "run_id": view_model.run_id,
240
+ "control": control,
241
+ "summary": required_summary,
242
+ "instructions": instructions or _directive_instructions(status),
243
+ }
244
+ )
245
+
246
+
247
+ def _project_agent_effect(
248
+ effect: WorkflowEffect | AgentEffect | JsonObject,
249
+ *,
250
+ effect_payload_projector: Callable[[object], JsonObject],
251
+ ) -> AgentEffect:
252
+ if isinstance(effect, AgentEffect):
253
+ return effect
254
+ if isinstance(effect, WorkflowEffect):
255
+ payload = effect_payload_projector(effect.payload)
256
+ return AgentEffect.model_validate(
257
+ {
258
+ "effect_id": effect.effect_id,
259
+ "kind": effect.kind,
260
+ "target": effect.target,
261
+ "origin_state": effect.origin_state,
262
+ "resume_action": effect.resume_action,
263
+ "mutates_resources": effect.mutates_resources,
264
+ "no_resource_mutation": effect.no_resource_mutation,
265
+ "rollback_declared": effect.rollback_declared,
266
+ "requires_receipt": effect.requires_receipt,
267
+ "requires_attestation": effect.requires_attestation,
268
+ "model_policy": effect.model_policy,
269
+ "metadata": effect.metadata,
270
+ "payload_schema": _json_str_field(payload, "schema"),
271
+ "payload": payload,
272
+ }
273
+ )
274
+ return AgentEffect.model_validate(effect)
275
+
276
+
277
+ def _default_effect_payload_projector(value: object) -> JsonObject:
278
+ """Validate effect payloads without applying domain-specific redaction."""
279
+
280
+ return JsonObjectAdapter.validate_python(value)
281
+
282
+
283
+ def _directive_status(status: WorkflowProgressStatus) -> str:
284
+ match status:
285
+ case WorkflowProgressStatus.RUNNING:
286
+ return "running"
287
+ case WorkflowProgressStatus.WAITING_AGENT:
288
+ return "waiting_agent"
289
+ case WorkflowProgressStatus.WAITING_EXTERNAL:
290
+ return "waiting_external"
291
+ case WorkflowProgressStatus.WAITING_HUMAN:
292
+ return "waiting_human"
293
+ case WorkflowProgressStatus.BLOCKED:
294
+ return "blocked"
295
+ case WorkflowProgressStatus.FAILED:
296
+ return "failed"
297
+ case WorkflowProgressStatus.COMPLETED:
298
+ return "completed"
299
+ case WorkflowProgressStatus.COMPLETED_WITH_WARNINGS:
300
+ return "completed_with_warnings"
301
+ case WorkflowProgressStatus.IDLE:
302
+ raise ValueError("idle progress cannot produce agent directive")
303
+
304
+
305
+ def _directive_instructions(status: str) -> list[str]:
306
+ match status:
307
+ case "running":
308
+ return ["aguarde o resultado final do workflow antes de concluir."]
309
+ case "waiting_agent":
310
+ return ["continue pela rota oficial antes do relatorio final."]
311
+ case "waiting_external":
312
+ return ["reporte o progresso parcial e aguarde a condicao externa."]
313
+ case "waiting_human":
314
+ return ["peca a decisao humana pelo pacote oficial antes de continuar."]
315
+ case "blocked":
316
+ return ["reporte o bloqueio literal; nao declare sucesso do workflow."]
317
+ case "failed":
318
+ return ["reporte a falha literal e o contexto de erro."]
319
+ case "completed" | "completed_with_warnings":
320
+ return ["escreva o relatorio final usando os relatorios oficiais."]
321
+ case _:
322
+ raise ValueError(f"unsupported directive status: {status}")
323
+
324
+
325
+ def _required_directive_text(value: str, *, field_name: str) -> str:
326
+ cleaned = value.strip()
327
+ if not cleaned:
328
+ raise ValueError(f"{field_name} must be non-empty")
329
+ return cleaned
330
+
331
+
332
+ def _required_directive_report_requires(value: list[str]) -> list[str]:
333
+ cleaned = [item.strip() for item in value if item.strip()]
334
+ if not cleaned:
335
+ raise ValueError("report_requires must be non-empty")
336
+ return cleaned