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,817 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import ConfigDict, Field, StrictBool, StrictStr, model_validator
|
|
6
|
+
from pydantic.json_schema import SkipJsonSchema
|
|
7
|
+
|
|
8
|
+
from mednotes.domains.flashcards.contracts import FlashcardAcceptedCard, FlashcardsTaggingReceipt, FlashcardWritePlan
|
|
9
|
+
from mednotes.domains.flashcards.flashcards_machine import (
|
|
10
|
+
FlashcardsMachine,
|
|
11
|
+
FlashcardsState,
|
|
12
|
+
ObsidianTaggingCompletedEvent,
|
|
13
|
+
category_for_flashcards_state,
|
|
14
|
+
)
|
|
15
|
+
from mednotes.kernel.agent_directive import (
|
|
16
|
+
AgentDirective,
|
|
17
|
+
agent_directive_from_progress_view_model,
|
|
18
|
+
assert_agent_directive_matches_progress,
|
|
19
|
+
)
|
|
20
|
+
from mednotes.kernel.base import ContractModel, JsonObject, JsonObjectAdapter
|
|
21
|
+
from mednotes.kernel.effects import WorkflowEffectKind
|
|
22
|
+
from mednotes.kernel.fsm_model import WorkflowModel
|
|
23
|
+
from mednotes.kernel.fsm_transition_result import WorkflowTransitionResult
|
|
24
|
+
from mednotes.kernel.progress import (
|
|
25
|
+
WorkflowProgressCounts,
|
|
26
|
+
WorkflowProgressEventType,
|
|
27
|
+
WorkflowProgressState,
|
|
28
|
+
WorkflowProgressStatus,
|
|
29
|
+
WorkflowProgressViewModel,
|
|
30
|
+
build_progress_view_model,
|
|
31
|
+
progress_state_from_view_model,
|
|
32
|
+
)
|
|
33
|
+
from mednotes.kernel.public_report import (
|
|
34
|
+
WorkflowPublicReport,
|
|
35
|
+
WorkflowReports,
|
|
36
|
+
assert_public_report_matches_progress,
|
|
37
|
+
public_progress_followup_line,
|
|
38
|
+
)
|
|
39
|
+
from mednotes.kernel.state_machine import (
|
|
40
|
+
WorkflowStateCategory,
|
|
41
|
+
WorkflowStateMachineSnapshot,
|
|
42
|
+
WorkflowTransition,
|
|
43
|
+
send_workflow_event,
|
|
44
|
+
)
|
|
45
|
+
from mednotes.kernel.workflow import (
|
|
46
|
+
HumanDecisionPacket,
|
|
47
|
+
WorkflowDecision,
|
|
48
|
+
assert_diagnostic_context_evidence_only,
|
|
49
|
+
diagnostic_context_evidence_only,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
FLASHCARDS_WORKFLOW = "/flashcards"
|
|
53
|
+
FLASHCARDS_FSM_SCHEMA = "medical-notes-workbench.flashcards-fsm-result.v1"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class _FlashcardsMachineEventEvidence(ContractModel):
|
|
57
|
+
"""Typed lens over persisted flashcards machine event evidence."""
|
|
58
|
+
|
|
59
|
+
model_config = ConfigDict(extra="ignore")
|
|
60
|
+
|
|
61
|
+
audit_evidence: JsonObject = Field(default_factory=dict)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
FLASHCARDS_ALLOWED_ROOT_KEYS = frozenset(
|
|
65
|
+
{
|
|
66
|
+
"schema",
|
|
67
|
+
"workflow",
|
|
68
|
+
"run_id",
|
|
69
|
+
"state_machine_snapshot",
|
|
70
|
+
"progress_view_model",
|
|
71
|
+
"decision",
|
|
72
|
+
"human_decision_packet",
|
|
73
|
+
"receipt",
|
|
74
|
+
"reports",
|
|
75
|
+
"agent_directive",
|
|
76
|
+
"artifacts",
|
|
77
|
+
"diagnostic_context",
|
|
78
|
+
"error_context",
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
FLASHCARDS_FORBIDDEN_ROOT_KEYS = frozenset(
|
|
82
|
+
{
|
|
83
|
+
"status",
|
|
84
|
+
"phase",
|
|
85
|
+
"blocked_reason",
|
|
86
|
+
"next_action",
|
|
87
|
+
"required_inputs",
|
|
88
|
+
"human_decision_required",
|
|
89
|
+
"workflow_exit_code",
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _json_object_field(payload: JsonObject, key: str) -> JsonObject:
|
|
95
|
+
value = payload[key] if key in payload else {}
|
|
96
|
+
return JsonObjectAdapter.validate_python(value) if isinstance(value, dict) else {}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class FlashcardsPrimaryObjectiveSummary(ContractModel):
|
|
100
|
+
schema_id: Literal["workflow.primary-objective-summary.v1"] = Field(
|
|
101
|
+
default="workflow.primary-objective-summary.v1",
|
|
102
|
+
alias="schema",
|
|
103
|
+
)
|
|
104
|
+
workflow: Literal["/flashcards"] = FLASHCARDS_WORKFLOW
|
|
105
|
+
run_id: StrictStr = Field(min_length=1)
|
|
106
|
+
objective: StrictStr = "Criar flashcards no Anki e marcar as fontes no Obsidian após sucesso real."
|
|
107
|
+
completed: StrictBool
|
|
108
|
+
status: StrictStr = Field(min_length=1)
|
|
109
|
+
mutation_state: Literal["changed", "unchanged", "not_applicable"]
|
|
110
|
+
mutation_summary: StrictStr = Field(min_length=1)
|
|
111
|
+
remaining_work_summary: StrictStr = Field(min_length=1)
|
|
112
|
+
next_step_summary: StrictStr = Field(min_length=1)
|
|
113
|
+
blocked_reason: StrictStr = ""
|
|
114
|
+
required_report_items: list[StrictStr] = Field(
|
|
115
|
+
default_factory=lambda: [
|
|
116
|
+
"objective_status",
|
|
117
|
+
"mutation_summary",
|
|
118
|
+
"remaining_work_summary",
|
|
119
|
+
"next_step_summary",
|
|
120
|
+
]
|
|
121
|
+
)
|
|
122
|
+
preview_only: bool
|
|
123
|
+
created_cards: bool
|
|
124
|
+
created_card_count: int = Field(ge=0)
|
|
125
|
+
processed_source_count: int = Field(ge=0)
|
|
126
|
+
tagged_source_count: int = Field(ge=0)
|
|
127
|
+
obsidian_links_valid: bool
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class FlashcardsReceipt(ContractModel):
|
|
131
|
+
status: WorkflowProgressStatus
|
|
132
|
+
changed_files: list[str] = Field(default_factory=list)
|
|
133
|
+
created_card_count: int = Field(default=0, ge=0)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class FlashcardReportInput(ContractModel):
|
|
137
|
+
accepted_cards: list[FlashcardAcceptedCard] = Field(default_factory=list)
|
|
138
|
+
reports: WorkflowReports | None = None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class FlashcardsArtifacts(ContractModel):
|
|
142
|
+
source_manifest_path: str = ""
|
|
143
|
+
write_plan_path: str = ""
|
|
144
|
+
final_report_path: str = ""
|
|
145
|
+
index_path: str = ""
|
|
146
|
+
dry_run: bool = False
|
|
147
|
+
write_plan: FlashcardWritePlan | None = None
|
|
148
|
+
apply_result: JsonObject = Field(default_factory=dict)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class FlashcardsFsmResult(ContractModel):
|
|
152
|
+
schema_id: Literal["medical-notes-workbench.flashcards-fsm-result.v1"] = Field(
|
|
153
|
+
default=FLASHCARDS_FSM_SCHEMA,
|
|
154
|
+
alias="schema",
|
|
155
|
+
)
|
|
156
|
+
workflow: Literal["/flashcards"] = FLASHCARDS_WORKFLOW
|
|
157
|
+
run_id: str
|
|
158
|
+
state_machine_snapshot: WorkflowStateMachineSnapshot
|
|
159
|
+
progress_state: SkipJsonSchema[WorkflowProgressState]
|
|
160
|
+
progress_view_model: WorkflowProgressViewModel
|
|
161
|
+
decision: WorkflowDecision | None = None
|
|
162
|
+
human_decision_packet: HumanDecisionPacket | None = None
|
|
163
|
+
receipt: FlashcardsReceipt
|
|
164
|
+
reports: WorkflowReports
|
|
165
|
+
agent_directive: JsonObject
|
|
166
|
+
artifacts: FlashcardsArtifacts = Field(default_factory=FlashcardsArtifacts)
|
|
167
|
+
diagnostic_context: JsonObject = Field(default_factory=dict)
|
|
168
|
+
error_context: JsonObject | None = None
|
|
169
|
+
|
|
170
|
+
@model_validator(mode="before")
|
|
171
|
+
@classmethod
|
|
172
|
+
def _hydrate_progress_state_from_public_payload(cls, value: object) -> object:
|
|
173
|
+
"""Accept public payloads where progress_state is intentionally hidden."""
|
|
174
|
+
|
|
175
|
+
if not isinstance(value, dict) or "progress_state" in value or "progress_view_model" not in value:
|
|
176
|
+
return value
|
|
177
|
+
hydrated = dict(value)
|
|
178
|
+
progress_view = WorkflowProgressViewModel.model_validate(value["progress_view_model"])
|
|
179
|
+
hydrated["progress_state"] = progress_state_from_view_model(progress_view).to_payload()
|
|
180
|
+
return hydrated
|
|
181
|
+
|
|
182
|
+
@model_validator(mode="after")
|
|
183
|
+
def _progress_view_model_matches_state(self) -> FlashcardsFsmResult:
|
|
184
|
+
expected = build_progress_view_model(self.progress_state).to_payload()
|
|
185
|
+
if self.progress_view_model.to_payload() != expected:
|
|
186
|
+
raise ValueError("progress_view_model must match progress_state")
|
|
187
|
+
return self
|
|
188
|
+
|
|
189
|
+
def to_payload(self) -> JsonObject:
|
|
190
|
+
payload: JsonObject = {
|
|
191
|
+
"schema": self.schema_id,
|
|
192
|
+
"workflow": self.workflow,
|
|
193
|
+
"run_id": self.run_id,
|
|
194
|
+
"state_machine_snapshot": self.state_machine_snapshot.to_payload(),
|
|
195
|
+
"progress_view_model": self.progress_view_model.to_payload(),
|
|
196
|
+
"decision": self.decision.to_payload() if self.decision is not None else None,
|
|
197
|
+
"human_decision_packet": self.human_decision_packet.to_payload()
|
|
198
|
+
if self.human_decision_packet is not None
|
|
199
|
+
else None,
|
|
200
|
+
"receipt": self.receipt.to_payload(),
|
|
201
|
+
"reports": self.reports.to_payload(),
|
|
202
|
+
"agent_directive": dict(self.agent_directive),
|
|
203
|
+
"artifacts": self.artifacts.to_payload(),
|
|
204
|
+
}
|
|
205
|
+
if self.diagnostic_context:
|
|
206
|
+
payload["diagnostic_context"] = dict(self.diagnostic_context)
|
|
207
|
+
if self.error_context is not None:
|
|
208
|
+
payload["error_context"] = dict(self.error_context)
|
|
209
|
+
payload = JsonObjectAdapter.validate_python(payload)
|
|
210
|
+
assert_flashcards_fsm_payload(payload)
|
|
211
|
+
return payload
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def assert_flashcards_fsm_payload(payload: JsonObject) -> None:
|
|
215
|
+
"""Gate the public `/flashcards` FSM payload against legacy root truth."""
|
|
216
|
+
|
|
217
|
+
payload = JsonObjectAdapter.validate_python(payload)
|
|
218
|
+
legacy_keys = set(payload) & FLASHCARDS_FORBIDDEN_ROOT_KEYS
|
|
219
|
+
if legacy_keys:
|
|
220
|
+
raise ValueError(f"flashcards FSM payload contains legacy root keys: {sorted(legacy_keys)}")
|
|
221
|
+
required_keys = FLASHCARDS_ALLOWED_ROOT_KEYS - {"diagnostic_context", "error_context"}
|
|
222
|
+
missing_keys = required_keys - set(payload)
|
|
223
|
+
if missing_keys:
|
|
224
|
+
raise ValueError(f"flashcards FSM payload missing canonical root keys: {sorted(missing_keys)}")
|
|
225
|
+
unexpected_keys = set(payload) - FLASHCARDS_ALLOWED_ROOT_KEYS
|
|
226
|
+
if unexpected_keys:
|
|
227
|
+
raise ValueError(f"flashcards FSM payload contains unexpected root keys: {sorted(unexpected_keys)}")
|
|
228
|
+
diagnostic_context = _json_object_field(payload, "diagnostic_context")
|
|
229
|
+
assert_diagnostic_context_evidence_only(diagnostic_context)
|
|
230
|
+
if "agent_directive" in diagnostic_context:
|
|
231
|
+
raise ValueError("flashcards FSM diagnostic_context must not contain agent_directive")
|
|
232
|
+
reports = WorkflowReports.model_validate(payload["reports"])
|
|
233
|
+
snapshot = WorkflowStateMachineSnapshot.model_validate(payload["state_machine_snapshot"])
|
|
234
|
+
progress_view_model = WorkflowProgressViewModel.model_validate(payload["progress_view_model"])
|
|
235
|
+
receipt = FlashcardsReceipt.model_validate(payload["receipt"])
|
|
236
|
+
if progress_view_model.status != snapshot.current_category.value:
|
|
237
|
+
raise ValueError("flashcards FSM status must match state_machine_snapshot category")
|
|
238
|
+
if receipt.status != progress_view_model.status:
|
|
239
|
+
raise ValueError("flashcards FSM receipt status must match progress view status")
|
|
240
|
+
assert_public_report_matches_progress(
|
|
241
|
+
reports.public_report,
|
|
242
|
+
workflow=FLASHCARDS_WORKFLOW,
|
|
243
|
+
run_id=str(payload["run_id"]),
|
|
244
|
+
progress_view_model=progress_view_model,
|
|
245
|
+
label="flashcards FSM",
|
|
246
|
+
)
|
|
247
|
+
assert_agent_directive_matches_progress(
|
|
248
|
+
AgentDirective.model_validate(_json_object_field(payload, "agent_directive")),
|
|
249
|
+
workflow=FLASHCARDS_WORKFLOW,
|
|
250
|
+
run_id=str(payload["run_id"]),
|
|
251
|
+
progress_view_model=progress_view_model,
|
|
252
|
+
snapshot=snapshot,
|
|
253
|
+
allowed_effect_kinds=_allowed_agent_effect_kinds_for_category(snapshot.current_category),
|
|
254
|
+
label="flashcards FSM",
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _allowed_agent_effect_kinds_for_category(category: WorkflowStateCategory) -> set[WorkflowEffectKind]:
|
|
259
|
+
"""Flashcards delegates no hidden execution outside its FSM contract."""
|
|
260
|
+
|
|
261
|
+
match category:
|
|
262
|
+
case WorkflowStateCategory.WAITING_AGENT:
|
|
263
|
+
return {WorkflowEffectKind.RUN_SUBWORKFLOW}
|
|
264
|
+
case WorkflowStateCategory.WAITING_EXTERNAL:
|
|
265
|
+
return {WorkflowEffectKind.WAIT_EXTERNAL}
|
|
266
|
+
case WorkflowStateCategory.WAITING_HUMAN:
|
|
267
|
+
return {WorkflowEffectKind.ASK_HUMAN}
|
|
268
|
+
case _:
|
|
269
|
+
return set()
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def build_flashcards_fsm_result_from_model(model: WorkflowModel) -> FlashcardsFsmResult:
|
|
273
|
+
"""Project the real FlashcardsMachine model without reclassifying aggregate facts."""
|
|
274
|
+
|
|
275
|
+
_validate_flashcards_machine_model(model)
|
|
276
|
+
state = FlashcardsState(model.state)
|
|
277
|
+
category = category_for_flashcards_state(state)
|
|
278
|
+
progress_state = _progress_state_from_model(model, state, category)
|
|
279
|
+
progress_view_model = build_progress_view_model(progress_state)
|
|
280
|
+
snapshot = _snapshot_from_model(model, state, category)
|
|
281
|
+
reports = _reports_from_model(model, state, progress_state)
|
|
282
|
+
agent_directive = agent_directive_from_progress_view_model(
|
|
283
|
+
progress_view_model,
|
|
284
|
+
schema="medical-notes-workbench.agent-directive.v1",
|
|
285
|
+
reason=_machine_reason_code(model, state),
|
|
286
|
+
effects=model.pending_effects,
|
|
287
|
+
blockers=_machine_blockers(category, model, state),
|
|
288
|
+
resume=progress_state.resume_action,
|
|
289
|
+
report_requires=["primary_objective", "anki_write", "obsidian_links"],
|
|
290
|
+
summary=_machine_agent_summary(state, progress_state),
|
|
291
|
+
instructions=_machine_agent_instructions(category),
|
|
292
|
+
).to_payload()
|
|
293
|
+
# Keep the public flashcards payload on the repository-wide directive schema
|
|
294
|
+
# while the generic kernel model remains intentionally domain-neutral.
|
|
295
|
+
return FlashcardsFsmResult(
|
|
296
|
+
run_id=model.run_id,
|
|
297
|
+
state_machine_snapshot=snapshot,
|
|
298
|
+
progress_state=progress_state,
|
|
299
|
+
progress_view_model=progress_view_model,
|
|
300
|
+
decision=model.last_transition.decision if model.last_transition is not None else None,
|
|
301
|
+
human_decision_packet=model.last_transition.human_decision_packet if model.last_transition is not None else None,
|
|
302
|
+
receipt=FlashcardsReceipt(
|
|
303
|
+
status=progress_view_model.status,
|
|
304
|
+
changed_files=_event_string_list(model, "changed_files"),
|
|
305
|
+
created_card_count=_event_int_max(model, "created_card_count"),
|
|
306
|
+
),
|
|
307
|
+
reports=reports,
|
|
308
|
+
agent_directive=JsonObjectAdapter.validate_python(agent_directive),
|
|
309
|
+
artifacts=FlashcardsArtifacts(),
|
|
310
|
+
diagnostic_context=_diagnostic_context_from_model(model, state, category),
|
|
311
|
+
error_context=_error_context_for(model.last_transition.decision if model.last_transition is not None else None),
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def flashcards_fsm_payload_from_model(model: WorkflowModel) -> JsonObject:
|
|
315
|
+
"""JSON boundary for the machine-driven `/flashcards` FSM projection."""
|
|
316
|
+
|
|
317
|
+
return build_flashcards_fsm_result_from_model(model).to_payload()
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def flashcards_fsm_payload_from_tagging_receipt(receipt: JsonObject, *, run_id: str) -> JsonObject:
|
|
321
|
+
"""Project the official Obsidian tag receipt into the terminal FSM state."""
|
|
322
|
+
|
|
323
|
+
tagging = FlashcardsTaggingReceipt.model_validate(receipt)
|
|
324
|
+
model = WorkflowModel.start(
|
|
325
|
+
workflow=FLASHCARDS_WORKFLOW,
|
|
326
|
+
run_id=run_id,
|
|
327
|
+
initial_state=FlashcardsState.TAGGING_OBSIDIAN.value,
|
|
328
|
+
)
|
|
329
|
+
send_workflow_event(
|
|
330
|
+
FlashcardsMachine(model=model, state_field=WorkflowModel.STATECHART_STATE_FIELD),
|
|
331
|
+
ObsidianTaggingCompletedEvent(
|
|
332
|
+
workflow=FLASHCARDS_WORKFLOW,
|
|
333
|
+
run_id=run_id,
|
|
334
|
+
current_state=FlashcardsState.TAGGING_OBSIDIAN.value,
|
|
335
|
+
tagged_source_count=len(tagging.changed_files),
|
|
336
|
+
changed_files=list(tagging.changed_files),
|
|
337
|
+
audit_evidence={
|
|
338
|
+
"effect_target": tagging.effect_target,
|
|
339
|
+
"status": tagging.status,
|
|
340
|
+
"tag": tagging.tag,
|
|
341
|
+
},
|
|
342
|
+
),
|
|
343
|
+
)
|
|
344
|
+
return flashcards_fsm_payload_from_model(model)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _validate_flashcards_machine_model(model: WorkflowModel) -> None:
|
|
348
|
+
if model.workflow != FLASHCARDS_WORKFLOW:
|
|
349
|
+
raise ValueError(f"flashcards FSM projector requires workflow={FLASHCARDS_WORKFLOW}")
|
|
350
|
+
FlashcardsState(model.state)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _progress_state_from_model(
|
|
354
|
+
model: WorkflowModel,
|
|
355
|
+
state: FlashcardsState,
|
|
356
|
+
category: WorkflowStateCategory,
|
|
357
|
+
) -> WorkflowProgressState:
|
|
358
|
+
status = _machine_progress_status(category)
|
|
359
|
+
current, total, counts = _machine_counts(model, state)
|
|
360
|
+
return WorkflowProgressState(
|
|
361
|
+
workflow=FLASHCARDS_WORKFLOW,
|
|
362
|
+
run_id=model.run_id,
|
|
363
|
+
state=state.value,
|
|
364
|
+
phase=_machine_phase_for_state(state),
|
|
365
|
+
event_type=_machine_event_type(status),
|
|
366
|
+
message=_machine_message_for_state(state),
|
|
367
|
+
status=status,
|
|
368
|
+
current=current,
|
|
369
|
+
total=total,
|
|
370
|
+
counts=counts,
|
|
371
|
+
resume_action=_machine_resume_action(model, state),
|
|
372
|
+
resume_supported=status
|
|
373
|
+
in {
|
|
374
|
+
WorkflowProgressStatus.WAITING_AGENT,
|
|
375
|
+
WorkflowProgressStatus.WAITING_EXTERNAL,
|
|
376
|
+
WorkflowProgressStatus.WAITING_HUMAN,
|
|
377
|
+
WorkflowProgressStatus.BLOCKED,
|
|
378
|
+
},
|
|
379
|
+
can_continue_now=status
|
|
380
|
+
in {
|
|
381
|
+
WorkflowProgressStatus.RUNNING,
|
|
382
|
+
WorkflowProgressStatus.WAITING_AGENT,
|
|
383
|
+
},
|
|
384
|
+
decision=model.last_transition.decision.decision_summary()
|
|
385
|
+
if model.last_transition is not None and model.last_transition.decision is not None
|
|
386
|
+
else None,
|
|
387
|
+
technical_context={
|
|
388
|
+
"reason": _machine_reason_code(model, state),
|
|
389
|
+
"category": category.value,
|
|
390
|
+
"source": "FlashcardsMachine",
|
|
391
|
+
"source_count": _event_int_max(model, "source_count"),
|
|
392
|
+
"candidate_count": _event_int_max(model, "candidate_count"),
|
|
393
|
+
"new_card_count": _event_int_max(model, "new_card_count"),
|
|
394
|
+
"created_card_count": _event_int_max(model, "created_card_count"),
|
|
395
|
+
"tagged_source_count": _event_int_max(model, "tagged_source_count"),
|
|
396
|
+
},
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _machine_counts(
|
|
401
|
+
model: WorkflowModel,
|
|
402
|
+
state: FlashcardsState,
|
|
403
|
+
) -> tuple[int, int, WorkflowProgressCounts]:
|
|
404
|
+
source_count = _event_int_max(model, "source_count")
|
|
405
|
+
candidate_count = _event_int_max(model, "candidate_count")
|
|
406
|
+
new_card_count = _event_int_max(model, "new_card_count")
|
|
407
|
+
created_count = _event_int_max(model, "created_card_count")
|
|
408
|
+
tagged_count = _event_int_max(model, "tagged_source_count")
|
|
409
|
+
planned = max(candidate_count, new_card_count, created_count, source_count)
|
|
410
|
+
if state == FlashcardsState.COMPLETED:
|
|
411
|
+
total = max(created_count, tagged_count, candidate_count, source_count)
|
|
412
|
+
return (
|
|
413
|
+
total,
|
|
414
|
+
total,
|
|
415
|
+
WorkflowProgressCounts(
|
|
416
|
+
planned_items=total,
|
|
417
|
+
processed_items=total,
|
|
418
|
+
mutated_files=tagged_count,
|
|
419
|
+
written_files=tagged_count,
|
|
420
|
+
),
|
|
421
|
+
)
|
|
422
|
+
if state == FlashcardsState.WAITING_HUMAN_CONFIRMATION:
|
|
423
|
+
return (
|
|
424
|
+
0,
|
|
425
|
+
max(candidate_count, new_card_count),
|
|
426
|
+
WorkflowProgressCounts(
|
|
427
|
+
planned_items=max(candidate_count, new_card_count),
|
|
428
|
+
remaining_items=new_card_count,
|
|
429
|
+
blocked_items=new_card_count,
|
|
430
|
+
),
|
|
431
|
+
)
|
|
432
|
+
if state in {
|
|
433
|
+
FlashcardsState.STALE_SOURCE,
|
|
434
|
+
FlashcardsState.CREATE_CANCELLED,
|
|
435
|
+
FlashcardsState.SOURCE_SELECTION_BLOCKED,
|
|
436
|
+
FlashcardsState.CANDIDATE_GENERATION_BLOCKED,
|
|
437
|
+
FlashcardsState.PREVIEW_DECISION_BLOCKED,
|
|
438
|
+
FlashcardsState.ANKI_WRITE_BLOCKED,
|
|
439
|
+
FlashcardsState.OBSIDIAN_TAGGING_BLOCKED,
|
|
440
|
+
FlashcardsState.FAILED,
|
|
441
|
+
}:
|
|
442
|
+
blocked = max(planned, 1)
|
|
443
|
+
return (
|
|
444
|
+
0,
|
|
445
|
+
blocked,
|
|
446
|
+
WorkflowProgressCounts(
|
|
447
|
+
planned_items=planned,
|
|
448
|
+
remaining_items=blocked,
|
|
449
|
+
blocked_items=blocked,
|
|
450
|
+
),
|
|
451
|
+
)
|
|
452
|
+
return (
|
|
453
|
+
0,
|
|
454
|
+
planned,
|
|
455
|
+
WorkflowProgressCounts(
|
|
456
|
+
planned_items=planned,
|
|
457
|
+
remaining_items=planned,
|
|
458
|
+
),
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def _snapshot_from_model(
|
|
463
|
+
model: WorkflowModel,
|
|
464
|
+
state: FlashcardsState,
|
|
465
|
+
category: WorkflowStateCategory,
|
|
466
|
+
) -> WorkflowStateMachineSnapshot:
|
|
467
|
+
return WorkflowStateMachineSnapshot(
|
|
468
|
+
workflow=FLASHCARDS_WORKFLOW,
|
|
469
|
+
run_id=model.run_id,
|
|
470
|
+
current_state=state.value,
|
|
471
|
+
current_category=category,
|
|
472
|
+
transitions=[_machine_snapshot_transition(transition) for transition in model.transition_log],
|
|
473
|
+
metadata={"reason": _machine_reason_code(model, state), "source": "FlashcardsMachine"},
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _machine_snapshot_transition(transition: WorkflowTransitionResult) -> WorkflowTransition:
|
|
478
|
+
return WorkflowTransition(
|
|
479
|
+
workflow=transition.workflow,
|
|
480
|
+
from_state=transition.from_state,
|
|
481
|
+
to_state=transition.to_state,
|
|
482
|
+
to_category=category_for_flashcards_state(FlashcardsState(transition.to_state)),
|
|
483
|
+
trigger=transition.trigger,
|
|
484
|
+
effects=list(transition.effects),
|
|
485
|
+
decision=transition.decision,
|
|
486
|
+
resume_action=transition.resume_action,
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _reports_from_model(
|
|
491
|
+
model: WorkflowModel,
|
|
492
|
+
state: FlashcardsState,
|
|
493
|
+
progress_state: WorkflowProgressState,
|
|
494
|
+
) -> WorkflowReports:
|
|
495
|
+
summary = _machine_message_for_state(state)
|
|
496
|
+
public_lines = [summary]
|
|
497
|
+
followup_line = public_progress_followup_line(progress_state)
|
|
498
|
+
if followup_line:
|
|
499
|
+
public_lines.append(followup_line)
|
|
500
|
+
public_report = WorkflowPublicReport(
|
|
501
|
+
workflow=FLASHCARDS_WORKFLOW,
|
|
502
|
+
run_id=progress_state.run_id,
|
|
503
|
+
headline=summary,
|
|
504
|
+
lines=public_lines,
|
|
505
|
+
)
|
|
506
|
+
created_count = _event_int_max(model, "created_card_count")
|
|
507
|
+
tagged_count = _event_int_max(model, "tagged_source_count")
|
|
508
|
+
completed = state == FlashcardsState.COMPLETED
|
|
509
|
+
return WorkflowReports(
|
|
510
|
+
summary=summary,
|
|
511
|
+
public_report=public_report,
|
|
512
|
+
details={
|
|
513
|
+
"primary_objective_summary": FlashcardsPrimaryObjectiveSummary(
|
|
514
|
+
run_id=model.run_id,
|
|
515
|
+
completed=completed,
|
|
516
|
+
status=state.value,
|
|
517
|
+
mutation_state="changed" if created_count > 0 or tagged_count > 0 else "unchanged",
|
|
518
|
+
mutation_summary=_flashcards_mutation_summary(created_count, tagged_count),
|
|
519
|
+
remaining_work_summary=_flashcards_remaining_work_summary(state, completed),
|
|
520
|
+
next_step_summary=_flashcards_next_step_summary(progress_state, completed),
|
|
521
|
+
blocked_reason="" if completed else state.value,
|
|
522
|
+
preview_only=state
|
|
523
|
+
in {
|
|
524
|
+
FlashcardsState.WAITING_AGENT_CANDIDATES,
|
|
525
|
+
FlashcardsState.WAITING_HUMAN_CONFIRMATION,
|
|
526
|
+
},
|
|
527
|
+
created_cards=created_count > 0,
|
|
528
|
+
created_card_count=created_count,
|
|
529
|
+
processed_source_count=max(
|
|
530
|
+
_event_int_max(model, "source_count"),
|
|
531
|
+
progress_state.counts.processed_items,
|
|
532
|
+
),
|
|
533
|
+
tagged_source_count=tagged_count,
|
|
534
|
+
obsidian_links_valid=state != FlashcardsState.STALE_SOURCE,
|
|
535
|
+
).to_payload()
|
|
536
|
+
},
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def _flashcards_mutation_summary(created_count: int, tagged_count: int) -> str:
|
|
541
|
+
if created_count > 0:
|
|
542
|
+
return f"{created_count} card(s) foram criados no Anki; {tagged_count} fonte(s) foram marcadas."
|
|
543
|
+
if tagged_count > 0:
|
|
544
|
+
return f"{tagged_count} fonte(s) foram marcadas no Obsidian."
|
|
545
|
+
return "Nenhum card foi criado e nenhuma fonte foi marcada nesta etapa."
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def _flashcards_remaining_work_summary(state: FlashcardsState, completed: bool) -> str:
|
|
549
|
+
if completed:
|
|
550
|
+
return "Cards aceitos foram criados e as fontes foram marcadas quando aplicável."
|
|
551
|
+
return _machine_message_for_state(state)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def _flashcards_next_step_summary(progress_state: WorkflowProgressState, completed: bool) -> str:
|
|
555
|
+
if completed:
|
|
556
|
+
return "Nenhuma ação pendente para flashcards."
|
|
557
|
+
return progress_state.resume_action or "Retomar /flashcards pela rota oficial."
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def _diagnostic_context_from_model(
|
|
561
|
+
model: WorkflowModel,
|
|
562
|
+
state: FlashcardsState,
|
|
563
|
+
category: WorkflowStateCategory,
|
|
564
|
+
) -> JsonObject:
|
|
565
|
+
if category == WorkflowStateCategory.COMPLETED:
|
|
566
|
+
return {}
|
|
567
|
+
context: JsonObject = {
|
|
568
|
+
"schema": "medical-notes-workbench.flashcards-fsm-diagnostic-context.v2",
|
|
569
|
+
"state": state.value,
|
|
570
|
+
"category": category.value,
|
|
571
|
+
"reason": _machine_reason_code(model, state),
|
|
572
|
+
"source": "FlashcardsMachine",
|
|
573
|
+
}
|
|
574
|
+
evidence = _machine_audit_evidence(model)
|
|
575
|
+
for key, value in evidence.items():
|
|
576
|
+
if key not in context:
|
|
577
|
+
context[key] = value
|
|
578
|
+
return diagnostic_context_evidence_only(context)
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def _machine_audit_evidence(model: WorkflowModel) -> JsonObject:
|
|
582
|
+
if not model.event_log:
|
|
583
|
+
return {}
|
|
584
|
+
event = _FlashcardsMachineEventEvidence.model_validate(model.event_log[-1])
|
|
585
|
+
return JsonObjectAdapter.validate_python(event.audit_evidence)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _machine_progress_status(category: WorkflowStateCategory) -> WorkflowProgressStatus:
|
|
589
|
+
match category:
|
|
590
|
+
case WorkflowStateCategory.PREPARING | WorkflowStateCategory.RUNNING:
|
|
591
|
+
return WorkflowProgressStatus.RUNNING
|
|
592
|
+
case WorkflowStateCategory.WAITING_AGENT:
|
|
593
|
+
return WorkflowProgressStatus.WAITING_AGENT
|
|
594
|
+
case WorkflowStateCategory.WAITING_EXTERNAL:
|
|
595
|
+
return WorkflowProgressStatus.WAITING_EXTERNAL
|
|
596
|
+
case WorkflowStateCategory.WAITING_HUMAN:
|
|
597
|
+
return WorkflowProgressStatus.WAITING_HUMAN
|
|
598
|
+
case WorkflowStateCategory.BLOCKED:
|
|
599
|
+
return WorkflowProgressStatus.BLOCKED
|
|
600
|
+
case WorkflowStateCategory.FAILED:
|
|
601
|
+
return WorkflowProgressStatus.FAILED
|
|
602
|
+
case WorkflowStateCategory.COMPLETED:
|
|
603
|
+
return WorkflowProgressStatus.COMPLETED
|
|
604
|
+
case WorkflowStateCategory.COMPLETED_WITH_WARNINGS:
|
|
605
|
+
return WorkflowProgressStatus.COMPLETED_WITH_WARNINGS
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def _machine_event_type(status: WorkflowProgressStatus) -> WorkflowProgressEventType:
|
|
609
|
+
match status:
|
|
610
|
+
case WorkflowProgressStatus.COMPLETED | WorkflowProgressStatus.COMPLETED_WITH_WARNINGS:
|
|
611
|
+
return WorkflowProgressEventType.WORKFLOW_COMPLETED
|
|
612
|
+
case WorkflowProgressStatus.FAILED:
|
|
613
|
+
return WorkflowProgressEventType.WORKFLOW_FAILED
|
|
614
|
+
case WorkflowProgressStatus.WAITING_EXTERNAL:
|
|
615
|
+
return WorkflowProgressEventType.EXTERNAL_WAIT_STARTED
|
|
616
|
+
case WorkflowProgressStatus.WAITING_HUMAN | WorkflowProgressStatus.BLOCKED:
|
|
617
|
+
return WorkflowProgressEventType.DECISION_EMITTED
|
|
618
|
+
case _:
|
|
619
|
+
return WorkflowProgressEventType.STATE_ENTERED
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def _machine_phase_for_state(state: FlashcardsState) -> str:
|
|
623
|
+
match state:
|
|
624
|
+
case FlashcardsState.CHECKING_SOURCES | FlashcardsState.STALE_SOURCE:
|
|
625
|
+
return "flashcards_sources"
|
|
626
|
+
case FlashcardsState.WAITING_AGENT_CANDIDATES | FlashcardsState.WAITING_HUMAN_CONFIRMATION:
|
|
627
|
+
return "flashcards_preview"
|
|
628
|
+
case FlashcardsState.ANKI_UNAVAILABLE | FlashcardsState.WRITING_ANKI:
|
|
629
|
+
return "flashcards_anki"
|
|
630
|
+
case FlashcardsState.TAGGING_OBSIDIAN:
|
|
631
|
+
return "flashcards_obsidian_tagging"
|
|
632
|
+
case FlashcardsState.COMPLETED:
|
|
633
|
+
return "flashcards_completed"
|
|
634
|
+
case FlashcardsState.CREATE_CANCELLED:
|
|
635
|
+
return "flashcards_create_cancelled"
|
|
636
|
+
case FlashcardsState.SOURCE_SELECTION_BLOCKED:
|
|
637
|
+
return "flashcards_source_selection_blocked"
|
|
638
|
+
case FlashcardsState.CANDIDATE_GENERATION_BLOCKED:
|
|
639
|
+
return "flashcards_candidate_generation_blocked"
|
|
640
|
+
case FlashcardsState.PREVIEW_DECISION_BLOCKED:
|
|
641
|
+
return "flashcards_preview_decision_blocked"
|
|
642
|
+
case FlashcardsState.ANKI_WRITE_BLOCKED:
|
|
643
|
+
return "flashcards_anki_write_blocked"
|
|
644
|
+
case FlashcardsState.OBSIDIAN_TAGGING_BLOCKED:
|
|
645
|
+
return "flashcards_obsidian_tagging_blocked"
|
|
646
|
+
case FlashcardsState.FAILED:
|
|
647
|
+
return "flashcards_failed"
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def _machine_message_for_state(state: FlashcardsState) -> str:
|
|
651
|
+
match state:
|
|
652
|
+
case FlashcardsState.WAITING_AGENT_CANDIDATES:
|
|
653
|
+
return "Flashcards aguardam geração de candidatos pelo agente."
|
|
654
|
+
case FlashcardsState.WAITING_HUMAN_CONFIRMATION:
|
|
655
|
+
return "Revise a prévia antes de criar cards no Anki."
|
|
656
|
+
case FlashcardsState.ANKI_UNAVAILABLE:
|
|
657
|
+
return "Flashcards aguardam o Anki ficar disponível."
|
|
658
|
+
case FlashcardsState.WRITING_ANKI:
|
|
659
|
+
return "Flashcards estão sendo gravados no Anki."
|
|
660
|
+
case FlashcardsState.TAGGING_OBSIDIAN:
|
|
661
|
+
return "Flashcards aguardam marcação das fontes no Obsidian."
|
|
662
|
+
case FlashcardsState.STALE_SOURCE:
|
|
663
|
+
return "A fonte dos flashcards ficou desatualizada."
|
|
664
|
+
case FlashcardsState.COMPLETED:
|
|
665
|
+
return "Flashcards criados e fontes conferidas."
|
|
666
|
+
case FlashcardsState.CREATE_CANCELLED:
|
|
667
|
+
return "Criação de flashcards cancelada antes de gravar no Anki."
|
|
668
|
+
case FlashcardsState.SOURCE_SELECTION_BLOCKED:
|
|
669
|
+
return "Seleção das fontes de flashcards bloqueada."
|
|
670
|
+
case FlashcardsState.CANDIDATE_GENERATION_BLOCKED:
|
|
671
|
+
return "Geração de candidatos de flashcards bloqueada."
|
|
672
|
+
case FlashcardsState.PREVIEW_DECISION_BLOCKED:
|
|
673
|
+
return "Decisão da prévia de flashcards bloqueada."
|
|
674
|
+
case FlashcardsState.ANKI_WRITE_BLOCKED:
|
|
675
|
+
return "Escrita dos flashcards no Anki bloqueada."
|
|
676
|
+
case FlashcardsState.OBSIDIAN_TAGGING_BLOCKED:
|
|
677
|
+
return "Marcação das fontes no Obsidian bloqueada."
|
|
678
|
+
case FlashcardsState.FAILED:
|
|
679
|
+
return "Flashcards falharam antes de concluir."
|
|
680
|
+
case _:
|
|
681
|
+
return "Workflow de flashcards em andamento."
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def _machine_resume_action(model: WorkflowModel, state: FlashcardsState) -> str:
|
|
685
|
+
if state == FlashcardsState.COMPLETED:
|
|
686
|
+
return ""
|
|
687
|
+
if model.last_transition is not None and model.last_transition.resume_action:
|
|
688
|
+
return model.last_transition.resume_action
|
|
689
|
+
match state:
|
|
690
|
+
case FlashcardsState.WAITING_AGENT_CANDIDATES:
|
|
691
|
+
return "flashcards:generate-candidates"
|
|
692
|
+
case FlashcardsState.WAITING_HUMAN_CONFIRMATION:
|
|
693
|
+
return "flashcards:confirm-create"
|
|
694
|
+
case FlashcardsState.ANKI_UNAVAILABLE:
|
|
695
|
+
return "flashcards:retry-anki"
|
|
696
|
+
case FlashcardsState.WRITING_ANKI:
|
|
697
|
+
return "flashcards:write-anki"
|
|
698
|
+
case FlashcardsState.TAGGING_OBSIDIAN:
|
|
699
|
+
return "flashcards:tag-obsidian"
|
|
700
|
+
case (
|
|
701
|
+
FlashcardsState.STALE_SOURCE
|
|
702
|
+
| FlashcardsState.CREATE_CANCELLED
|
|
703
|
+
| FlashcardsState.SOURCE_SELECTION_BLOCKED
|
|
704
|
+
| FlashcardsState.CANDIDATE_GENERATION_BLOCKED
|
|
705
|
+
| FlashcardsState.PREVIEW_DECISION_BLOCKED
|
|
706
|
+
| FlashcardsState.ANKI_WRITE_BLOCKED
|
|
707
|
+
| FlashcardsState.OBSIDIAN_TAGGING_BLOCKED
|
|
708
|
+
| FlashcardsState.FAILED
|
|
709
|
+
):
|
|
710
|
+
return "flashcards:prepare"
|
|
711
|
+
case _:
|
|
712
|
+
return ""
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def _machine_reason_code(model: WorkflowModel, state: FlashcardsState) -> str:
|
|
716
|
+
if model.last_transition is not None:
|
|
717
|
+
return model.last_transition.reason_code
|
|
718
|
+
return state.value
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
def _machine_blockers(
|
|
722
|
+
category: WorkflowStateCategory,
|
|
723
|
+
model: WorkflowModel,
|
|
724
|
+
state: FlashcardsState,
|
|
725
|
+
) -> list[str]:
|
|
726
|
+
if category in {
|
|
727
|
+
WorkflowStateCategory.WAITING_AGENT,
|
|
728
|
+
WorkflowStateCategory.WAITING_EXTERNAL,
|
|
729
|
+
WorkflowStateCategory.WAITING_HUMAN,
|
|
730
|
+
WorkflowStateCategory.BLOCKED,
|
|
731
|
+
WorkflowStateCategory.FAILED,
|
|
732
|
+
}:
|
|
733
|
+
return [_machine_reason_code(model, state)]
|
|
734
|
+
return []
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
def _machine_agent_summary(state: FlashcardsState, progress_state: WorkflowProgressState) -> str:
|
|
738
|
+
return progress_state.message or _machine_message_for_state(state)
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
def _machine_agent_instructions(category: WorkflowStateCategory) -> list[str]:
|
|
742
|
+
if category == WorkflowStateCategory.WAITING_AGENT:
|
|
743
|
+
return ["Execute somente os efeitos em agent_directive.control.effects e retome /flashcards pelo resultado tipado."]
|
|
744
|
+
if category == WorkflowStateCategory.WAITING_EXTERNAL:
|
|
745
|
+
return ["Aguarde o Anki ficar disponivel antes de retomar /flashcards."]
|
|
746
|
+
if category == WorkflowStateCategory.WAITING_HUMAN:
|
|
747
|
+
return ["Peça a decisão humana fechada antes de criar cards no Anki."]
|
|
748
|
+
if category in {WorkflowStateCategory.BLOCKED, WorkflowStateCategory.FAILED}:
|
|
749
|
+
return ["Use a decisão e o resume_action da FSM para recuperar /flashcards."]
|
|
750
|
+
return ["Use a FlashcardsMachine como fonte de verdade do estado de flashcards."]
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
def _last_event_int(model: WorkflowModel, field_name: str) -> int:
|
|
754
|
+
if not model.event_log:
|
|
755
|
+
return 0
|
|
756
|
+
event = model.event_log[-1]
|
|
757
|
+
value = event[field_name] if field_name in event else 0
|
|
758
|
+
if isinstance(value, bool):
|
|
759
|
+
return 0
|
|
760
|
+
if isinstance(value, int) and value >= 0:
|
|
761
|
+
return value
|
|
762
|
+
return 0
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
def _event_int_max(model: WorkflowModel, field_name: str) -> int:
|
|
766
|
+
values: list[int] = []
|
|
767
|
+
for event in model.event_log:
|
|
768
|
+
value = event[field_name] if field_name in event else 0
|
|
769
|
+
if isinstance(value, bool):
|
|
770
|
+
continue
|
|
771
|
+
if isinstance(value, int) and value >= 0:
|
|
772
|
+
values.append(value)
|
|
773
|
+
return max(values, default=0)
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
def _event_string_list(model: WorkflowModel, field_name: str) -> list[str]:
|
|
777
|
+
values: list[str] = []
|
|
778
|
+
for event in model.event_log:
|
|
779
|
+
value = event[field_name] if field_name in event else []
|
|
780
|
+
if not isinstance(value, list):
|
|
781
|
+
continue
|
|
782
|
+
for item in value:
|
|
783
|
+
if isinstance(item, str) and item:
|
|
784
|
+
values.append(item)
|
|
785
|
+
return list(dict.fromkeys(values))
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
def _error_context_for(decision: WorkflowDecision | None) -> JsonObject | None:
|
|
789
|
+
if decision is None or decision.kind == "ask_human":
|
|
790
|
+
return None
|
|
791
|
+
if decision.reason_code == "invalid_obsidian_deeplink":
|
|
792
|
+
return JsonObjectAdapter.validate_python(
|
|
793
|
+
{
|
|
794
|
+
"phase": decision.phase,
|
|
795
|
+
"blocked_reason": decision.reason_code,
|
|
796
|
+
"root_cause": "invalid_obsidian_deeplink",
|
|
797
|
+
"affected_artifact": "flashcards_source_manifest",
|
|
798
|
+
"error_summary": decision.developer_summary,
|
|
799
|
+
"suggested_fix": "Regenerar o manifest de fontes e preparar novamente antes de criar cards.",
|
|
800
|
+
"next_action": decision.next_action,
|
|
801
|
+
"retry_scope": "regenerate_flashcard_sources_then_prepare",
|
|
802
|
+
"human_decision_required": False,
|
|
803
|
+
}
|
|
804
|
+
)
|
|
805
|
+
return JsonObjectAdapter.validate_python(
|
|
806
|
+
{
|
|
807
|
+
"phase": decision.phase,
|
|
808
|
+
"blocked_reason": decision.reason_code,
|
|
809
|
+
"root_cause": decision.reason_code,
|
|
810
|
+
"affected_artifact": "flashcards_prepare_payload",
|
|
811
|
+
"error_summary": decision.developer_summary,
|
|
812
|
+
"suggested_fix": decision.next_action,
|
|
813
|
+
"next_action": decision.next_action,
|
|
814
|
+
"retry_scope": "resolve_flashcards_prepare_blocker_then_prepare",
|
|
815
|
+
"human_decision_required": False,
|
|
816
|
+
}
|
|
817
|
+
)
|