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,454 @@
1
+ """Redacted context packets for fix-wiki diagnostics."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from collections.abc import Sequence
6
+ from pathlib import Path
7
+
8
+ from pydantic import ConfigDict, Field
9
+
10
+ from mednotes.domains.wiki.capabilities.notes.raw_chats import atomic_write_text
11
+ from mednotes.domains.wiki.capabilities.vocabulary.taxonomy.schema import canonical_taxonomy_invariants
12
+ from mednotes.kernel.base import ContractModel, JsonObject, JsonObjectAdapter
13
+
14
+ FIX_WIKI_CONTEXT_PACKET_SCHEMA = "medical-notes-workbench.fix-wiki-context-packet.v1"
15
+ _IGNORED_DIR_NAMES = {".git", ".obsidian", "__pycache__"}
16
+
17
+
18
+ class _PacketProjection(ContractModel):
19
+ """Typed read-model for broad upstream artifacts rendered as redacted context."""
20
+
21
+ model_config = ConfigDict(extra="ignore", populate_by_name=True, validate_assignment=True)
22
+
23
+
24
+ class FolderTreeNode(ContractModel):
25
+ """Redacted folder inventory: counts and paths only, never note bodies."""
26
+
27
+ name: str
28
+ path: str
29
+ note_count: int = Field(ge=0)
30
+ empty: bool
31
+ children: list[FolderTreeNode] = Field(default_factory=list)
32
+
33
+
34
+ class ContextProblem(_PacketProjection):
35
+ """Problem projection used by context packets, tolerant of partial fixtures."""
36
+
37
+ domain: str = ""
38
+ code: str = "unknown"
39
+ severity: str = ""
40
+ risk: str = ""
41
+ problem: str = ""
42
+ recommendation: str = ""
43
+ can_autofix: bool = False
44
+ decision_required: bool = False
45
+ status: str = ""
46
+ recommended_action: str = ""
47
+ resolver: str = ""
48
+ context_packet: str = ""
49
+ linker_trigger_after_resolve: bool = False
50
+ evidence: JsonObject = Field(default_factory=dict)
51
+
52
+
53
+ class HistoricalAliasMapping(_PacketProjection):
54
+ alias: str = ""
55
+ canonical: str = ""
56
+
57
+
58
+ class TaxonomyInvariantProjection(_PacketProjection):
59
+ specialty_paths: list[str] = Field(default_factory=list)
60
+ legacy_alias_mappings: list[HistoricalAliasMapping] = Field(default_factory=list)
61
+
62
+
63
+ class TaxonomyPlanProjection(_PacketProjection):
64
+ operations: list[JsonObject] = Field(default_factory=list)
65
+ blocked_items: list[JsonObject] = Field(default_factory=list)
66
+ blocked: list[JsonObject] = Field(default_factory=list)
67
+
68
+
69
+ class LinkerDiagnosisProjection(_PacketProjection):
70
+ status: str = ""
71
+ blocked_reason: str = ""
72
+ blocker_count: int = Field(default=0, ge=0)
73
+ links_planned: int = Field(default=0, ge=0)
74
+ links_rewritten: int = Field(default=0, ge=0)
75
+ diagnosis_path: str = ""
76
+
77
+
78
+ class VocabularyMapDiagnosisProjection(_PacketProjection):
79
+ status: str = ""
80
+ map_hash: str = ""
81
+ pending_semantic_ingestion_count: int = Field(default=0, ge=0)
82
+
83
+
84
+ class LinkTriggerContextProjection(_PacketProjection):
85
+ schema_id: str = Field(default="", alias="schema")
86
+ events: list[JsonObject] = Field(default_factory=list)
87
+ path: str = ""
88
+
89
+
90
+ class LinkerReceiptProjection(_PacketProjection):
91
+ status: str = ""
92
+ receipt_path: str = ""
93
+ changed_files: list[JsonObject | str] = Field(default_factory=list)
94
+
95
+
96
+ def _ignore_path(path: Path) -> bool:
97
+ return (
98
+ path.name.startswith(".")
99
+ or path.name in _IGNORED_DIR_NAMES
100
+ or path.name.endswith(".bak")
101
+ or path.name.endswith(".rewrite")
102
+ or ".bak" in path.name
103
+ or ".rewrite" in path.name
104
+ )
105
+
106
+
107
+ def build_folder_tree(wiki_dir: Path) -> FolderTreeNode:
108
+ def build_node(path: Path, rel: str) -> FolderTreeNode | None:
109
+ if path != wiki_dir and _ignore_path(path):
110
+ return None
111
+ dirs: list[FolderTreeNode] = []
112
+ note_count = 0
113
+ if path.exists():
114
+ for child in sorted(path.iterdir(), key=lambda item: item.name.casefold()):
115
+ if _ignore_path(child):
116
+ continue
117
+ if child.is_dir():
118
+ node = build_node(child, child.relative_to(wiki_dir).as_posix())
119
+ if node is not None:
120
+ dirs.append(node)
121
+ elif child.is_file() and child.suffix == ".md":
122
+ note_count += 1
123
+ return FolderTreeNode(
124
+ name=path.name if path != wiki_dir else wiki_dir.name,
125
+ path=rel,
126
+ note_count=note_count,
127
+ empty=note_count == 0 and not dirs,
128
+ children=dirs,
129
+ )
130
+
131
+ root = build_node(wiki_dir, ".")
132
+ return root or FolderTreeNode(name=wiki_dir.name, path=".", note_count=0, empty=True)
133
+
134
+
135
+ def _render_tree(node: FolderTreeNode, *, indent: int = 0) -> list[str]:
136
+ suffix = " [empty]" if node.empty else ""
137
+ name = node.name or "."
138
+ lines = [" " * indent + f"{name}/{suffix}"]
139
+ for child in node.children:
140
+ lines.extend(_render_tree(child, indent=indent + 1))
141
+ return lines
142
+
143
+
144
+ def render_structure_context_packet(
145
+ *,
146
+ wiki_dir: Path,
147
+ folder_tree: FolderTreeNode | object,
148
+ problems: Sequence[object],
149
+ taxonomy_plan: object | None = None,
150
+ ) -> str:
151
+ tree = FolderTreeNode.model_validate(folder_tree)
152
+ invariants = TaxonomyInvariantProjection.model_validate(canonical_taxonomy_invariants())
153
+ canonical_paths = invariants.specialty_paths
154
+ legacy_aliases = [
155
+ f"{mapping.alias} -> {mapping.canonical}"
156
+ for mapping in invariants.legacy_alias_mappings
157
+ if mapping.alias and mapping.canonical
158
+ ]
159
+ problem_items = _context_problems(problems)
160
+ lines = [
161
+ "# Structure Context Packet",
162
+ "",
163
+ f"wiki_dir: {wiki_dir}",
164
+ "domain: structure",
165
+ "",
166
+ "## Canonical invariants",
167
+ "",
168
+ *(f"- `{path}`" for path in canonical_paths),
169
+ "",
170
+ "## Historical aliases",
171
+ "",
172
+ *(f"- `{alias}`" for alias in legacy_aliases),
173
+ "",
174
+ "## Folder Tree",
175
+ "",
176
+ "```text",
177
+ *_render_tree(tree),
178
+ "```",
179
+ "",
180
+ "## Problems",
181
+ "",
182
+ ]
183
+ if problem_items:
184
+ for problem in problem_items:
185
+ evidence = _problem_evidence(problem)
186
+ evidence_path = _text_field(evidence, "path") or _text_field(evidence, "target")
187
+ lines.append(f"- {problem.code}: {evidence_path}")
188
+ else:
189
+ lines.append("- none")
190
+ plan = _taxonomy_plan(taxonomy_plan)
191
+ operations = plan.operations
192
+ blocked = plan.blocked_items
193
+ lines.extend(["", "## Taxonomy Plan", "", f"- operations: {len(operations)}", f"- blocked: {len(blocked)}"])
194
+ return "\n".join(lines) + "\n"
195
+
196
+
197
+ def _json_object(value: object) -> JsonObject:
198
+ return JsonObjectAdapter.validate_python(value) if isinstance(value, dict) else {}
199
+
200
+
201
+ def _text_field(source: JsonObject, key: str) -> str:
202
+ if key not in source:
203
+ return ""
204
+ return str(source[key])
205
+
206
+
207
+ def _problem_evidence(problem: ContextProblem) -> JsonObject:
208
+ return JsonObjectAdapter.validate_python(problem.evidence)
209
+
210
+
211
+ def _context_problems(problems: Sequence[object]) -> list[ContextProblem]:
212
+ return [ContextProblem.model_validate(problem) for problem in problems]
213
+
214
+
215
+ def _taxonomy_plan(taxonomy_plan: object | None) -> TaxonomyPlanProjection:
216
+ return TaxonomyPlanProjection.model_validate(_json_object(taxonomy_plan))
217
+
218
+
219
+ def _problem_line(problem: ContextProblem) -> str:
220
+ evidence = _problem_evidence(problem)
221
+ evidence_bits: list[str] = []
222
+ for key in ("path", "target", "source", "link_diagnosis_path"):
223
+ value = _text_field(evidence, key)
224
+ if value:
225
+ evidence_bits.append(f"{key}={value}")
226
+ paths = evidence["paths"] if "paths" in evidence else []
227
+ if isinstance(paths, list):
228
+ evidence_bits.append("paths=" + ", ".join(str(item) for item in paths[:8]))
229
+ suffix = f" ({'; '.join(evidence_bits)})" if evidence_bits else ""
230
+ return f"- {problem.code}: {problem.problem}{suffix}"
231
+
232
+
233
+ def _render_generic_context_packet(
234
+ *,
235
+ title: str,
236
+ domain: str,
237
+ wiki_dir: Path,
238
+ problems: Sequence[object],
239
+ extra_sections: JsonObject | None = None,
240
+ ) -> str:
241
+ problem_items = _context_problems(problems)
242
+ lines = [
243
+ f"# {title}",
244
+ "",
245
+ f"wiki_dir: {wiki_dir}",
246
+ f"domain: {domain}",
247
+ "",
248
+ "## Rules",
249
+ "",
250
+ "- This packet is redacted.",
251
+ "- It may include paths, titles, hashes, counts, problem codes and operational summaries.",
252
+ "- It must not include raw clinical Markdown, raw chat text or textual diffs.",
253
+ "",
254
+ "## Problems",
255
+ "",
256
+ ]
257
+ if problem_items:
258
+ lines.extend(_problem_line(problem) for problem in problem_items)
259
+ else:
260
+ lines.append("- none")
261
+ for section, value in (extra_sections or {}).items():
262
+ lines.extend(["", f"## {section}", "", "```json", json.dumps(value, ensure_ascii=False, indent=2), "```"])
263
+ return "\n".join(lines) + "\n"
264
+
265
+
266
+ def _safe_problem(problem: ContextProblem) -> JsonObject:
267
+ payload = problem.to_payload()
268
+ for key in ("severity", "risk", "problem", "recommendation", "status"):
269
+ if not getattr(problem, key):
270
+ payload.pop(key, None)
271
+ for key in ("recommended_action", "resolver", "context_packet", "evidence"):
272
+ if not getattr(problem, key):
273
+ payload.pop(key, None)
274
+ if not problem.can_autofix:
275
+ payload.pop("can_autofix", None)
276
+ if not problem.decision_required:
277
+ payload.pop("decision_required", None)
278
+ if not problem.linker_trigger_after_resolve:
279
+ payload.pop("linker_trigger_after_resolve", None)
280
+ return JsonObjectAdapter.validate_python(payload)
281
+
282
+
283
+ def _write_domain_packet(
284
+ *,
285
+ run_dir: Path,
286
+ wiki_dir: Path,
287
+ domain: str,
288
+ title: str,
289
+ problems: Sequence[object],
290
+ extra_sections: JsonObject | None = None,
291
+ ) -> JsonObject:
292
+ md_path = run_dir / f"{domain.replace('_', '-')}-context-packet.md"
293
+ json_path = run_dir / f"{domain.replace('_', '-')}-context-packet.json"
294
+ problem_items = _context_problems(problems)
295
+ atomic_write_text(
296
+ md_path,
297
+ _render_generic_context_packet(
298
+ title=title,
299
+ domain=domain,
300
+ wiki_dir=wiki_dir,
301
+ problems=problems,
302
+ extra_sections=extra_sections,
303
+ ),
304
+ )
305
+ payload = {
306
+ "schema": FIX_WIKI_CONTEXT_PACKET_SCHEMA,
307
+ "domain": domain,
308
+ "wiki_dir": str(wiki_dir),
309
+ "problem_count": len(problem_items),
310
+ "problems": [_safe_problem(problem) for problem in problem_items],
311
+ "summaries": extra_sections or {},
312
+ }
313
+ for section, value in (extra_sections or {}).items():
314
+ payload[section.lower().replace(" ", "_")] = value
315
+ atomic_write_text(json_path, json.dumps(payload, ensure_ascii=False, indent=2) + "\n")
316
+ prefix = domain
317
+ return {
318
+ f"{prefix}_markdown": str(md_path),
319
+ f"{prefix}_json": str(json_path),
320
+ }
321
+
322
+
323
+ def write_context_packets(
324
+ *,
325
+ run_dir: Path,
326
+ wiki_dir: Path,
327
+ problems: Sequence[object],
328
+ taxonomy_plan: object | None = None,
329
+ vocabulary_map_diagnosis: object | None = None,
330
+ linker_diagnosis: object | None = None,
331
+ link_trigger_context: object | None = None,
332
+ linker_receipt: JsonObject | None = None,
333
+ ) -> JsonObject:
334
+ outputs: JsonObject = {}
335
+ run_dir.mkdir(parents=True, exist_ok=True)
336
+ problem_items = _context_problems(problems)
337
+ if any(problem.domain == "structure" for problem in problem_items):
338
+ tree = build_folder_tree(wiki_dir)
339
+ structure_problems = [problem for problem in problem_items if problem.domain == "structure"]
340
+ md_path = run_dir / "structure-context-packet.md"
341
+ json_path = run_dir / "structure-context-packet.json"
342
+ taxonomy = _taxonomy_plan(taxonomy_plan)
343
+ atomic_write_text(
344
+ md_path,
345
+ render_structure_context_packet(
346
+ wiki_dir=wiki_dir,
347
+ folder_tree=tree,
348
+ problems=structure_problems,
349
+ taxonomy_plan=taxonomy_plan,
350
+ ),
351
+ )
352
+ atomic_write_text(
353
+ json_path,
354
+ json.dumps(
355
+ {
356
+ "schema": FIX_WIKI_CONTEXT_PACKET_SCHEMA,
357
+ "domain": "structure",
358
+ "wiki_dir": str(wiki_dir),
359
+ "folder_tree": tree.to_payload(),
360
+ "problem_count": len(structure_problems),
361
+ "problems": [_safe_problem(problem) for problem in structure_problems],
362
+ "taxonomy_plan_summary": {
363
+ "operation_count": len(taxonomy.operations),
364
+ "blocked_count": len(taxonomy.blocked_items),
365
+ },
366
+ },
367
+ ensure_ascii=False,
368
+ indent=2,
369
+ )
370
+ + "\n",
371
+ )
372
+ outputs["structure_markdown"] = str(md_path)
373
+ outputs["structure_json"] = str(json_path)
374
+ identity_problems = [problem for problem in problem_items if problem.domain == "identity"]
375
+ if identity_problems:
376
+ outputs.update(
377
+ _write_domain_packet(
378
+ run_dir=run_dir,
379
+ wiki_dir=wiki_dir,
380
+ domain="identity",
381
+ title="Identity Context Packet",
382
+ problems=identity_problems,
383
+ extra_sections={
384
+ "Identity Invariants": {
385
+ "one_note_many_meanings": "identity.atomicity.one_note_multiple_meanings",
386
+ "many_notes_one_meaning": "identity.duplication.same_meaning_multiple_notes",
387
+ "canonical_rule": "1 meaning = 1 canonical note",
388
+ }
389
+ },
390
+ )
391
+ )
392
+ content_problems = [problem for problem in problem_items if problem.domain == "content"]
393
+ if content_problems:
394
+ outputs.update(
395
+ _write_domain_packet(
396
+ run_dir=run_dir,
397
+ wiki_dir=wiki_dir,
398
+ domain="content",
399
+ title="Content Context Packet",
400
+ problems=content_problems,
401
+ extra_sections={
402
+ "Rewrite Constraints": {
403
+ "preserve": ["YAML aliases/operational tags/images_*", "footer", "images", "embeds", "code blocks"],
404
+ "rewrite_scope": "Only paths listed in problems[].evidence.path",
405
+ }
406
+ },
407
+ )
408
+ )
409
+ graph_problems = [problem for problem in problem_items if problem.domain == "knowledge_graph"]
410
+ if graph_problems or linker_diagnosis or link_trigger_context or linker_receipt or vocabulary_map_diagnosis:
411
+ linker = LinkerDiagnosisProjection.model_validate(_json_object(linker_diagnosis))
412
+ vocabulary = VocabularyMapDiagnosisProjection.model_validate(_json_object(vocabulary_map_diagnosis))
413
+ trigger = LinkTriggerContextProjection.model_validate(_json_object(link_trigger_context))
414
+ receipt = LinkerReceiptProjection.model_validate(_json_object(linker_receipt))
415
+ linker_summary = {
416
+ "status": linker.status,
417
+ "blocked_reason": linker.blocked_reason,
418
+ "blocker_count": linker.blocker_count,
419
+ "links_planned": linker.links_planned,
420
+ "links_rewritten": linker.links_rewritten,
421
+ "diagnosis_path": linker.diagnosis_path,
422
+ }
423
+ outputs.update(
424
+ _write_domain_packet(
425
+ run_dir=run_dir,
426
+ wiki_dir=wiki_dir,
427
+ domain="knowledge_graph",
428
+ title="Knowledge Graph Context Packet",
429
+ problems=graph_problems,
430
+ extra_sections={
431
+ "Boundary": {
432
+ "owner": "/mednotes:link",
433
+ "fix_wiki_role": "orchestrates linker diagnosis/apply and reports the receipt",
434
+ },
435
+ "Vocabulary Map": {
436
+ "status": vocabulary.status,
437
+ "map_hash": vocabulary.map_hash,
438
+ "pending_semantic_ingestion_count": vocabulary.pending_semantic_ingestion_count,
439
+ },
440
+ "Trigger Context": {
441
+ "schema": trigger.schema_id,
442
+ "event_count": len(trigger.events),
443
+ "path": trigger.path,
444
+ },
445
+ "Linker Diagnosis": linker_summary,
446
+ "Linker Receipt": {
447
+ "status": receipt.status,
448
+ "receipt_path": receipt.receipt_path,
449
+ "files_changed": len(receipt.changed_files),
450
+ },
451
+ },
452
+ )
453
+ )
454
+ return outputs
@@ -0,0 +1,133 @@
1
+ """Typed decision projection for `/mednotes:fix-wiki`.
2
+
3
+ `health.py` owns workflow composition, but human-decision UX must not be
4
+ fabricated from loose blocker dictionaries. This module is the typed domain
5
+ lens between blocker-resolution evidence and the canonical `WorkflowDecision`.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Literal
11
+
12
+ from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr
13
+
14
+ from mednotes.domains.wiki.contracts.workflow_outcomes import DecisionEvidence, RejectedAutomation, WorkflowDecision
15
+ from mednotes.kernel.base import JsonObject, JsonObjectAdapter
16
+
17
+
18
+ class FixWikiBlockerResolutionGroup(BaseModel):
19
+ """One blocker-resolution group that can require a human decision."""
20
+
21
+ model_config = ConfigDict(extra="ignore", strict=True)
22
+
23
+ route: StrictStr = "manual_review"
24
+ reason: StrictStr = "Revisão humana necessária."
25
+ next_action: StrictStr = "Resolver a decisão humana pendente antes de continuar."
26
+ automatic: StrictBool = False
27
+ sample: list[JsonObject] = Field(default_factory=list)
28
+
29
+
30
+ class FixWikiBlockerResolutionPacket(BaseModel):
31
+ """Typed packet emitted by blocker-resolution before UX projection."""
32
+
33
+ model_config = ConfigDict(extra="ignore", strict=True)
34
+
35
+ groups: list[FixWikiBlockerResolutionGroup] = Field(default_factory=list)
36
+
37
+
38
+ DecisionOption = dict[Literal["id", "label"], str]
39
+
40
+
41
+ def project_fix_wiki_human_decision_packets(blocker_resolution: object) -> list[JsonObject]:
42
+ """Project typed blocker groups into human-decision packets.
43
+
44
+ Invalid group shapes raise Pydantic validation errors before any
45
+ `WorkflowDecision` or public UX is built.
46
+ """
47
+
48
+ packet = FixWikiBlockerResolutionPacket.model_validate(blocker_resolution)
49
+ packets: list[JsonObject] = []
50
+ for group in packet.groups:
51
+ if group.automatic:
52
+ continue
53
+ decision = _workflow_decision_for_group(group, options=_options_for_route(group.route))
54
+ packets.append(JsonObjectAdapter.validate_python(decision.to_human_decision_packet()))
55
+ if len(packets) >= 5:
56
+ break
57
+ return packets
58
+
59
+
60
+ def _workflow_decision_for_group(
61
+ group: FixWikiBlockerResolutionGroup,
62
+ *,
63
+ options: list[DecisionOption],
64
+ ) -> WorkflowDecision:
65
+ question = group.reason
66
+ next_action = group.next_action
67
+ return WorkflowDecision(
68
+ kind="ask_human",
69
+ phase="fix_wiki_apply",
70
+ reason_code=group.route,
71
+ public_summary=question,
72
+ developer_summary="fix-wiki reached an editorial/organizational decision that has no safe automatic route.",
73
+ evidence=[
74
+ DecisionEvidence(
75
+ summary=question,
76
+ technical_code=group.route,
77
+ source="fix_wiki",
78
+ candidates=[{"decision_kind": group.route, "sample": group.sample[:5]}],
79
+ risk="Escolha automática pode aplicar rota editorial ou organizacional errada.",
80
+ )
81
+ ],
82
+ rejected_automations=[
83
+ RejectedAutomation(
84
+ kind="auto_fix",
85
+ reason_code="unsafe_editorial_choice",
86
+ reason="Não há correção determinística segura para este blocker.",
87
+ ),
88
+ RejectedAutomation(
89
+ kind="auto_defer",
90
+ reason_code="blocks_fix_wiki",
91
+ reason="Pular a decisão deixa o fix-wiki bloqueado.",
92
+ ),
93
+ RejectedAutomation(
94
+ kind="auto_plan",
95
+ reason_code="plan_needs_choice",
96
+ reason="O plano precisa da escolha antes de aplicar a próxima fase.",
97
+ ),
98
+ ],
99
+ next_action=next_action,
100
+ resume_action=next_action,
101
+ recommended_option_id=options[0]["id"],
102
+ options=options,
103
+ human_decision_kind=group.route,
104
+ )
105
+
106
+
107
+ def _options_for_route(route: str) -> list[DecisionOption]:
108
+ match route:
109
+ case "note_merge_required" | "title_driven_merge_review":
110
+ return [
111
+ {"id": "merge_keep_canonical", "label": "Fundir e manter uma nota canônica"},
112
+ {"id": "rename_split_topics", "label": "Renomear para separar tópicos distintos"},
113
+ ]
114
+ case "taxonomy_review_required":
115
+ return [
116
+ {"id": "choose_taxonomy", "label": "Escolher a taxonomia correta"},
117
+ {"id": "defer_move", "label": "Adiar migração e manter como está"},
118
+ ]
119
+ case "taxonomy_migrate":
120
+ return [
121
+ {"id": "apply_taxonomy", "label": "Autorizar reorganização de pastas"},
122
+ {"id": "review_plan", "label": "Revisar plano antes de mover pastas"},
123
+ ]
124
+ case "io_retry":
125
+ return [
126
+ {"id": "retry_now", "label": "Liberar arquivo e tentar novamente"},
127
+ {"id": "stop_and_inspect", "label": "Parar para inspecionar o bloqueio externo"},
128
+ ]
129
+ case _:
130
+ return [
131
+ {"id": "continue_safely", "label": "Escolher a rota segura sugerida"},
132
+ {"id": "stop_and_review", "label": "Parar e revisar manualmente"},
133
+ ]