devflow-engine 1.0.0__py3-none-any.whl
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.
- devflow_engine/__init__.py +3 -0
- devflow_engine/agentic_prompts.py +100 -0
- devflow_engine/agentic_runtime.py +398 -0
- devflow_engine/api_key_flow_harness.py +539 -0
- devflow_engine/api_keys.py +357 -0
- devflow_engine/bootstrap/__init__.py +2 -0
- devflow_engine/bootstrap/provision_from_template.py +84 -0
- devflow_engine/cli/__init__.py +0 -0
- devflow_engine/cli/app.py +7270 -0
- devflow_engine/core/__init__.py +0 -0
- devflow_engine/core/config.py +86 -0
- devflow_engine/core/logging.py +29 -0
- devflow_engine/core/paths.py +45 -0
- devflow_engine/core/toml_kv.py +33 -0
- devflow_engine/devflow_event_worker.py +1292 -0
- devflow_engine/devflow_state.py +201 -0
- devflow_engine/devin2/__init__.py +9 -0
- devflow_engine/devin2/agent_definition.py +120 -0
- devflow_engine/devin2/pi_runner.py +204 -0
- devflow_engine/devin_orchestration.py +69 -0
- devflow_engine/docs/prompts/anti-patterns.md +42 -0
- devflow_engine/docs/prompts/devin-agent-prompt.md +55 -0
- devflow_engine/docs/prompts/devin2-agent-prompt.md +81 -0
- devflow_engine/docs/prompts/examples/devin-vapi-clone-reference-exchange.json +85 -0
- devflow_engine/doctor/__init__.py +2 -0
- devflow_engine/doctor/triage.py +140 -0
- devflow_engine/error/__init__.py +0 -0
- devflow_engine/error/remediation.py +21 -0
- devflow_engine/errors/error_solver_dag.py +522 -0
- devflow_engine/errors/runtime_observability.py +67 -0
- devflow_engine/idea/__init__.py +4 -0
- devflow_engine/idea/actors.py +481 -0
- devflow_engine/idea/agentic.py +465 -0
- devflow_engine/idea/analyze.py +93 -0
- devflow_engine/idea/devin_chat_dag.py +1 -0
- devflow_engine/idea/diff.py +99 -0
- devflow_engine/idea/drafts.py +446 -0
- devflow_engine/idea/idea_creation_dag.py +643 -0
- devflow_engine/idea/ideation_enrichment.py +355 -0
- devflow_engine/idea/ideation_enrichment_worker.py +19 -0
- devflow_engine/idea/paths.py +28 -0
- devflow_engine/idea/promote.py +53 -0
- devflow_engine/idea/redaction.py +27 -0
- devflow_engine/idea/repo_tools.py +1277 -0
- devflow_engine/idea/response_mode.py +30 -0
- devflow_engine/idea/story_pipeline.py +1585 -0
- devflow_engine/idea/sufficiency.py +376 -0
- devflow_engine/idea/traditional_stories.py +1257 -0
- devflow_engine/implementation/__init__.py +0 -0
- devflow_engine/implementation/alembic_preflight.py +700 -0
- devflow_engine/implementation/dag.py +8450 -0
- devflow_engine/implementation/green_gate.py +93 -0
- devflow_engine/implementation/prompts.py +108 -0
- devflow_engine/implementation/test_runtime.py +623 -0
- devflow_engine/integration/__init__.py +19 -0
- devflow_engine/integration/agentic.py +66 -0
- devflow_engine/integration/dag.py +3539 -0
- devflow_engine/integration/prompts.py +114 -0
- devflow_engine/integration/supabase_schema.sql +31 -0
- devflow_engine/integration/supabase_sync.py +177 -0
- devflow_engine/llm/__init__.py +1 -0
- devflow_engine/llm/cli_one_shot.py +84 -0
- devflow_engine/llm/cli_stream.py +371 -0
- devflow_engine/llm/execution_context.py +26 -0
- devflow_engine/llm/invoke.py +1322 -0
- devflow_engine/llm/provider_api.py +304 -0
- devflow_engine/llm/repo_knowledge.py +588 -0
- devflow_engine/llm_primitives.py +315 -0
- devflow_engine/orchestration.py +62 -0
- devflow_engine/planning/__init__.py +0 -0
- devflow_engine/planning/analyze_repo.py +92 -0
- devflow_engine/planning/render_drafts.py +133 -0
- devflow_engine/playground/__init__.py +0 -0
- devflow_engine/playground/hooks.py +26 -0
- devflow_engine/playwright_workflow/__init__.py +5 -0
- devflow_engine/playwright_workflow/dag.py +1317 -0
- devflow_engine/process/__init__.py +5 -0
- devflow_engine/process/dag.py +59 -0
- devflow_engine/project_registration/__init__.py +3 -0
- devflow_engine/project_registration/dag.py +1581 -0
- devflow_engine/project_registry.py +109 -0
- devflow_engine/prompts/devin/generic/prompt.md +6 -0
- devflow_engine/prompts/devin/ideation/prompt.md +263 -0
- devflow_engine/prompts/devin/ideation/scenarios.md +5 -0
- devflow_engine/prompts/devin/ideation_loop/prompt.md +6 -0
- devflow_engine/prompts/devin/insight/prompt.md +11 -0
- devflow_engine/prompts/devin/insight/scenarios.md +5 -0
- devflow_engine/prompts/devin/intake/prompt.md +15 -0
- devflow_engine/prompts/devin/iterate/prompt.md +12 -0
- devflow_engine/prompts/devin/shared/eval_doctrine.md +9 -0
- devflow_engine/prompts/devin/shared/principles.md +246 -0
- devflow_engine/prompts/devin_eval/assessment/prompt.md +18 -0
- devflow_engine/prompts/idea/api_ideation_agent/prompt.md +8 -0
- devflow_engine/prompts/idea/api_insight_agent/prompt.md +8 -0
- devflow_engine/prompts/idea/response_doctrine/prompt.md +18 -0
- devflow_engine/prompts/implementation/dependency_assessment/prompt.md +12 -0
- devflow_engine/prompts/implementation/green/green/prompt.md +11 -0
- devflow_engine/prompts/implementation/green/node_config/prompt.md +3 -0
- devflow_engine/prompts/implementation/green_review/outcome_review/prompt.md +5 -0
- devflow_engine/prompts/implementation/green_review/prior_run_review/prompt.md +5 -0
- devflow_engine/prompts/implementation/red/prompt.md +27 -0
- devflow_engine/prompts/implementation/redreview/prompt.md +23 -0
- devflow_engine/prompts/implementation/redreview_repair/prompt.md +16 -0
- devflow_engine/prompts/implementation/setupdoc/prompt.md +10 -0
- devflow_engine/prompts/implementation/story_planning/prompt.md +13 -0
- devflow_engine/prompts/implementation/test_design/prompt.md +27 -0
- devflow_engine/prompts/integration/README.md +185 -0
- devflow_engine/prompts/integration/green/example.md +67 -0
- devflow_engine/prompts/integration/green/green/prompt.md +10 -0
- devflow_engine/prompts/integration/green/node_config/prompt.md +42 -0
- devflow_engine/prompts/integration/green/past_prompts/20260417T212300/green/prompt.md +15 -0
- devflow_engine/prompts/integration/green/past_prompts/20260417T212300/node_config/prompt.md +42 -0
- devflow_engine/prompts/integration/green_enrich/example.md +79 -0
- devflow_engine/prompts/integration/green_enrich/green_enrich/prompt.md +9 -0
- devflow_engine/prompts/integration/green_enrich/node_config/prompt.md +41 -0
- devflow_engine/prompts/integration/green_enrich/past_prompts/20260417T212300/green_enrich/prompt.md +14 -0
- devflow_engine/prompts/integration/green_enrich/past_prompts/20260417T212300/node_config/prompt.md +41 -0
- devflow_engine/prompts/integration/red/code_repair/prompt.md +12 -0
- devflow_engine/prompts/integration/red/example.md +152 -0
- devflow_engine/prompts/integration/red/node_config/prompt.md +86 -0
- devflow_engine/prompts/integration/red/past_prompts/20260417T212300/code_repair/prompt.md +19 -0
- devflow_engine/prompts/integration/red/past_prompts/20260417T212300/node_config/prompt.md +84 -0
- devflow_engine/prompts/integration/red/past_prompts/20260417T212300/red/prompt.md +16 -0
- devflow_engine/prompts/integration/red/past_prompts/20260417T212300/red_repair/prompt.md +15 -0
- devflow_engine/prompts/integration/red/past_prompts/20260417T215032/code_repair/prompt.md +10 -0
- devflow_engine/prompts/integration/red/past_prompts/20260417T215032/node_config/prompt.md +84 -0
- devflow_engine/prompts/integration/red/past_prompts/20260417T215032/red_repair/prompt.md +11 -0
- devflow_engine/prompts/integration/red/red/prompt.md +11 -0
- devflow_engine/prompts/integration/red/red_repair/prompt.md +12 -0
- devflow_engine/prompts/integration/red_review/example.md +71 -0
- devflow_engine/prompts/integration/red_review/node_config/prompt.md +41 -0
- devflow_engine/prompts/integration/red_review/past_prompts/20260417T212300/node_config/prompt.md +41 -0
- devflow_engine/prompts/integration/red_review/past_prompts/20260417T212300/red_review/prompt.md +15 -0
- devflow_engine/prompts/integration/red_review/red_review/prompt.md +9 -0
- devflow_engine/prompts/integration/resolve/example.md +111 -0
- devflow_engine/prompts/integration/resolve/node_config/prompt.md +64 -0
- devflow_engine/prompts/integration/resolve/past_prompts/20260417T212300/node_config/prompt.md +64 -0
- devflow_engine/prompts/integration/resolve/past_prompts/20260417T212300/resolve_implicated_users/prompt.md +15 -0
- devflow_engine/prompts/integration/resolve/past_prompts/20260417T212300/resolve_side_effects/prompt.md +15 -0
- devflow_engine/prompts/integration/resolve/resolve_implicated_users/prompt.md +10 -0
- devflow_engine/prompts/integration/resolve/resolve_side_effects/prompt.md +10 -0
- devflow_engine/prompts/integration/validate/build_idea_acceptance_coverage/prompt.md +12 -0
- devflow_engine/prompts/integration/validate/code_repair/prompt.md +13 -0
- devflow_engine/prompts/integration/validate/example.md +143 -0
- devflow_engine/prompts/integration/validate/node_config/prompt.md +87 -0
- devflow_engine/prompts/integration/validate/past_prompts/20260417T212300/code_repair/prompt.md +19 -0
- devflow_engine/prompts/integration/validate/past_prompts/20260417T212300/node_config/prompt.md +67 -0
- devflow_engine/prompts/integration/validate/past_prompts/20260417T212300/validate_enrich_gate/prompt.md +17 -0
- devflow_engine/prompts/integration/validate/past_prompts/20260417T212300/validate_repair/prompt.md +16 -0
- devflow_engine/prompts/integration/validate/past_prompts/20260417T215032/code_repair/prompt.md +10 -0
- devflow_engine/prompts/integration/validate/past_prompts/20260417T215032/node_config/prompt.md +67 -0
- devflow_engine/prompts/integration/validate/past_prompts/20260417T215032/validate_repair/prompt.md +9 -0
- devflow_engine/prompts/integration/validate/validate_enrich_gate/prompt.md +10 -0
- devflow_engine/prompts/integration/validate/validate_repair/prompt.md +20 -0
- devflow_engine/prompts/integration/write_workflows/example.md +100 -0
- devflow_engine/prompts/integration/write_workflows/node_config/prompt.md +44 -0
- devflow_engine/prompts/integration/write_workflows/past_prompts/20260417T212300/node_config/prompt.md +44 -0
- devflow_engine/prompts/integration/write_workflows/past_prompts/20260417T212300/write_workflows/prompt.md +17 -0
- devflow_engine/prompts/integration/write_workflows/write_workflows/prompt.md +11 -0
- devflow_engine/prompts/iterate/README.md +7 -0
- devflow_engine/prompts/iterate/coder/prompt.md +11 -0
- devflow_engine/prompts/iterate/framer/prompt.md +11 -0
- devflow_engine/prompts/iterate/iterator/prompt.md +13 -0
- devflow_engine/prompts/iterate/observer/prompt.md +11 -0
- devflow_engine/prompts/recovery/diagnosis/prompt.md +7 -0
- devflow_engine/prompts/recovery/execution/prompt.md +8 -0
- devflow_engine/prompts/recovery/execution_verification/prompt.md +7 -0
- devflow_engine/prompts/recovery/failure_investigation/prompt.md +10 -0
- devflow_engine/prompts/recovery/preflight_health_repo_repair/prompt.md +8 -0
- devflow_engine/prompts/recovery/remediation_execution/prompt.md +11 -0
- devflow_engine/prompts/recovery/root_cause_investigation/prompt.md +12 -0
- devflow_engine/prompts/scope_idea/doctrine/prompt.md +7 -0
- devflow_engine/prompts/source_doc_eval/document/prompt.md +6 -0
- devflow_engine/prompts/source_doc_eval/targeted_mutation/prompt.md +9 -0
- devflow_engine/prompts/source_doc_mutation/domain_entities/prompt.md +6 -0
- devflow_engine/prompts/source_doc_mutation/product_brief/prompt.md +6 -0
- devflow_engine/prompts/source_doc_mutation/project_doc_coherence/prompt.md +7 -0
- devflow_engine/prompts/source_doc_mutation/project_doc_render/prompt.md +9 -0
- devflow_engine/prompts/source_doc_mutation/source_doc_coherence/prompt.md +5 -0
- devflow_engine/prompts/source_doc_mutation/source_doc_enrichment_coherence/prompt.md +6 -0
- devflow_engine/prompts/source_doc_mutation/user_workflows/prompt.md +6 -0
- devflow_engine/prompts/source_scope/doctrine/prompt.md +10 -0
- devflow_engine/prompts/ui_grounding/doctrine/prompt.md +7 -0
- devflow_engine/recovery/__init__.py +3 -0
- devflow_engine/recovery/dag.py +2609 -0
- devflow_engine/recovery/models.py +220 -0
- devflow_engine/refactor.py +93 -0
- devflow_engine/registry/__init__.py +1 -0
- devflow_engine/registry/cards.py +238 -0
- devflow_engine/registry/domain_normalize.py +60 -0
- devflow_engine/registry/effects.py +65 -0
- devflow_engine/registry/enforce_report.py +150 -0
- devflow_engine/registry/module_cards_classify.py +164 -0
- devflow_engine/registry/module_cards_draft.py +184 -0
- devflow_engine/registry/module_cards_gate.py +59 -0
- devflow_engine/registry/packages.py +347 -0
- devflow_engine/registry/pathways.py +323 -0
- devflow_engine/review/__init__.py +11 -0
- devflow_engine/review/dag.py +588 -0
- devflow_engine/review/review_story.py +67 -0
- devflow_engine/scope_idea/__init__.py +3 -0
- devflow_engine/scope_idea/agentic.py +39 -0
- devflow_engine/scope_idea/dag.py +1069 -0
- devflow_engine/scope_idea/models.py +175 -0
- devflow_engine/skills/builtins/devflow/queue_failure_investigation/SKILL.md +112 -0
- devflow_engine/skills/builtins/devflow/queue_idea_to_story/SKILL.md +120 -0
- devflow_engine/skills/builtins/devflow/queue_integration/SKILL.md +105 -0
- devflow_engine/skills/builtins/devflow/queue_recovery/SKILL.md +108 -0
- devflow_engine/skills/builtins/devflow/queue_runtime_core/SKILL.md +155 -0
- devflow_engine/skills/builtins/devflow/queue_story_implementation/SKILL.md +122 -0
- devflow_engine/skills/builtins/devin/idea_to_story_handoff/SKILL.md +120 -0
- devflow_engine/skills/builtins/devin/ideation/SKILL.md +168 -0
- devflow_engine/skills/builtins/devin/ideation/state-and-phrasing-reference.md +18 -0
- devflow_engine/skills/builtins/devin/insight/SKILL.md +22 -0
- devflow_engine/skills/registry.example.yaml +42 -0
- devflow_engine/source_doc_assumptions.py +291 -0
- devflow_engine/source_doc_mutation_dag.py +1606 -0
- devflow_engine/source_doc_mutation_eval.py +417 -0
- devflow_engine/source_doc_mutation_worker.py +25 -0
- devflow_engine/source_docs_schema.py +207 -0
- devflow_engine/source_docs_updater.py +309 -0
- devflow_engine/source_scope/__init__.py +15 -0
- devflow_engine/source_scope/agentic.py +45 -0
- devflow_engine/source_scope/dag.py +1626 -0
- devflow_engine/source_scope/models.py +177 -0
- devflow_engine/stores/__init__.py +0 -0
- devflow_engine/stores/execution_store.py +3534 -0
- devflow_engine/story/__init__.py +0 -0
- devflow_engine/story/contracts.py +160 -0
- devflow_engine/story/discovery.py +47 -0
- devflow_engine/story/evidence.py +118 -0
- devflow_engine/story/hashing.py +27 -0
- devflow_engine/story/implemented_queue_purge.py +148 -0
- devflow_engine/story/indexer.py +105 -0
- devflow_engine/story/io.py +20 -0
- devflow_engine/story/markdown_contracts.py +298 -0
- devflow_engine/story/reconciliation.py +408 -0
- devflow_engine/story/validate_stories.py +149 -0
- devflow_engine/story/validate_tests_story.py +512 -0
- devflow_engine/story/validation.py +133 -0
- devflow_engine/ui_grounding/__init__.py +11 -0
- devflow_engine/ui_grounding/agentic.py +31 -0
- devflow_engine/ui_grounding/dag.py +874 -0
- devflow_engine/ui_grounding/models.py +224 -0
- devflow_engine/ui_grounding/pencil_bridge.py +247 -0
- devflow_engine/vendor/__init__.py +0 -0
- devflow_engine/vendor/datalumina_genai/__init__.py +11 -0
- devflow_engine/vendor/datalumina_genai/core/__init__.py +0 -0
- devflow_engine/vendor/datalumina_genai/core/exceptions.py +9 -0
- devflow_engine/vendor/datalumina_genai/core/nodes/__init__.py +0 -0
- devflow_engine/vendor/datalumina_genai/core/nodes/agent.py +48 -0
- devflow_engine/vendor/datalumina_genai/core/nodes/agent_streaming_node.py +26 -0
- devflow_engine/vendor/datalumina_genai/core/nodes/base.py +89 -0
- devflow_engine/vendor/datalumina_genai/core/nodes/concurrent.py +30 -0
- devflow_engine/vendor/datalumina_genai/core/nodes/router.py +69 -0
- devflow_engine/vendor/datalumina_genai/core/schema.py +72 -0
- devflow_engine/vendor/datalumina_genai/core/task.py +52 -0
- devflow_engine/vendor/datalumina_genai/core/validate.py +139 -0
- devflow_engine/vendor/datalumina_genai/core/workflow.py +200 -0
- devflow_engine/worker.py +1086 -0
- devflow_engine/worker_guard.py +233 -0
- devflow_engine-1.0.0.dist-info/METADATA +235 -0
- devflow_engine-1.0.0.dist-info/RECORD +393 -0
- devflow_engine-1.0.0.dist-info/WHEEL +4 -0
- devflow_engine-1.0.0.dist-info/entry_points.txt +3 -0
- devin/__init__.py +6 -0
- devin/dag.py +58 -0
- devin/dag_two_arm.py +138 -0
- devin/devin_chat_scenario_catalog.json +588 -0
- devin/devin_eval.py +677 -0
- devin/nodes/__init__.py +0 -0
- devin/nodes/ideation/__init__.py +0 -0
- devin/nodes/ideation/node.py +195 -0
- devin/nodes/ideation/playground.py +267 -0
- devin/nodes/ideation/prompt.md +65 -0
- devin/nodes/ideation/scenarios/continue_refinement.py +13 -0
- devin/nodes/ideation/scenarios/continue_refinement_evals.py +18 -0
- devin/nodes/ideation/scenarios/idea_fits_existing_patterns.py +17 -0
- devin/nodes/ideation/scenarios/idea_fits_existing_patterns_evals.py +16 -0
- devin/nodes/ideation/scenarios/large_idea_split.py +4 -0
- devin/nodes/ideation/scenarios/large_idea_split_evals.py +17 -0
- devin/nodes/ideation/scenarios/source_documentation_added.py +4 -0
- devin/nodes/ideation/scenarios/source_documentation_added_evals.py +16 -0
- devin/nodes/ideation/scenarios/user_says_create_it.py +30 -0
- devin/nodes/ideation/scenarios/user_says_create_it_evals.py +23 -0
- devin/nodes/ideation/scenarios/vague_idea.py +16 -0
- devin/nodes/ideation/scenarios/vague_idea_evals.py +47 -0
- devin/nodes/ideation/tools.json +312 -0
- devin/nodes/insight/__init__.py +0 -0
- devin/nodes/insight/node.py +49 -0
- devin/nodes/insight/playground.py +154 -0
- devin/nodes/insight/prompt.md +61 -0
- devin/nodes/insight/scenarios/architecture_pattern_query.py +15 -0
- devin/nodes/insight/scenarios/architecture_pattern_query_evals.py +25 -0
- devin/nodes/insight/scenarios/codebase_exploration.py +15 -0
- devin/nodes/insight/scenarios/codebase_exploration_evals.py +23 -0
- devin/nodes/insight/scenarios/devin_ideation_routing.py +19 -0
- devin/nodes/insight/scenarios/devin_ideation_routing_evals.py +39 -0
- devin/nodes/insight/scenarios/devin_insight_routing.py +20 -0
- devin/nodes/insight/scenarios/devin_insight_routing_evals.py +40 -0
- devin/nodes/insight/scenarios/operational_debugging.py +15 -0
- devin/nodes/insight/scenarios/operational_debugging_evals.py +23 -0
- devin/nodes/insight/scenarios/operational_question.py +9 -0
- devin/nodes/insight/scenarios/operational_question_evals.py +8 -0
- devin/nodes/insight/scenarios/queue_status.py +15 -0
- devin/nodes/insight/scenarios/queue_status_evals.py +23 -0
- devin/nodes/insight/scenarios/source_doc_explanation.py +14 -0
- devin/nodes/insight/scenarios/source_doc_explanation_evals.py +21 -0
- devin/nodes/insight/scenarios/worker_state_check.py +15 -0
- devin/nodes/insight/scenarios/worker_state_check_evals.py +22 -0
- devin/nodes/insight/tools.json +126 -0
- devin/nodes/intake/__init__.py +0 -0
- devin/nodes/intake/node.py +27 -0
- devin/nodes/intake/playground.py +47 -0
- devin/nodes/intake/prompt.md +12 -0
- devin/nodes/intake/scenarios/ideation_routing.py +4 -0
- devin/nodes/intake/scenarios/ideation_routing_evals.py +5 -0
- devin/nodes/intake/scenarios/insight_routing.py +4 -0
- devin/nodes/intake/scenarios/insight_routing_evals.py +5 -0
- devin/nodes/iterate/README.md +44 -0
- devin/nodes/iterate/__init__.py +1 -0
- devin/nodes/iterate/_archived_design_stages/01-objectives-requirements.md +112 -0
- devin/nodes/iterate/_archived_design_stages/02-evals.md +131 -0
- devin/nodes/iterate/_archived_design_stages/03-tools-and-boundaries.md +110 -0
- devin/nodes/iterate/_archived_design_stages/04-harness-and-playground.md +32 -0
- devin/nodes/iterate/_archived_design_stages/05-prompt-deferred.md +11 -0
- devin/nodes/iterate/_archived_design_stages/coder_agent_design/01-objectives-requirements.md +20 -0
- devin/nodes/iterate/_archived_design_stages/coder_agent_design/02-evals.md +8 -0
- devin/nodes/iterate/_archived_design_stages/coder_agent_design/03-tools-and-boundaries.md +14 -0
- devin/nodes/iterate/_archived_design_stages/coder_agent_design/04-harness-and-playground.md +12 -0
- devin/nodes/iterate/_archived_design_stages/framer_agent_design/01-objectives-requirements.md +20 -0
- devin/nodes/iterate/_archived_design_stages/framer_agent_design/02-evals.md +8 -0
- devin/nodes/iterate/_archived_design_stages/framer_agent_design/03-tools-and-boundaries.md +13 -0
- devin/nodes/iterate/_archived_design_stages/framer_agent_design/04-harness-and-playground.md +12 -0
- devin/nodes/iterate/_archived_design_stages/iterator_agent_design/01-objectives-requirements.md +25 -0
- devin/nodes/iterate/_archived_design_stages/iterator_agent_design/02-evals.md +9 -0
- devin/nodes/iterate/_archived_design_stages/iterator_agent_design/03-tools-and-boundaries.md +14 -0
- devin/nodes/iterate/_archived_design_stages/iterator_agent_design/04-harness-and-playground.md +12 -0
- devin/nodes/iterate/_archived_design_stages/observer_agent_design/01-objectives-requirements.md +20 -0
- devin/nodes/iterate/_archived_design_stages/observer_agent_design/02-evals.md +8 -0
- devin/nodes/iterate/_archived_design_stages/observer_agent_design/03-tools-and-boundaries.md +14 -0
- devin/nodes/iterate/_archived_design_stages/observer_agent_design/04-harness-and-playground.md +13 -0
- devin/nodes/iterate/agent-roles.md +89 -0
- devin/nodes/iterate/agents/README.md +10 -0
- devin/nodes/iterate/artifacts.md +504 -0
- devin/nodes/iterate/contract.md +100 -0
- devin/nodes/iterate/eval-plan.md +74 -0
- devin/nodes/iterate/node.py +100 -0
- devin/nodes/iterate/pipeline/README.md +13 -0
- devin/nodes/iterate/playground-contract.md +76 -0
- devin/nodes/iterate/prompt.md +11 -0
- devin/nodes/iterate/scenarios/README.md +38 -0
- devin/nodes/iterate/scenarios/artifact-and-loop-scenarios.md +101 -0
- devin/nodes/iterate/scenarios/coder_artifact_alignment.py +32 -0
- devin/nodes/iterate/scenarios/coder_artifact_alignment_evals.py +45 -0
- devin/nodes/iterate/scenarios/coder_bounded_fix.py +27 -0
- devin/nodes/iterate/scenarios/coder_bounded_fix_evals.py +45 -0
- devin/nodes/iterate/scenarios/devin_iterate_routing.py +21 -0
- devin/nodes/iterate/scenarios/devin_iterate_routing_evals.py +36 -0
- devin/nodes/iterate/scenarios/framer_scope_boundary.py +25 -0
- devin/nodes/iterate/scenarios/framer_scope_boundary_evals.py +57 -0
- devin/nodes/iterate/scenarios/framer_task_framing.py +25 -0
- devin/nodes/iterate/scenarios/framer_task_framing_evals.py +58 -0
- devin/nodes/iterate/scenarios/iterate_error_fix.py +21 -0
- devin/nodes/iterate/scenarios/iterate_error_fix_evals.py +39 -0
- devin/nodes/iterate/scenarios/iterate_quick_change.py +21 -0
- devin/nodes/iterate/scenarios/iterate_quick_change_evals.py +35 -0
- devin/nodes/iterate/scenarios/iterate_to_idea_promotion.py +23 -0
- devin/nodes/iterate/scenarios/iterate_to_idea_promotion_evals.py +53 -0
- devin/nodes/iterate/scenarios/iterate_to_insight_reroute.py +23 -0
- devin/nodes/iterate/scenarios/iterate_to_insight_reroute_evals.py +53 -0
- devin/nodes/iterate/scenarios/observer_evidence_seam.py +28 -0
- devin/nodes/iterate/scenarios/observer_evidence_seam_evals.py +55 -0
- devin/nodes/iterate/scenarios/observer_repro_creation.py +28 -0
- devin/nodes/iterate/scenarios/observer_repro_creation_evals.py +45 -0
- devin/nodes/iterate/scenarios/routing-matrix.md +45 -0
- devin/nodes/shared/__init__.py +0 -0
- devin/nodes/shared/filemaker_expert.md +80 -0
- devin/nodes/shared/filemaker_expert.py +354 -0
- devin/nodes/shared/filemaker_expert_eval/runner.py +176 -0
- devin/nodes/shared/filemaker_expert_eval/scenarios.json +65 -0
- devin/nodes/shared/goldilocks_advisor_eval/runner.py +214 -0
- devin/nodes/shared/goldilocks_advisor_eval/scenarios.json +58 -0
- devin/nodes/shared/helpers.py +156 -0
- devin/nodes/shared/idea_compliance_advisor_eval/runner.py +252 -0
- devin/nodes/shared/idea_compliance_advisor_eval/scenarios.json +75 -0
- devin/nodes/shared/models.py +44 -0
- devin/nodes/shared/post.py +40 -0
- devin/nodes/shared/router.py +107 -0
- devin/nodes/shared/tools.py +191 -0
- devin/shared/devin-chat-rubric.md +237 -0
- devin/shared/devin-chat-scenario-suite.md +90 -0
- devin/shared/eval_doctrine.md +9 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from devflow_engine.llm.cli_one_shot import run_one_shot
|
|
10
|
+
|
|
11
|
+
from .paths import get_idea_paths
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_REGISTRY_JSON_FILENAME = "users.json"
|
|
15
|
+
_REGISTRY_MD_FILENAME = "users.md"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _load_llm_cli_config() -> tuple[str, str]:
|
|
19
|
+
base_cmd = os.environ.get("DEVFLOW_LLM_CLI_BASE", "claude --print")
|
|
20
|
+
delivery = os.environ.get("DEVFLOW_LLM_CLI_DELIVERY", "stdin").strip().lower()
|
|
21
|
+
if delivery not in {"stdin", "argument"}:
|
|
22
|
+
raise RuntimeError("DEVFLOW_LLM_CLI_DELIVERY must be stdin or argument")
|
|
23
|
+
return base_cmd, delivery
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _extract_json(text: str) -> str | None:
|
|
27
|
+
if "```" in text:
|
|
28
|
+
parts = text.split("```")
|
|
29
|
+
for i in range(len(parts) - 1):
|
|
30
|
+
body = parts[i + 1]
|
|
31
|
+
body_lines = body.splitlines()
|
|
32
|
+
if body_lines and body_lines[0].strip().lower() in {"json", "javascript"}:
|
|
33
|
+
body = "\n".join(body_lines[1:])
|
|
34
|
+
body = body.strip()
|
|
35
|
+
if body.startswith("{") and body.endswith("}"):
|
|
36
|
+
return body
|
|
37
|
+
start = text.find("{")
|
|
38
|
+
end = text.rfind("}")
|
|
39
|
+
if start != -1 and end != -1 and end > start:
|
|
40
|
+
return text[start:end+1]
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _call_json_llm(*, repo_root: Path, prompt: dict[str, Any], error_message: str) -> dict[str, Any]:
|
|
45
|
+
base_cmd, delivery = _load_llm_cli_config()
|
|
46
|
+
result = run_one_shot(base_cmd=base_cmd, delivery=delivery, prompt=json.dumps(prompt, indent=2, sort_keys=True), cwd=repo_root, timeout_seconds=900)
|
|
47
|
+
if not result.ok:
|
|
48
|
+
raise RuntimeError(result.stderr or result.stdout or error_message)
|
|
49
|
+
raw_json = _extract_json(result.stdout)
|
|
50
|
+
if raw_json is None:
|
|
51
|
+
raise RuntimeError(f"Failed to locate JSON in actor normalization output for task={prompt.get('task')!r}.")
|
|
52
|
+
parsed = json.loads(raw_json)
|
|
53
|
+
if not isinstance(parsed, dict):
|
|
54
|
+
raise RuntimeError(f"Actor normalization output for task={prompt.get('task')!r} must be a JSON object.")
|
|
55
|
+
return parsed
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _llm_normalize_actor_entries(*, repo_root: Path, idea_actors: list[str], existing_entries: list[dict[str, Any]], sufficient_idea: dict[str, Any]) -> dict[str, Any]:
|
|
59
|
+
prompt = {
|
|
60
|
+
"task": "normalize_idea_actors",
|
|
61
|
+
"instructions": [
|
|
62
|
+
"Map each raw actor string from the idea into the existing canonical actor registry when possible.",
|
|
63
|
+
"Use semantic matching, aliases, descriptions, kind, and inheritance.",
|
|
64
|
+
"If a source actor phrase semantically refers to one or more existing canonical actors, return decision='matched' and canonical_actor_ids.",
|
|
65
|
+
"A single source actor phrase may legitimately map to multiple canonical actors when it explicitly combines roles like Regular Manager / Super Manager.",
|
|
66
|
+
"If no existing actor fits, return decision='proposed' with one or more singular canonical proposed actors.",
|
|
67
|
+
"Never return merged actor labels as canonical outputs. Split combined source phrases into multiple singular canonical actors when appropriate.",
|
|
68
|
+
"Return JSON only."
|
|
69
|
+
],
|
|
70
|
+
"existing_actors": existing_entries,
|
|
71
|
+
"idea_actors": idea_actors,
|
|
72
|
+
"idea_summary": str(sufficient_idea.get("summary") or sufficient_idea.get("description") or ""),
|
|
73
|
+
"output_schema": {
|
|
74
|
+
"mappings": [
|
|
75
|
+
{
|
|
76
|
+
"source_actor_text": "string",
|
|
77
|
+
"decision": "matched|proposed",
|
|
78
|
+
"canonical_actor_ids": ["string"],
|
|
79
|
+
"canonical_actor_labels": ["string"],
|
|
80
|
+
"proposed_actors": [{"proposed_id": "string", "proposed_label": "string", "kind": "human|system|consumer|null", "inherits_from": "string|null"}],
|
|
81
|
+
"rationale": "string"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return _call_json_llm(repo_root=repo_root, prompt=prompt, error_message="actor normalization LLM command failed")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def actor_registry_json_path(repo_root: Path) -> Path:
|
|
90
|
+
return repo_root / _REGISTRY_JSON_FILENAME
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def actor_registry_markdown_path(repo_root: Path) -> Path:
|
|
94
|
+
return repo_root / _REGISTRY_MD_FILENAME
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def actor_registry_path(repo_root: Path) -> Path:
|
|
98
|
+
json_path = actor_registry_json_path(repo_root)
|
|
99
|
+
if json_path.exists():
|
|
100
|
+
return json_path
|
|
101
|
+
return actor_registry_markdown_path(repo_root)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def normalize_actor_label(value: str) -> str:
|
|
105
|
+
lowered = value.strip().lower()
|
|
106
|
+
lowered = re.sub(r"[^a-z0-9]+", " ", lowered)
|
|
107
|
+
lowered = re.sub(r"\s+", " ", lowered).strip()
|
|
108
|
+
return lowered
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def validate_canonical_actor_label(label: str) -> None:
|
|
112
|
+
text = str(label).strip()
|
|
113
|
+
if not text:
|
|
114
|
+
raise RuntimeError("Canonical actor labels must not be empty.")
|
|
115
|
+
if "/" in text:
|
|
116
|
+
raise RuntimeError(
|
|
117
|
+
f"Canonical actor label {text!r} is invalid: use one actor class only and never slash-separated aliases."
|
|
118
|
+
)
|
|
119
|
+
lowered = text.lower()
|
|
120
|
+
if " and/or " in lowered:
|
|
121
|
+
raise RuntimeError(
|
|
122
|
+
f"Canonical actor label {text!r} is invalid: do not use merged actor aliases like and/or."
|
|
123
|
+
)
|
|
124
|
+
if re.search(r"\bor\b", lowered):
|
|
125
|
+
raise RuntimeError(
|
|
126
|
+
f"Canonical actor label {text!r} is invalid: use exactly one primary actor and do not merge actors with 'or'."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _coerce_actor_list(value: object) -> list[str]:
|
|
131
|
+
if isinstance(value, str):
|
|
132
|
+
text = value.strip()
|
|
133
|
+
return [text] if text else []
|
|
134
|
+
if isinstance(value, list):
|
|
135
|
+
out: list[str] = []
|
|
136
|
+
for item in value:
|
|
137
|
+
text = str(item).strip()
|
|
138
|
+
if text:
|
|
139
|
+
out.append(text)
|
|
140
|
+
return out
|
|
141
|
+
return []
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _contains_any(text: str, terms: set[str]) -> bool:
|
|
145
|
+
return any(term in text for term in terms)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _slash_variants(label: str) -> list[str]:
|
|
149
|
+
if "/" not in label:
|
|
150
|
+
return []
|
|
151
|
+
parts = [part.strip(" -\t") for part in re.split(r"\s*/\s*", label) if part.strip(" -\t")]
|
|
152
|
+
return parts
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _actor_entry(*, label: str, description: str = "", aliases: list[str] | None = None, actor_id: str | None = None, kind: str | None = None, inherits_from: str | None = None) -> dict[str, Any]:
|
|
156
|
+
clean_label = str(label).strip()
|
|
157
|
+
validate_canonical_actor_label(clean_label)
|
|
158
|
+
clean_description = str(description).strip()
|
|
159
|
+
norm_label = normalize_actor_label(clean_label)
|
|
160
|
+
dedup_aliases: list[str] = []
|
|
161
|
+
seen: set[str] = set()
|
|
162
|
+
for alias in [clean_label, *(aliases or [])]:
|
|
163
|
+
alias_text = str(alias).strip()
|
|
164
|
+
alias_norm = normalize_actor_label(alias_text)
|
|
165
|
+
if not alias_text or not alias_norm or alias_norm in seen:
|
|
166
|
+
continue
|
|
167
|
+
seen.add(alias_norm)
|
|
168
|
+
dedup_aliases.append(alias_text)
|
|
169
|
+
actor_kind = str(kind or "human").strip().lower() or "human"
|
|
170
|
+
if actor_kind not in {"human", "system", "consumer"}:
|
|
171
|
+
raise RuntimeError(f"Actor kind {actor_kind!r} is invalid. Allowed kinds: human, system, consumer.")
|
|
172
|
+
inherited_actor = str(inherits_from or "").strip() or None
|
|
173
|
+
return {
|
|
174
|
+
"id": str(actor_id or norm_label.replace(" ", "_")).strip() or norm_label.replace(" ", "_"),
|
|
175
|
+
"label": clean_label,
|
|
176
|
+
"description": clean_description,
|
|
177
|
+
"kind": actor_kind,
|
|
178
|
+
"inherits_from": inherited_actor,
|
|
179
|
+
"aliases": dedup_aliases,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _markdown_value_to_actor_entry(value: str) -> dict[str, Any]:
|
|
184
|
+
text = value.strip()
|
|
185
|
+
if not text:
|
|
186
|
+
raise RuntimeError("Actor registry entry must not be empty.")
|
|
187
|
+
match = re.match(r"^(.*?)\s+[—-]\s+(.*)$", text)
|
|
188
|
+
if match:
|
|
189
|
+
return _actor_entry(label=match.group(1).strip(), description=match.group(2).strip())
|
|
190
|
+
if ":" in text:
|
|
191
|
+
label, description = text.split(":", 1)
|
|
192
|
+
return _actor_entry(label=label.strip(), description=description.strip())
|
|
193
|
+
return _actor_entry(label=text)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _repair_actor_label(label: str, *, existing_actors: list[str]) -> tuple[str, str | None]:
|
|
197
|
+
text = str(label).strip()
|
|
198
|
+
try:
|
|
199
|
+
validate_canonical_actor_label(text)
|
|
200
|
+
return text, None
|
|
201
|
+
except RuntimeError:
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
existing_labels = [actor.strip() for actor in existing_actors if actor.strip()]
|
|
205
|
+
existing_norms = {normalize_actor_label(actor): actor for actor in existing_labels}
|
|
206
|
+
norm = normalize_actor_label(text)
|
|
207
|
+
manager_labels = [actor for actor in existing_labels if "manager" in normalize_actor_label(actor)]
|
|
208
|
+
if manager_labels and "manager" in norm:
|
|
209
|
+
super_like = {"super", "org wide", "org-wide", "all groups", "all group", "global"}
|
|
210
|
+
if _contains_any(norm, super_like):
|
|
211
|
+
for actor in manager_labels:
|
|
212
|
+
if "super manager" in normalize_actor_label(actor):
|
|
213
|
+
return actor, f"repaired {text!r} to existing canonical manager role {actor!r}"
|
|
214
|
+
for actor in manager_labels:
|
|
215
|
+
if "regular manager" in normalize_actor_label(actor):
|
|
216
|
+
return actor, f"repaired {text!r} to existing canonical manager role {actor!r}"
|
|
217
|
+
if len(manager_labels) == 1:
|
|
218
|
+
return manager_labels[0], f"repaired {text!r} to existing canonical manager role {manager_labels[0]!r}"
|
|
219
|
+
|
|
220
|
+
variants = _slash_variants(text)
|
|
221
|
+
for variant in sorted(variants, key=lambda item: len(normalize_actor_label(item).split()), reverse=True):
|
|
222
|
+
variant_norm = normalize_actor_label(variant)
|
|
223
|
+
if variant_norm in existing_norms:
|
|
224
|
+
repaired = existing_norms[variant_norm]
|
|
225
|
+
return repaired, f"repaired {text!r} to existing canonical actor {repaired!r}"
|
|
226
|
+
|
|
227
|
+
generic_terms = {"admin", "administrator", "user", "operator", "team", "org", "organization", "level"}
|
|
228
|
+
if variants:
|
|
229
|
+
ranked = sorted(
|
|
230
|
+
variants,
|
|
231
|
+
key=lambda item: (
|
|
232
|
+
-len([tok for tok in normalize_actor_label(item).split() if tok not in generic_terms]),
|
|
233
|
+
-len(normalize_actor_label(item).split()),
|
|
234
|
+
),
|
|
235
|
+
)
|
|
236
|
+
repaired = ranked[0].strip()
|
|
237
|
+
validate_canonical_actor_label(repaired)
|
|
238
|
+
return repaired, f"repaired slash-merged actor {text!r} to canonical actor {repaired!r}"
|
|
239
|
+
|
|
240
|
+
raise RuntimeError(
|
|
241
|
+
f"Canonical actor label {text!r} is invalid and could not be repaired to an existing or canonical actor label."
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _load_json_registry(path: Path) -> dict[str, Any]:
|
|
246
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
247
|
+
raw_actors = payload.get("actors") if isinstance(payload, dict) else None
|
|
248
|
+
raw_notes = payload.get("notes") if isinstance(payload, dict) else None
|
|
249
|
+
actors: list[dict[str, Any]] = []
|
|
250
|
+
for item in raw_actors or []:
|
|
251
|
+
if not isinstance(item, dict):
|
|
252
|
+
continue
|
|
253
|
+
actors.append(
|
|
254
|
+
_actor_entry(
|
|
255
|
+
label=str(item.get("label") or "").strip(),
|
|
256
|
+
description=str(item.get("description") or "").strip(),
|
|
257
|
+
aliases=[str(alias).strip() for alias in (item.get("aliases") or []) if str(alias).strip()],
|
|
258
|
+
actor_id=str(item.get("id") or "").strip() or None,
|
|
259
|
+
kind=str(item.get("kind") or "").strip() or None,
|
|
260
|
+
inherits_from=str(item.get("inherits_from") or "").strip() or None,
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
notes = [str(item).strip() for item in (raw_notes or []) if str(item).strip()]
|
|
264
|
+
return {"path": str(path), "actors": actors, "notes": notes}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _load_markdown_registry(path: Path) -> dict[str, Any]:
|
|
268
|
+
actors: list[dict[str, Any]] = []
|
|
269
|
+
notes: list[str] = []
|
|
270
|
+
if path.exists():
|
|
271
|
+
section: str | None = None
|
|
272
|
+
for raw_line in path.read_text(encoding="utf-8").splitlines():
|
|
273
|
+
line = raw_line.strip()
|
|
274
|
+
lowered = line.lower()
|
|
275
|
+
if lowered.startswith("## "):
|
|
276
|
+
if "actor" in lowered:
|
|
277
|
+
section = "actors"
|
|
278
|
+
elif "note" in lowered:
|
|
279
|
+
section = "notes"
|
|
280
|
+
else:
|
|
281
|
+
section = None
|
|
282
|
+
continue
|
|
283
|
+
if not line.startswith("- "):
|
|
284
|
+
continue
|
|
285
|
+
value = line[2:].strip()
|
|
286
|
+
if not value:
|
|
287
|
+
continue
|
|
288
|
+
if section == "actors":
|
|
289
|
+
actors.append(_markdown_value_to_actor_entry(value))
|
|
290
|
+
elif section == "notes":
|
|
291
|
+
notes.append(value)
|
|
292
|
+
return {"path": str(path), "actors": actors, "notes": notes}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def load_actor_registry(repo_root: Path) -> dict[str, Any]:
|
|
296
|
+
json_path = actor_registry_json_path(repo_root)
|
|
297
|
+
if json_path.exists():
|
|
298
|
+
return _load_json_registry(json_path)
|
|
299
|
+
return _load_markdown_registry(actor_registry_markdown_path(repo_root))
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def write_actor_registry(repo_root: Path, *, actors: list[str] | list[dict[str, Any]], notes: list[str] | None = None) -> Path:
|
|
303
|
+
path = actor_registry_json_path(repo_root)
|
|
304
|
+
unique: list[dict[str, Any]] = []
|
|
305
|
+
seen: set[str] = set()
|
|
306
|
+
for actor in actors:
|
|
307
|
+
if isinstance(actor, dict):
|
|
308
|
+
entry = _actor_entry(
|
|
309
|
+
label=str(actor.get("label") or "").strip(),
|
|
310
|
+
description=str(actor.get("description") or "").strip(),
|
|
311
|
+
aliases=[str(alias).strip() for alias in (actor.get("aliases") or []) if str(alias).strip()],
|
|
312
|
+
actor_id=str(actor.get("id") or "").strip() or None,
|
|
313
|
+
kind=str(actor.get("kind") or "").strip() or None,
|
|
314
|
+
inherits_from=str(actor.get("inherits_from") or "").strip() or None,
|
|
315
|
+
)
|
|
316
|
+
else:
|
|
317
|
+
entry = _actor_entry(label=str(actor).strip())
|
|
318
|
+
norm = normalize_actor_label(str(entry["label"]))
|
|
319
|
+
if not norm or norm in seen:
|
|
320
|
+
continue
|
|
321
|
+
seen.add(norm)
|
|
322
|
+
unique.append(entry)
|
|
323
|
+
payload = {
|
|
324
|
+
"version": 1,
|
|
325
|
+
"actors": unique,
|
|
326
|
+
"notes": [str(item).strip() for item in (notes or []) if str(item).strip()],
|
|
327
|
+
}
|
|
328
|
+
path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
329
|
+
return path
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def normalize_idea_actors(*, repo_root: Path, idea_id: str, sufficient_idea: dict[str, Any], existing_registry: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
333
|
+
del idea_id
|
|
334
|
+
registry = existing_registry or load_actor_registry(repo_root)
|
|
335
|
+
actor_entries = [item for item in (registry.get("actors") or []) if isinstance(item, dict)]
|
|
336
|
+
existing_entries_by_id = {str(item.get("id") or "").strip(): dict(item) for item in actor_entries if str(item.get("id") or "").strip()}
|
|
337
|
+
idea_actors = _coerce_actor_list(sufficient_idea.get("target_users") or sufficient_idea.get("users"))
|
|
338
|
+
if not idea_actors:
|
|
339
|
+
return {
|
|
340
|
+
"canonical_actors": [str(item.get("label") or "").strip() for item in actor_entries if str(item.get("label") or "").strip()],
|
|
341
|
+
"resolved_actors": actor_entries,
|
|
342
|
+
"actors": actor_entries,
|
|
343
|
+
"additions": [],
|
|
344
|
+
"repairs": [],
|
|
345
|
+
"notes": list(registry.get("notes") or []),
|
|
346
|
+
"path": str(actor_registry_path(repo_root).name),
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
llm_result = _llm_normalize_actor_entries(repo_root=repo_root, idea_actors=idea_actors, existing_entries=actor_entries, sufficient_idea=sufficient_idea)
|
|
350
|
+
mappings = [item for item in (llm_result.get("mappings") or []) if isinstance(item, dict)]
|
|
351
|
+
resolved_entries = list(actor_entries)
|
|
352
|
+
additions: list[str] = []
|
|
353
|
+
repairs: list[dict[str, str]] = []
|
|
354
|
+
seen_ids = {str(item.get("id") or "").strip() for item in resolved_entries if str(item.get("id") or "").strip()}
|
|
355
|
+
|
|
356
|
+
for mapping in mappings:
|
|
357
|
+
source_text = str(mapping.get("source_actor_text") or "").strip()
|
|
358
|
+
decision = str(mapping.get("decision") or "").strip().lower()
|
|
359
|
+
if decision == "matched":
|
|
360
|
+
actor_ids = [str(item).strip() for item in (mapping.get("canonical_actor_ids") or []) if str(item).strip()]
|
|
361
|
+
if not actor_ids:
|
|
362
|
+
raise RuntimeError(f"Actor normalization returned no canonical_actor_ids for matched source actor {source_text!r}")
|
|
363
|
+
matched_labels: list[str] = []
|
|
364
|
+
for actor_id in actor_ids:
|
|
365
|
+
if actor_id not in existing_entries_by_id:
|
|
366
|
+
raise RuntimeError(f"Actor normalization returned unknown canonical_actor_id {actor_id!r} for source actor {source_text!r}")
|
|
367
|
+
matched = existing_entries_by_id[actor_id]
|
|
368
|
+
matched_labels.append(str(matched.get("label") or "").strip())
|
|
369
|
+
repairs.append({"original": source_text, "repaired": ", ".join(matched_labels), "reason": str(mapping.get("rationale") or "semantic match").strip()})
|
|
370
|
+
continue
|
|
371
|
+
if decision == "proposed":
|
|
372
|
+
proposed_actors = [item for item in (mapping.get("proposed_actors") or []) if isinstance(item, dict)]
|
|
373
|
+
if not proposed_actors:
|
|
374
|
+
raise RuntimeError(f"Actor normalization returned no proposed_actors for source actor {source_text!r}")
|
|
375
|
+
proposed_labels: list[str] = []
|
|
376
|
+
for proposed in proposed_actors:
|
|
377
|
+
proposed_label = str(proposed.get("proposed_label") or "").strip()
|
|
378
|
+
proposed_id = str(proposed.get("proposed_id") or "").strip() or normalize_actor_label(proposed_label).replace(" ", "_")
|
|
379
|
+
entry = _actor_entry(
|
|
380
|
+
label=proposed_label,
|
|
381
|
+
actor_id=proposed_id,
|
|
382
|
+
kind=str(proposed.get("kind") or "human").strip() or None,
|
|
383
|
+
inherits_from=str(proposed.get("inherits_from") or "").strip() or None,
|
|
384
|
+
)
|
|
385
|
+
proposed_labels.append(entry["label"])
|
|
386
|
+
if entry["id"] not in seen_ids:
|
|
387
|
+
resolved_entries.append(entry)
|
|
388
|
+
seen_ids.add(entry["id"])
|
|
389
|
+
additions.append(entry["label"])
|
|
390
|
+
repairs.append({"original": source_text, "repaired": ", ".join(proposed_labels), "reason": str(mapping.get("rationale") or "proposed canonical actor").strip()})
|
|
391
|
+
continue
|
|
392
|
+
raise RuntimeError(f"Actor normalization returned invalid decision {decision!r} for source actor {source_text!r}")
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
"canonical_actors": [str(item.get("label") or "").strip() for item in resolved_entries if str(item.get("label") or "").strip()],
|
|
396
|
+
"resolved_actors": resolved_entries,
|
|
397
|
+
"actors": resolved_entries,
|
|
398
|
+
"additions": additions,
|
|
399
|
+
"repairs": repairs,
|
|
400
|
+
"notes": list(registry.get("notes") or []),
|
|
401
|
+
"path": str(actor_registry_path(repo_root).name),
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
def persist_actor_registry_artifact(*, repo_root: Path, idea_id: str, payload: dict[str, Any]) -> Path:
|
|
405
|
+
idea_dir = get_idea_paths(repo_root, idea_id=idea_id).idea_dir
|
|
406
|
+
idea_dir.mkdir(parents=True, exist_ok=True)
|
|
407
|
+
out = idea_dir / "actor_registry.json"
|
|
408
|
+
out.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
409
|
+
return out
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def resolve_actor_entry(*, actor: str, actor_registry: dict[str, Any]) -> dict[str, Any] | None:
|
|
413
|
+
norm = normalize_actor_label(actor)
|
|
414
|
+
if not norm:
|
|
415
|
+
return None
|
|
416
|
+
raw_entries = actor_registry.get("resolved_actors") or actor_registry.get("actors") or actor_registry.get("canonical_actors") or []
|
|
417
|
+
entries: list[dict[str, Any]] = []
|
|
418
|
+
alias_to_entry: dict[str, dict[str, Any]] = {}
|
|
419
|
+
for item in raw_entries:
|
|
420
|
+
if isinstance(item, dict):
|
|
421
|
+
entry = dict(item)
|
|
422
|
+
label = str(entry.get("label") or "").strip()
|
|
423
|
+
aliases = [str(alias).strip() for alias in (entry.get("aliases") or []) if str(alias).strip()]
|
|
424
|
+
else:
|
|
425
|
+
label = str(item).strip()
|
|
426
|
+
aliases = []
|
|
427
|
+
entry = {"id": normalize_actor_label(label).replace(" ", "_"), "label": label, "description": "", "aliases": [label]}
|
|
428
|
+
if not label:
|
|
429
|
+
continue
|
|
430
|
+
entries.append(entry)
|
|
431
|
+
for alias in [label, *aliases]:
|
|
432
|
+
alias_norm = normalize_actor_label(alias)
|
|
433
|
+
if alias_norm and alias_norm not in alias_to_entry:
|
|
434
|
+
alias_to_entry[alias_norm] = entry
|
|
435
|
+
if norm in alias_to_entry:
|
|
436
|
+
return dict(alias_to_entry[norm])
|
|
437
|
+
actor_tokens = set(norm.split())
|
|
438
|
+
fuzzy: list[dict[str, Any]] = []
|
|
439
|
+
for entry in entries:
|
|
440
|
+
label_norm = normalize_actor_label(str(entry.get("label") or ""))
|
|
441
|
+
if not label_norm:
|
|
442
|
+
continue
|
|
443
|
+
label_tokens = set(label_norm.split())
|
|
444
|
+
if norm in label_norm or label_norm in norm or actor_tokens.issubset(label_tokens) or label_tokens.issubset(actor_tokens):
|
|
445
|
+
fuzzy.append(entry)
|
|
446
|
+
if len(fuzzy) == 1:
|
|
447
|
+
return dict(fuzzy[0])
|
|
448
|
+
return None
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def upsert_runtime_actor_entry(*, actor: str, actor_registry: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
452
|
+
resolved = resolve_actor_entry(actor=actor, actor_registry=actor_registry)
|
|
453
|
+
if resolved is not None:
|
|
454
|
+
return actor_registry, resolved
|
|
455
|
+
label = str(actor).strip()
|
|
456
|
+
validate_canonical_actor_label(label)
|
|
457
|
+
raw_entries = list(actor_registry.get("resolved_actors") or actor_registry.get("actors") or [])
|
|
458
|
+
new_entry = _actor_entry(label=label)
|
|
459
|
+
raw_entries.append(new_entry)
|
|
460
|
+
deduped: list[dict[str, Any]] = []
|
|
461
|
+
seen: set[str] = set()
|
|
462
|
+
for item in raw_entries:
|
|
463
|
+
if not isinstance(item, dict):
|
|
464
|
+
continue
|
|
465
|
+
norm = normalize_actor_label(str(item.get("label") or ""))
|
|
466
|
+
if not norm or norm in seen:
|
|
467
|
+
continue
|
|
468
|
+
seen.add(norm)
|
|
469
|
+
deduped.append(dict(item))
|
|
470
|
+
updated = {
|
|
471
|
+
**actor_registry,
|
|
472
|
+
"resolved_actors": deduped,
|
|
473
|
+
"actors": deduped,
|
|
474
|
+
"canonical_actors": [str(item.get("label") or "").strip() for item in deduped if str(item.get("label") or "").strip()],
|
|
475
|
+
}
|
|
476
|
+
return updated, new_entry
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def canonicalize_story_actor(*, actor: str, actor_registry: dict[str, Any]) -> str | None:
|
|
480
|
+
entry = resolve_actor_entry(actor=actor, actor_registry=actor_registry)
|
|
481
|
+
return None if entry is None else str(entry.get("label") or "").strip() or None
|