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.
- package/.opencode/agents/med-chat-triager.md +204 -0
- package/.opencode/agents/med-flashcard-maker.md +63 -0
- package/.opencode/agents/med-knowledge-architect.md +230 -0
- package/.opencode/agents/med-link-graph-curator.md +177 -0
- package/.opencode/agents/med-publish-guard.md +62 -0
- package/.opencode/commands/flashcards.md +25 -0
- package/.opencode/commands/mednotes/create.md +25 -0
- package/.opencode/commands/mednotes/enrich.md +27 -0
- package/.opencode/commands/mednotes/fix-wiki.md +27 -0
- package/.opencode/commands/mednotes/history.md +22 -0
- package/.opencode/commands/mednotes/link-body.md +25 -0
- package/.opencode/commands/mednotes/link-related.md +27 -0
- package/.opencode/commands/mednotes/link.md +27 -0
- package/.opencode/commands/mednotes/pdf-library.md +27 -0
- package/.opencode/commands/mednotes/process-chats.md +23 -0
- package/.opencode/commands/mednotes/setup.md +21 -0
- package/.opencode/commands/mednotes/status.md +27 -0
- package/.opencode/commands/mednotes/telemetry.md +27 -0
- package/.opencode/commands/report.md +26 -0
- package/.opencode/mednotes/AGENTS.md +57 -0
- package/.opencode/mednotes/agents/med-chat-triager.md +197 -0
- package/.opencode/mednotes/agents/med-flashcard-maker.md +56 -0
- package/.opencode/mednotes/agents/med-knowledge-architect.md +224 -0
- package/.opencode/mednotes/agents/med-link-graph-curator.md +171 -0
- package/.opencode/mednotes/agents/med-publish-guard.md +55 -0
- package/.opencode/mednotes/contracts/.gitkeep +1 -0
- package/.opencode/mednotes/contracts/agents.json +116 -0
- package/.opencode/mednotes/contracts/opencode-plugin.json +70 -0
- package/.opencode/mednotes/docs/agent-prompt-hardening.md +567 -0
- package/.opencode/mednotes/docs/agent-role-contracts.md +94 -0
- package/.opencode/mednotes/docs/anki-mcp-twenty-rules.md +214 -0
- package/.opencode/mednotes/docs/anki-templates/README.md +39 -0
- package/.opencode/mednotes/docs/anki-templates/cloze.back.html +23 -0
- package/.opencode/mednotes/docs/anki-templates/cloze.front.html +14 -0
- package/.opencode/mednotes/docs/anki-templates/qa.back.html +24 -0
- package/.opencode/mednotes/docs/anki-templates/qa.front.html +14 -0
- package/.opencode/mednotes/docs/anki-templates/style.css +182 -0
- package/.opencode/mednotes/docs/atomicity-splitting-policy.md +113 -0
- package/.opencode/mednotes/docs/extension-docs.md +40 -0
- package/.opencode/mednotes/docs/flashcard-ingestion.md +278 -0
- package/.opencode/mednotes/docs/knowledge-architect.md +208 -0
- package/.opencode/mednotes/docs/merge-policy.md +110 -0
- package/.opencode/mednotes/docs/public-vocabulary.md +104 -0
- package/.opencode/mednotes/docs/semantic-linker.md +141 -0
- package/.opencode/mednotes/docs/taxonomy-policy.md +90 -0
- package/.opencode/mednotes/docs/triage-policy.md +187 -0
- package/.opencode/mednotes/docs/vault-version-control.md +758 -0
- package/.opencode/mednotes/docs/vocabulary-db-recovery.md +58 -0
- package/.opencode/mednotes/docs/workflow-output-contract.md +779 -0
- package/.opencode/mednotes/hooks/hooks.json +79 -0
- package/.opencode/mednotes/package-lock.json +6361 -0
- package/.opencode/mednotes/package.json +15 -0
- package/.opencode/mednotes/pyproject.toml +48 -0
- package/.opencode/mednotes/scripts/bootstrap_windows_python_uv.cmd +13 -0
- package/.opencode/mednotes/scripts/bootstrap_windows_python_uv.ps1 +172 -0
- package/.opencode/mednotes/scripts/enrich_notes.py +23 -0
- package/.opencode/mednotes/scripts/full_reset_windows_python_uv.cmd +13 -0
- package/.opencode/mednotes/scripts/hooks/antigravity_hook_status.mjs +212 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/antigravity.mjs +169 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/harness_payload.mjs +103 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/opencode_plugin.mjs +341 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/opencode_user_config_sync.mjs +177 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/anki_preflight.mjs +214 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/cli.mjs +143 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/diagnostics.mjs +11 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/domain/agent_directive_core.mjs +160 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/fsm_directive.mjs +1470 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/hook_errors.mjs +120 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/retention.mjs +114 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/runtime.mjs +174 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/telemetry_capture.mjs +511 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/vault_guard.mjs +624 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook.mjs +5 -0
- package/.opencode/mednotes/scripts/mednotes/_runtime_paths.py +24 -0
- package/.opencode/mednotes/scripts/mednotes/anki_model_validator.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/capture_extension_diff.py +1562 -0
- package/.opencode/mednotes/scripts/mednotes/feedback_report.py +16 -0
- package/.opencode/mednotes/scripts/mednotes/flashcard_index.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/flashcard_pipeline.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/flashcard_report.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/flashcard_sources.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/obsidian/README.md +6 -0
- package/.opencode/mednotes/scripts/mednotes/obsidian_note_utils.py +20 -0
- package/.opencode/mednotes/scripts/mednotes/pdf_library/cli.py +16 -0
- package/.opencode/mednotes/scripts/mednotes/project_fsm.py +229 -0
- package/.opencode/mednotes/scripts/mednotes/setup_telemetry_email.py +404 -0
- package/.opencode/mednotes/scripts/mednotes/sync_anki_twenty_rules.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/sync_opencode_user_config.py +36 -0
- package/.opencode/mednotes/scripts/mednotes/wiki/cli.py +20 -0
- package/.opencode/mednotes/scripts/mednotes/wiki_graph.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/wiki_tree.py +134 -0
- package/.opencode/mednotes/scripts/reset_windows_python_uv.ps1 +625 -0
- package/.opencode/mednotes/scripts/run_python.mjs +109 -0
- package/.opencode/mednotes/scripts/vault/vault_commit.ps1 +19 -0
- package/.opencode/mednotes/scripts/vault/vault_commit.sh +18 -0
- package/.opencode/mednotes/scripts/vault/vault_git.ps1 +19 -0
- package/.opencode/mednotes/scripts/vault/vault_git.py +3107 -0
- package/.opencode/mednotes/scripts/vault/vault_git.sh +18 -0
- package/.opencode/mednotes/scripts/vault/vault_precommit.ps1 +19 -0
- package/.opencode/mednotes/scripts/vault/vault_precommit.sh +18 -0
- package/.opencode/mednotes/skills/THIRD_PARTY_NOTICES.md +45 -0
- package/.opencode/mednotes/skills/create-medical-flashcards/SKILL.md +113 -0
- package/.opencode/mednotes/skills/create-medical-note/SKILL.md +90 -0
- package/.opencode/mednotes/skills/enrich-medical-note/SKILL.md +120 -0
- package/.opencode/mednotes/skills/fix-medical-wiki/SKILL.md +559 -0
- package/.opencode/mednotes/skills/link-medical-wiki/SKILL.md +224 -0
- package/.opencode/mednotes/skills/obsidian-cli/SKILL.md +118 -0
- package/.opencode/mednotes/skills/obsidian-markdown/SKILL.md +207 -0
- package/.opencode/mednotes/skills/obsidian-markdown/references/CALLOUTS.md +58 -0
- package/.opencode/mednotes/skills/obsidian-markdown/references/EMBEDS.md +63 -0
- package/.opencode/mednotes/skills/obsidian-markdown/references/PROPERTIES.md +61 -0
- package/.opencode/mednotes/skills/obsidian-ops/SKILL.md +136 -0
- package/.opencode/mednotes/skills/pdf-library/SKILL.md +45 -0
- package/.opencode/mednotes/skills/process-medical-chats/SKILL.md +246 -0
- package/.opencode/mednotes/skills/workflow-report/SKILL.md +100 -0
- package/.opencode/mednotes/src/mednotes/__init__.py +5 -0
- package/.opencode/mednotes/src/mednotes/domains/__init__.py +5 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/README.md +26 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/__init__.py +2 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/build_demo_apkg.py +177 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/contracts.py +385 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/flashcards_machine.py +522 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/fsm.py +817 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/index.py +630 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/install_models.py +445 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/model.py +359 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/obsidian_links.py +135 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/obsidian_note_utils.py +546 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/pipeline.py +580 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/report.py +510 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/sources.py +682 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/sync_rules.py +184 -0
- package/.opencode/mednotes/src/mednotes/domains/history/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/history/history_fsm.py +852 -0
- package/.opencode/mednotes/src/mednotes/domains/history/history_machine.py +453 -0
- package/.opencode/mednotes/src/mednotes/domains/setup/__init__.py +7 -0
- package/.opencode/mednotes/src/mednotes/domains/setup/setup_fsm.py +808 -0
- package/.opencode/mednotes/src/mednotes/domains/setup/setup_machine.py +973 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/README.md +64 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/api.py +668 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/batch_state.py +102 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/atomicity/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/atomicity/atomicity.py +877 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/body_link/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/body_link/body_linker.py +1562 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/effect_adapters.py +949 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/fix_wiki_runtime_adapters.py +433 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/coverage.py +413 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/graph.py +396 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/graph_fixes.py +161 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/hygiene/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/hygiene/hygiene.py +483 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/__init__.py +2 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/anchors.py +185 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/__init__.py +0 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/cache.py +223 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/config.py +131 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/download.py +224 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/frontmatter.py +59 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/insert.py +227 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/local_import.py +54 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/__init__.py +42 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/web_profiles.py +99 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/web_search.py +203 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/wikimedia.py +102 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_db_adapter.mjs +434 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_node_runtime.py +274 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_query.py +227 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/artifacts.py +605 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/canonical_merge.py +277 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/markdown_zones.py +85 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/meaning_planner.py +307 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_iter.py +67 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_merge.py +278 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_plan.py +409 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_policy.py +22 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/__init__.py +79 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/fixes.py +264 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/frontmatter.py +435 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/models.py +208 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/prompts.py +37 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/tables.py +236 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/validate.py +404 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/provenance.py +478 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/raw_chats.py +273 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/sources_backfill.py +235 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/__init__.py +10 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/anchors.py +16 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/captions.py +47 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/cli.py +179 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/cloud.py +52 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/config.py +196 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/context_packets.py +76 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/db.py +81 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/doctor.py +102 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/figure_ids.py +42 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/ingest.py +326 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/insert.py +316 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/mentions.py +57 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/ocr.py +71 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/paths.py +35 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/pdf_engine.py +77 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/schema.py +155 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/search.py +188 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/app.py +89 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/image_backend.py +29 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/state.py +65 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish.py +1139 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish_receipts.py +365 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish_recovery.py +240 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_behavior_corpus.py +2069 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_report_validation.py +4448 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_run_audit.py +852 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/architect_prompt_eval.py +341 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/body_linker_eval.py +240 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/curator_output_validation.py +175 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/curator_prompt_eval.py +865 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/triager_prompt_eval.py +1295 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/related_notes.py +1920 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/related_notes_headless.py +1186 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/plan_attestation.py +148 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_receipts.py +360 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_runtime.py +52 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_task_runner.py +2470 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/style/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/style/style.py +1952 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/subagents/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/subagents/agents.py +1767 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/alias_projection.py +331 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/link_terms.py +151 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/llm_disambiguation.py +182 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/__init__.py +116 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/audit.py +201 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/migration.py +314 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/normalize.py +72 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/policy.py +135 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/resolve.py +413 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/schema.py +157 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/status.py +137 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_bootstrap.py +509 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_curator_batch.py +1115 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_ingestion.py +632 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_map.py +930 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_recovery.py +1388 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/cli.py +6665 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/common.py +69 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/config.py +210 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/__init__.py +74 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agent_report.py +242 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agent_run_audit.py +196 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agents.py +601 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/curator.py +256 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/effect_payloads.py +519 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/happy_path.py +190 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/link_git.py +110 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/link_runtime_artifact.py +52 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/note_plan.py +75 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/paths.py +114 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/public_report.py +53 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/publish.py +111 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/raw_coverage.py +217 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes.py +136 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes_headless.py +153 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes_runtime.py +395 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/schema_registry.py +637 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/specialist.py +432 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/status.py +62 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/style_rewrite.py +568 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/vocabulary_ingestion.py +223 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_blockers.py +510 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_guardrails.py +637 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_outcomes.py +121 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_receipts.py +100 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/__main__.py +4 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/cli.py +275 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/__init__.py +2 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/candidates.py +193 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/cli.py +189 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/gemini.py +220 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/inputs.py +120 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/models.py +34 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/parsing.py +48 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/prompts.py +216 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/quality.py +54 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/reporting.py +24 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/runner.py +433 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/utils.py +39 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/vault_guard_bridge.py +17 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_context_packets.py +454 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_decision_projection.py +133 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_effects.py +1260 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_fsm.py +2768 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_machine.py +1588 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_plan.py +306 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_primary_objective.py +316 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_problem.py +153 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_receipt_evidence.py +306 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_states.py +290 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_user_report.py +342 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/health.py +6332 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_fsm.py +1119 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_git.py +638 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_machine.py +1106 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_retry_governance.py +374 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_runtime_result.py +485 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_triggers.py +183 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/linking.py +2758 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/reference_repair.py +718 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/related_notes_fsm.py +1855 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link_related/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link_related/link_related_machine.py +834 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_fsm.py +1592 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_machine.py +3097 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_primary_objective.py +28 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_runtime_result.py +185 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/performance.py +97 -0
- package/.opencode/mednotes/src/mednotes/kernel/__init__.py +6 -0
- package/.opencode/mednotes/src/mednotes/kernel/agent_directive.py +336 -0
- package/.opencode/mednotes/src/mednotes/kernel/base.py +51 -0
- package/.opencode/mednotes/src/mednotes/kernel/blockers.py +39 -0
- package/.opencode/mednotes/src/mednotes/kernel/effect_executor.py +55 -0
- package/.opencode/mednotes/src/mednotes/kernel/effect_intent.py +69 -0
- package/.opencode/mednotes/src/mednotes/kernel/effects.py +160 -0
- package/.opencode/mednotes/src/mednotes/kernel/errors.py +38 -0
- package/.opencode/mednotes/src/mednotes/kernel/fsm_event.py +35 -0
- package/.opencode/mednotes/src/mednotes/kernel/fsm_model.py +55 -0
- package/.opencode/mednotes/src/mednotes/kernel/fsm_transition_result.py +75 -0
- package/.opencode/mednotes/src/mednotes/kernel/guardrails.py +188 -0
- package/.opencode/mednotes/src/mednotes/kernel/progress.py +319 -0
- package/.opencode/mednotes/src/mednotes/kernel/public_report.py +346 -0
- package/.opencode/mednotes/src/mednotes/kernel/state_machine.py +164 -0
- package/.opencode/mednotes/src/mednotes/kernel/workflow.py +619 -0
- package/.opencode/mednotes/src/mednotes/platform/__init__.py +5 -0
- package/.opencode/mednotes/src/mednotes/platform/backup_policy.py +382 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/__init__.py +62 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/cli.py +275 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/contracts.py +83 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/core.py +4168 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/integrity.py +989 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/operational_contract.py +2293 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/telemetry.py +875 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/telemetry_config.py +65 -0
- package/.opencode/mednotes/src/mednotes/platform/opencode_runtime_config.py +182 -0
- package/.opencode/mednotes/src/mednotes/platform/paths/__init__.py +1560 -0
- package/.opencode/mednotes/src/mednotes/platform/secrets.py +89 -0
- package/.opencode/mednotes/src/mednotes/platform/user_config.py +103 -0
- package/.opencode/mednotes/src/mednotes/platform/vault_guard.py +214 -0
- package/.opencode/mednotes/uv.lock +932 -0
- package/.opencode/mednotes.generated.json +395 -0
- package/.opencode/opencode.json +31 -0
- package/.opencode/plugins/mednotes-fsm.mjs +7 -0
- package/.opencode/plugins/mednotes_hook/adapters/antigravity.mjs +169 -0
- package/.opencode/plugins/mednotes_hook/adapters/harness_payload.mjs +103 -0
- package/.opencode/plugins/mednotes_hook/adapters/opencode_plugin.mjs +341 -0
- package/.opencode/plugins/mednotes_hook/adapters/opencode_user_config_sync.mjs +177 -0
- package/.opencode/plugins/mednotes_hook/anki_preflight.mjs +214 -0
- package/.opencode/plugins/mednotes_hook/cli.mjs +143 -0
- package/.opencode/plugins/mednotes_hook/diagnostics.mjs +11 -0
- package/.opencode/plugins/mednotes_hook/domain/agent_directive_core.mjs +160 -0
- package/.opencode/plugins/mednotes_hook/fsm_directive.mjs +1470 -0
- package/.opencode/plugins/mednotes_hook/hook_errors.mjs +120 -0
- package/.opencode/plugins/mednotes_hook/retention.mjs +114 -0
- package/.opencode/plugins/mednotes_hook/runtime.mjs +174 -0
- package/.opencode/plugins/mednotes_hook/telemetry_capture.mjs +511 -0
- package/.opencode/plugins/mednotes_hook/vault_guard.mjs +624 -0
- package/AGENTS.md +57 -0
- package/README.md +194 -0
- package/adapters/antigravity/agents.json +80 -0
- package/adapters/antigravity/templates/med-chat-triager.md +214 -0
- package/adapters/antigravity/templates/med-flashcard-maker.md +72 -0
- package/adapters/antigravity/templates/med-knowledge-architect.md +241 -0
- package/adapters/antigravity/templates/med-link-graph-curator.md +187 -0
- package/adapters/antigravity/templates/med-publish-guard.md +71 -0
- package/adapters/gemini-cli/gemini-extension.json +14 -0
- package/adapters/gemini-cli/package.json +15 -0
- package/adapters/gemini-cli/pyproject.toml +48 -0
- package/bin/mednotes-opencode.mjs +155 -0
- package/contracts/agents.json +116 -0
- package/core/agents/med-chat-triager.md +197 -0
- package/core/agents/med-flashcard-maker.md +56 -0
- package/core/agents/med-knowledge-architect.md +224 -0
- package/core/agents/med-link-graph-curator.md +171 -0
- package/core/agents/med-publish-guard.md +55 -0
- package/core/commands/flashcards.toml +22 -0
- package/core/commands/mednotes/create.toml +22 -0
- package/core/commands/mednotes/enrich.toml +24 -0
- package/core/commands/mednotes/fix-wiki.toml +24 -0
- package/core/commands/mednotes/history.toml +19 -0
- package/core/commands/mednotes/link-body.toml +22 -0
- package/core/commands/mednotes/link-related.toml +24 -0
- package/core/commands/mednotes/link.toml +24 -0
- package/core/commands/mednotes/pdf-library.toml +24 -0
- package/core/commands/mednotes/process-chats.toml +20 -0
- package/core/commands/mednotes/setup.toml +18 -0
- package/core/commands/mednotes/status.toml +24 -0
- package/core/commands/mednotes/telemetry.toml +24 -0
- package/core/commands/report.toml +23 -0
- package/core/skills/THIRD_PARTY_NOTICES.md +45 -0
- package/core/skills/create-medical-flashcards/SKILL.md +113 -0
- package/core/skills/create-medical-note/SKILL.md +90 -0
- package/core/skills/enrich-medical-note/SKILL.md +120 -0
- package/core/skills/fix-medical-wiki/SKILL.md +559 -0
- package/core/skills/link-medical-wiki/SKILL.md +224 -0
- package/core/skills/obsidian-cli/SKILL.md +118 -0
- package/core/skills/obsidian-markdown/SKILL.md +207 -0
- package/core/skills/obsidian-markdown/references/CALLOUTS.md +58 -0
- package/core/skills/obsidian-markdown/references/EMBEDS.md +63 -0
- package/core/skills/obsidian-markdown/references/PROPERTIES.md +61 -0
- package/core/skills/obsidian-ops/SKILL.md +136 -0
- package/core/skills/pdf-library/SKILL.md +45 -0
- package/core/skills/process-medical-chats/SKILL.md +246 -0
- package/core/skills/workflow-report/SKILL.md +100 -0
- package/package.json +45 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"""Domain-agnostic public report contract for FSM-first workflows.
|
|
2
|
+
|
|
3
|
+
The FSM owns the state and emits one human-visible wording channel under
|
|
4
|
+
``reports.public_report``. Adapters, hooks and agents may display this model,
|
|
5
|
+
but they must not infer workflow state from legacy text fields.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from typing import Literal
|
|
11
|
+
|
|
12
|
+
from pydantic import ConfigDict, Field, StrictBool, StrictStr, field_validator
|
|
13
|
+
|
|
14
|
+
from mednotes.kernel.base import ContractModel, JsonObject
|
|
15
|
+
from mednotes.kernel.progress import WorkflowProgressState, WorkflowProgressStatus, WorkflowProgressViewModel
|
|
16
|
+
|
|
17
|
+
_PUBLIC_REPORT_INTERNAL_PATTERNS = tuple(
|
|
18
|
+
re.compile(pattern, re.IGNORECASE)
|
|
19
|
+
for pattern in (
|
|
20
|
+
r"--json\b",
|
|
21
|
+
r"--dry-run\b",
|
|
22
|
+
r"\bdry-run\b",
|
|
23
|
+
r"\bschema\b",
|
|
24
|
+
r"\breceipt\b",
|
|
25
|
+
r"\brecibo\b",
|
|
26
|
+
r"sha256:",
|
|
27
|
+
r"(?<!\w)hash(?!\w)",
|
|
28
|
+
r"/Users/",
|
|
29
|
+
r"~/\.[\w-]+",
|
|
30
|
+
r"\b[\w./-]+/cli\.py\b",
|
|
31
|
+
r"\b[\w:/.-]+\s+--[\w-]+",
|
|
32
|
+
r"decision\.reason_code",
|
|
33
|
+
r"human_decision_packet",
|
|
34
|
+
r"next_action",
|
|
35
|
+
r"run_id",
|
|
36
|
+
r"guard_lease",
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _public_report_text_without_internal_terms(value: str, *, field_name: str) -> str:
|
|
42
|
+
"""Keep the human-visible channel free of automation/debug vocabulary."""
|
|
43
|
+
|
|
44
|
+
for pattern in _PUBLIC_REPORT_INTERNAL_PATTERNS:
|
|
45
|
+
if pattern.search(value):
|
|
46
|
+
raise ValueError(f"public report {field_name} contains internal term")
|
|
47
|
+
return value
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class WorkflowPublicReport(ContractModel):
|
|
51
|
+
"""Canonical human-visible text embedded under ``reports.public_report``.
|
|
52
|
+
|
|
53
|
+
This object intentionally carries no status, phase, mutation count or
|
|
54
|
+
decision flags. Those facts belong to the FSM snapshot, progress model,
|
|
55
|
+
receipt and agent directive; duplicating them here creates a second source
|
|
56
|
+
of truth for human and agent consumers.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
schema_: Literal["workflow.public-report.v1"] = Field(
|
|
60
|
+
"workflow.public-report.v1",
|
|
61
|
+
alias="schema",
|
|
62
|
+
)
|
|
63
|
+
audience: Literal["user"] = "user"
|
|
64
|
+
workflow: StrictStr = Field(min_length=1)
|
|
65
|
+
run_id: StrictStr = Field(min_length=1)
|
|
66
|
+
headline: StrictStr = Field(min_length=1)
|
|
67
|
+
lines: list[StrictStr] = Field(min_length=1)
|
|
68
|
+
|
|
69
|
+
@field_validator("headline", mode="after")
|
|
70
|
+
@classmethod
|
|
71
|
+
def _headline_must_be_clean(cls, value: str) -> str:
|
|
72
|
+
cleaned = value.strip()
|
|
73
|
+
if not cleaned:
|
|
74
|
+
raise ValueError("public report headline must be non-empty")
|
|
75
|
+
return cleaned
|
|
76
|
+
|
|
77
|
+
@field_validator("lines", mode="after")
|
|
78
|
+
@classmethod
|
|
79
|
+
def _lines_must_be_clean(cls, value: list[str]) -> list[str]:
|
|
80
|
+
cleaned = [line.strip() for line in value if line.strip()]
|
|
81
|
+
if not cleaned:
|
|
82
|
+
raise ValueError("public report lines must contain visible text")
|
|
83
|
+
return cleaned
|
|
84
|
+
|
|
85
|
+
def summary_text(self) -> str:
|
|
86
|
+
"""Return the exact text agents may reuse without rephrasing."""
|
|
87
|
+
|
|
88
|
+
return "\n".join(self.lines)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def assert_public_report_has_no_internal_terms(public_report: WorkflowPublicReport) -> None:
|
|
92
|
+
"""Reject default public text that leaks automation/debug vocabulary."""
|
|
93
|
+
|
|
94
|
+
_public_report_text_without_internal_terms(public_report.headline, field_name="headline")
|
|
95
|
+
for line in public_report.lines:
|
|
96
|
+
_public_report_text_without_internal_terms(line, field_name="line")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class WorkflowReports(ContractModel):
|
|
100
|
+
"""Shared reports envelope for FSM-first workflows.
|
|
101
|
+
|
|
102
|
+
``summary`` and ``public_report`` are the common reporting surface. Domain
|
|
103
|
+
projections that need structured evidence for validators can place it in
|
|
104
|
+
``details`` without teaching renderers to infer workflow state from it.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
summary: StrictStr = Field(min_length=1)
|
|
108
|
+
public_report: WorkflowPublicReport
|
|
109
|
+
details: JsonObject = Field(default_factory=dict)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class WorkflowPrimaryObjectiveSummary(ContractModel):
|
|
113
|
+
"""Minimum structured answer to a workflow's user-visible objective.
|
|
114
|
+
|
|
115
|
+
FSM projections place this under
|
|
116
|
+
``reports.details.primary_objective_summary`` so validators can tell the
|
|
117
|
+
difference between "work completed", "preview prepared", and "blocked"
|
|
118
|
+
without parsing human prose or legacy root fields.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
model_config = ConfigDict(extra="ignore", populate_by_name=True, validate_assignment=True)
|
|
122
|
+
|
|
123
|
+
schema_: StrictStr = Field(
|
|
124
|
+
"workflow.primary-objective-summary.v1",
|
|
125
|
+
alias="schema",
|
|
126
|
+
)
|
|
127
|
+
workflow: StrictStr = Field(min_length=1)
|
|
128
|
+
run_id: StrictStr = Field(min_length=1)
|
|
129
|
+
objective: StrictStr = Field(min_length=1)
|
|
130
|
+
completed: StrictBool
|
|
131
|
+
status: StrictStr = Field(min_length=1)
|
|
132
|
+
mutation_state: Literal["changed", "unchanged", "not_applicable"]
|
|
133
|
+
mutation_summary: StrictStr = Field(min_length=1)
|
|
134
|
+
remaining_work_summary: StrictStr = Field(min_length=1)
|
|
135
|
+
next_step_summary: StrictStr = Field(min_length=1)
|
|
136
|
+
blocked_reason: StrictStr = ""
|
|
137
|
+
required_report_items: list[StrictStr] = Field(
|
|
138
|
+
default_factory=lambda: [
|
|
139
|
+
"objective_status",
|
|
140
|
+
"mutation_summary",
|
|
141
|
+
"remaining_work_summary",
|
|
142
|
+
"next_step_summary",
|
|
143
|
+
]
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class FsmFirstPayloadSummary(ContractModel):
|
|
148
|
+
"""Typed feedback/telemetry lens over the canonical FSM payload roots.
|
|
149
|
+
|
|
150
|
+
Feedback records are observability artifacts, not workflow controllers. This
|
|
151
|
+
model lets adapters summarize FSM-first payloads without letting stale
|
|
152
|
+
legacy root fields such as ``status`` or ``next_action`` become a second
|
|
153
|
+
source of truth.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
status: StrictStr = ""
|
|
157
|
+
phase: StrictStr = ""
|
|
158
|
+
blocked_reason: StrictStr = ""
|
|
159
|
+
next_action: StrictStr = ""
|
|
160
|
+
required_inputs: list[StrictStr] = Field(default_factory=list)
|
|
161
|
+
human_decision_required: StrictBool = False
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
def from_payload(cls, payload: JsonObject) -> FsmFirstPayloadSummary:
|
|
165
|
+
progress = _object_field(payload, "progress_view_model")
|
|
166
|
+
snapshot = _object_field(payload, "state_machine_snapshot")
|
|
167
|
+
decision = _object_field(payload, "decision")
|
|
168
|
+
receipt = _object_field(payload, "receipt")
|
|
169
|
+
error_context = _object_field(payload, "error_context")
|
|
170
|
+
human_packet = _object_field(payload, "human_decision_packet")
|
|
171
|
+
human_packets = _array_field(payload, "human_decision_packets")
|
|
172
|
+
required_inputs = (
|
|
173
|
+
_string_list_field(decision, "required_inputs")
|
|
174
|
+
or _string_list_field(receipt, "required_inputs")
|
|
175
|
+
or _string_list_field(error_context, "required_inputs")
|
|
176
|
+
)
|
|
177
|
+
return cls(
|
|
178
|
+
status=_text_field(progress, "status")
|
|
179
|
+
or _text_field(receipt, "status")
|
|
180
|
+
or _text_field(snapshot, "current_category"),
|
|
181
|
+
phase=_text_field(progress, "phase")
|
|
182
|
+
or _text_field(progress, "state")
|
|
183
|
+
or _text_field(snapshot, "current_state")
|
|
184
|
+
or _text_field(snapshot, "current_category"),
|
|
185
|
+
blocked_reason=_text_field(error_context, "blocked_reason")
|
|
186
|
+
or _text_field(error_context, "root_cause")
|
|
187
|
+
or _text_field(decision, "reason_code"),
|
|
188
|
+
next_action=_text_field(decision, "next_action")
|
|
189
|
+
or _text_field(receipt, "next_action")
|
|
190
|
+
or _text_field(progress, "user_action")
|
|
191
|
+
or _text_field(progress, "resume_action"),
|
|
192
|
+
required_inputs=required_inputs,
|
|
193
|
+
human_decision_required=(
|
|
194
|
+
_text_field(decision, "kind") == "ask_human"
|
|
195
|
+
or bool(human_packet)
|
|
196
|
+
or bool(human_packets)
|
|
197
|
+
),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _object_field(source: JsonObject, key: str) -> JsonObject:
|
|
202
|
+
value = source[key] if key in source else {}
|
|
203
|
+
return value if isinstance(value, dict) else {}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _array_field(source: JsonObject, key: str) -> list[object]:
|
|
207
|
+
value = source[key] if key in source else []
|
|
208
|
+
return value if isinstance(value, list) else []
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _text_field(source: JsonObject, key: str) -> str:
|
|
212
|
+
value = source[key] if key in source else ""
|
|
213
|
+
return str(value).strip() if value is not None else ""
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _string_list_field(source: JsonObject, key: str) -> list[str]:
|
|
217
|
+
value = source[key] if key in source else []
|
|
218
|
+
if not isinstance(value, list):
|
|
219
|
+
return []
|
|
220
|
+
return [str(item).strip() for item in value if str(item).strip()]
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class WorkflowPublicDecisionOption(ContractModel):
|
|
224
|
+
"""Human-facing closed option rendered from a technical decision packet."""
|
|
225
|
+
|
|
226
|
+
model_config = ConfigDict(extra="ignore", populate_by_name=True, validate_assignment=True)
|
|
227
|
+
|
|
228
|
+
id: StrictStr = Field(min_length=1)
|
|
229
|
+
label: StrictStr = Field(min_length=1)
|
|
230
|
+
description: StrictStr = ""
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class _HumanDecisionPacketForPublicReport(ContractModel):
|
|
234
|
+
"""Narrow lens over ``human_decision_packet`` for public rendering only."""
|
|
235
|
+
|
|
236
|
+
model_config = ConfigDict(extra="ignore", populate_by_name=True, validate_assignment=True)
|
|
237
|
+
|
|
238
|
+
question: StrictStr = Field(min_length=1)
|
|
239
|
+
options: list[WorkflowPublicDecisionOption] = Field(min_length=1)
|
|
240
|
+
items: list[JsonObject] = Field(default_factory=list)
|
|
241
|
+
resume_action: StrictStr = Field(min_length=1)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class WorkflowPublicDecisionPrompt(ContractModel):
|
|
245
|
+
"""Question/options view shown to the user instead of raw decision fields."""
|
|
246
|
+
|
|
247
|
+
question: StrictStr = Field(min_length=1)
|
|
248
|
+
options: list[WorkflowPublicDecisionOption] = Field(min_length=1)
|
|
249
|
+
affected_items: list[StrictStr] = Field(default_factory=list)
|
|
250
|
+
resume_summary: StrictStr = Field(min_length=1)
|
|
251
|
+
|
|
252
|
+
def summary_lines(self) -> list[str]:
|
|
253
|
+
"""Return readable lines without leaking packet field names."""
|
|
254
|
+
|
|
255
|
+
lines = [self.question]
|
|
256
|
+
lines.extend(f"- {option.label}" for option in self.options)
|
|
257
|
+
if self.affected_items:
|
|
258
|
+
lines.append("Itens afetados: " + ", ".join(self.affected_items))
|
|
259
|
+
lines.append(self.resume_summary)
|
|
260
|
+
return lines
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def public_decision_prompt_from_packet(packet: JsonObject) -> WorkflowPublicDecisionPrompt:
|
|
264
|
+
"""Render a typed human-decision packet as public question/options text."""
|
|
265
|
+
|
|
266
|
+
fields = _HumanDecisionPacketForPublicReport.model_validate(packet)
|
|
267
|
+
affected_items: list[str] = []
|
|
268
|
+
for item in fields.items:
|
|
269
|
+
source = item.get("source")
|
|
270
|
+
if isinstance(source, str) and source.strip():
|
|
271
|
+
affected_items.append(source.strip())
|
|
272
|
+
return WorkflowPublicDecisionPrompt(
|
|
273
|
+
question=fields.question.strip(),
|
|
274
|
+
options=fields.options,
|
|
275
|
+
affected_items=affected_items,
|
|
276
|
+
resume_summary=fields.resume_action.strip(),
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
_INCOMPLETE_STATUSES = {
|
|
281
|
+
WorkflowProgressStatus.RUNNING,
|
|
282
|
+
WorkflowProgressStatus.WAITING_AGENT,
|
|
283
|
+
WorkflowProgressStatus.WAITING_EXTERNAL,
|
|
284
|
+
WorkflowProgressStatus.WAITING_HUMAN,
|
|
285
|
+
WorkflowProgressStatus.BLOCKED,
|
|
286
|
+
WorkflowProgressStatus.FAILED,
|
|
287
|
+
}
|
|
288
|
+
_SUCCESS_ONLY_PUBLIC_MARKERS = (
|
|
289
|
+
"workflow concluido",
|
|
290
|
+
"workflow concluído",
|
|
291
|
+
"concluido com sucesso",
|
|
292
|
+
"concluído com sucesso",
|
|
293
|
+
"conclui com sucesso",
|
|
294
|
+
"concluí com sucesso",
|
|
295
|
+
"nao encontrei bloqueios",
|
|
296
|
+
"não encontrei bloqueios",
|
|
297
|
+
"nenhum bloqueio tecnico restante",
|
|
298
|
+
"nenhum bloqueio técnico restante",
|
|
299
|
+
"sem bloqueios",
|
|
300
|
+
"nada pendente",
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def assert_public_report_matches_progress(
|
|
305
|
+
public_report: WorkflowPublicReport,
|
|
306
|
+
*,
|
|
307
|
+
workflow: str,
|
|
308
|
+
run_id: str,
|
|
309
|
+
progress_view_model: WorkflowProgressViewModel,
|
|
310
|
+
label: str,
|
|
311
|
+
) -> None:
|
|
312
|
+
"""Reject human-facing text that contradicts the FSM progress contract."""
|
|
313
|
+
|
|
314
|
+
if public_report.workflow != workflow or public_report.run_id != run_id:
|
|
315
|
+
raise ValueError(f"{label} reports.public_report must match workflow and run_id")
|
|
316
|
+
if progress_view_model.workflow != workflow or progress_view_model.run_id != run_id:
|
|
317
|
+
raise ValueError(f"{label} progress_view_model must match workflow and run_id")
|
|
318
|
+
assert_public_report_has_no_internal_terms(public_report)
|
|
319
|
+
if progress_view_model.status not in _INCOMPLETE_STATUSES:
|
|
320
|
+
return
|
|
321
|
+
public_text = f"{public_report.headline}\n{public_report.summary_text()}".casefold()
|
|
322
|
+
for marker in _SUCCESS_ONLY_PUBLIC_MARKERS:
|
|
323
|
+
if marker in public_text:
|
|
324
|
+
raise ValueError(f"{label} reports.public_report contradicts incomplete FSM status")
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def public_progress_followup_line(progress: WorkflowProgressState | WorkflowProgressViewModel) -> str:
|
|
328
|
+
"""Render a safe user-facing next-step line from canonical progress state."""
|
|
329
|
+
|
|
330
|
+
match progress.status:
|
|
331
|
+
case WorkflowProgressStatus.RUNNING:
|
|
332
|
+
return "O workflow ainda está em andamento."
|
|
333
|
+
case WorkflowProgressStatus.WAITING_AGENT:
|
|
334
|
+
if progress.can_continue_now:
|
|
335
|
+
return "Vou continuar pela rota oficial antes do relatório final."
|
|
336
|
+
return "Há uma etapa do agente pendente antes de concluir."
|
|
337
|
+
case WorkflowProgressStatus.WAITING_EXTERNAL:
|
|
338
|
+
return "Aguardando uma condição externa antes de retomar com segurança."
|
|
339
|
+
case WorkflowProgressStatus.WAITING_HUMAN:
|
|
340
|
+
return "Preciso da sua escolha antes de continuar com segurança."
|
|
341
|
+
case WorkflowProgressStatus.BLOCKED:
|
|
342
|
+
return "Há um bloqueio que precisa ser resolvido antes de continuar."
|
|
343
|
+
case WorkflowProgressStatus.FAILED:
|
|
344
|
+
return "A execução falhou; vou usar o contexto técnico para orientar a recuperação."
|
|
345
|
+
case _:
|
|
346
|
+
return ""
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
from typing import TYPE_CHECKING, Protocol, TypeVar
|
|
5
|
+
|
|
6
|
+
from pydantic import Field, StrictStr, ValidationInfo, field_validator, model_validator
|
|
7
|
+
|
|
8
|
+
from mednotes.kernel.base import ContractModel, JsonObject
|
|
9
|
+
from mednotes.kernel.effect_intent import WorkflowEffect
|
|
10
|
+
from mednotes.kernel.progress import WorkflowProgressEvent
|
|
11
|
+
from mednotes.kernel.workflow import WorkflowDecision
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from mednotes.kernel.fsm_event import WorkflowEventLike
|
|
15
|
+
from mednotes.kernel.fsm_model import WorkflowModel
|
|
16
|
+
from mednotes.kernel.fsm_transition_result import WorkflowTransitionResult
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class WorkflowStateCategory(StrEnum):
|
|
20
|
+
PREPARING = "preparing"
|
|
21
|
+
RUNNING = "running"
|
|
22
|
+
WAITING_AGENT = "waiting_agent"
|
|
23
|
+
WAITING_EXTERNAL = "waiting_external"
|
|
24
|
+
WAITING_HUMAN = "waiting_human"
|
|
25
|
+
BLOCKED = "blocked"
|
|
26
|
+
FAILED = "failed"
|
|
27
|
+
COMPLETED = "completed"
|
|
28
|
+
COMPLETED_WITH_WARNINGS = "completed_with_warnings"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class WorkflowTransition(ContractModel):
|
|
32
|
+
workflow: StrictStr
|
|
33
|
+
from_state: StrictStr
|
|
34
|
+
to_state: StrictStr
|
|
35
|
+
to_category: WorkflowStateCategory
|
|
36
|
+
trigger: StrictStr
|
|
37
|
+
# Executable effects are canonical WorkflowEffect intents emitted by the FSM.
|
|
38
|
+
# Resource-mutation safety facts belong in receipts, not in transitions.
|
|
39
|
+
effects: list[WorkflowEffect] = Field(default_factory=list)
|
|
40
|
+
progress_events: list[WorkflowProgressEvent] = Field(default_factory=list)
|
|
41
|
+
decision: WorkflowDecision | None = None
|
|
42
|
+
resume_action: str = ""
|
|
43
|
+
allowed_next_triggers: list[str] = Field(default_factory=list)
|
|
44
|
+
|
|
45
|
+
@field_validator("workflow", "from_state", "to_state", "trigger")
|
|
46
|
+
@classmethod
|
|
47
|
+
def _required_text(cls, value: str, info: ValidationInfo) -> str:
|
|
48
|
+
cleaned = value.strip()
|
|
49
|
+
if not cleaned:
|
|
50
|
+
raise ValueError(f"{info.field_name} must be non-empty")
|
|
51
|
+
return cleaned
|
|
52
|
+
|
|
53
|
+
@model_validator(mode="after")
|
|
54
|
+
def _terminal_or_human_wait_requires_decision(self) -> WorkflowTransition:
|
|
55
|
+
if self.to_category in {
|
|
56
|
+
WorkflowStateCategory.WAITING_HUMAN,
|
|
57
|
+
WorkflowStateCategory.BLOCKED,
|
|
58
|
+
WorkflowStateCategory.FAILED,
|
|
59
|
+
} and self.decision is None:
|
|
60
|
+
raise ValueError(f"{self.to_category} transition requires decision")
|
|
61
|
+
if self.to_category == WorkflowStateCategory.WAITING_EXTERNAL and not self.resume_action.strip():
|
|
62
|
+
raise ValueError("waiting_external transition requires resume_action")
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class WorkflowStateMachineSnapshot(ContractModel):
|
|
67
|
+
workflow: StrictStr
|
|
68
|
+
run_id: StrictStr
|
|
69
|
+
current_state: StrictStr
|
|
70
|
+
current_category: WorkflowStateCategory
|
|
71
|
+
transitions: list[WorkflowTransition] = Field(default_factory=list)
|
|
72
|
+
metadata: JsonObject = Field(default_factory=dict)
|
|
73
|
+
|
|
74
|
+
@field_validator("workflow", "run_id", "current_state")
|
|
75
|
+
@classmethod
|
|
76
|
+
def _required_text(cls, value: str, info: ValidationInfo) -> str:
|
|
77
|
+
cleaned = value.strip()
|
|
78
|
+
if not cleaned:
|
|
79
|
+
raise ValueError(f"{info.field_name} must be non-empty")
|
|
80
|
+
return cleaned
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
WorkflowEventT = TypeVar("WorkflowEventT", bound="WorkflowEventLike")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class WorkflowStateChart(Protocol):
|
|
87
|
+
"""Minimum StateChart surface the kernel can wrap without owning the FSM."""
|
|
88
|
+
|
|
89
|
+
model: WorkflowModel
|
|
90
|
+
state_field: str
|
|
91
|
+
|
|
92
|
+
def send(self, event_name: str, **kwargs: object) -> object: ...
|
|
93
|
+
|
|
94
|
+
def category_for_state(self, state: str) -> WorkflowStateCategory: ...
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def send_workflow_event(
|
|
98
|
+
machine: WorkflowStateChart,
|
|
99
|
+
event: WorkflowEventT,
|
|
100
|
+
) -> WorkflowTransitionResult:
|
|
101
|
+
"""Send one typed event through python-statemachine and persist its result."""
|
|
102
|
+
|
|
103
|
+
from mednotes.kernel.fsm_model import WorkflowModel
|
|
104
|
+
from mednotes.kernel.fsm_transition_result import validate_transition_result
|
|
105
|
+
|
|
106
|
+
model = machine.model
|
|
107
|
+
if machine.state_field != WorkflowModel.STATECHART_STATE_FIELD:
|
|
108
|
+
raise ValueError(f"workflow machines must use state_field={WorkflowModel.STATECHART_STATE_FIELD}")
|
|
109
|
+
if not isinstance(event, ContractModel):
|
|
110
|
+
raise TypeError("workflow events must be Pydantic contract models before machine.send")
|
|
111
|
+
if event.workflow != model.workflow or event.run_id != model.run_id:
|
|
112
|
+
raise ValueError("event belongs to a different workflow run")
|
|
113
|
+
if event.current_state != model.state:
|
|
114
|
+
raise ValueError("event.current_state does not match the workflow model state")
|
|
115
|
+
|
|
116
|
+
previous_state = model.state
|
|
117
|
+
try:
|
|
118
|
+
raw_result = machine.send(event.name, workflow_event=event)
|
|
119
|
+
result = extract_single_transition_result(raw_result)
|
|
120
|
+
validate_transition_result_matches_event(result, event)
|
|
121
|
+
validate_transition_result(result, category_for_state=machine.category_for_state)
|
|
122
|
+
|
|
123
|
+
carrier_state = model.state
|
|
124
|
+
if not carrier_state:
|
|
125
|
+
raise ValueError("statechart state is not stable after transition")
|
|
126
|
+
if model.state != result.to_state:
|
|
127
|
+
raise ValueError("public workflow state does not match transition target")
|
|
128
|
+
|
|
129
|
+
model.record_event(event)
|
|
130
|
+
model.record_transition(result)
|
|
131
|
+
except Exception:
|
|
132
|
+
# python-statemachine mutates the configured carrier during send; failed
|
|
133
|
+
# contract validation must not leak a candidate state as public truth.
|
|
134
|
+
object.__setattr__(model, "state", previous_state)
|
|
135
|
+
raise
|
|
136
|
+
return result
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def extract_single_transition_result(raw_result: object) -> WorkflowTransitionResult:
|
|
140
|
+
"""Normalize the callback result shape produced by python-statemachine."""
|
|
141
|
+
|
|
142
|
+
from mednotes.kernel.fsm_transition_result import WorkflowTransitionResult
|
|
143
|
+
|
|
144
|
+
if isinstance(raw_result, WorkflowTransitionResult):
|
|
145
|
+
return raw_result
|
|
146
|
+
if isinstance(raw_result, list) and len(raw_result) == 1 and isinstance(raw_result[0], WorkflowTransitionResult):
|
|
147
|
+
return raw_result[0]
|
|
148
|
+
raise TypeError("workflow callback must return exactly one WorkflowTransitionResult")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def validate_transition_result_matches_event(
|
|
152
|
+
result: WorkflowTransitionResult,
|
|
153
|
+
event: WorkflowEventT,
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Reject callback results that do not describe the event just sent."""
|
|
156
|
+
|
|
157
|
+
if result.workflow != event.workflow:
|
|
158
|
+
raise ValueError("transition workflow does not match event workflow")
|
|
159
|
+
if result.run_id != event.run_id:
|
|
160
|
+
raise ValueError("transition run_id does not match event run_id")
|
|
161
|
+
if result.from_state != event.current_state:
|
|
162
|
+
raise ValueError("transition from_state does not match event.current_state")
|
|
163
|
+
if result.trigger != event.name:
|
|
164
|
+
raise ValueError("transition trigger does not match event.name")
|