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,874 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
from ..devflow_state import publish_devflow_state
|
|
13
|
+
from ..stores.execution_store import ExecutionStore
|
|
14
|
+
from ..vendor.datalumina_genai.core.nodes.agent import AgentConfig, AgentNode
|
|
15
|
+
from ..vendor.datalumina_genai.core.nodes.base import Node
|
|
16
|
+
from ..vendor.datalumina_genai.core.schema import NodeConfig, WorkflowSchema
|
|
17
|
+
from ..vendor.datalumina_genai.core.task import TaskContext
|
|
18
|
+
from ..vendor.datalumina_genai.core.workflow import Workflow
|
|
19
|
+
from . import agentic as ui_grounding_agentic
|
|
20
|
+
from .models import (
|
|
21
|
+
CodeDesignGeneratedReference,
|
|
22
|
+
CodeDesignInventoryArtifact,
|
|
23
|
+
GroundedStoryReferencePatchArtifact,
|
|
24
|
+
UIGapReportArtifact,
|
|
25
|
+
UIGenerationManifestArtifact,
|
|
26
|
+
UIGroundingContextArtifact,
|
|
27
|
+
UIGroundingDagSummary,
|
|
28
|
+
UIGroundingReportArtifact,
|
|
29
|
+
UIGroundingSurfaceResult,
|
|
30
|
+
UIFlowMapArtifact,
|
|
31
|
+
UIReferenceInventoryArtifact,
|
|
32
|
+
UIReferenceInventoryItem,
|
|
33
|
+
UIScreenRelationshipsArtifact,
|
|
34
|
+
UIStructureContractArtifact,
|
|
35
|
+
UIStructureScreenItem,
|
|
36
|
+
UISurfaceDerivationArtifact,
|
|
37
|
+
DerivedUISurface,
|
|
38
|
+
)
|
|
39
|
+
from .pencil_bridge import run_pencil_preflight
|
|
40
|
+
|
|
41
|
+
DAG_ID = "ui_grounding_dag"
|
|
42
|
+
_CURRENT_STORE: ExecutionStore | None = None
|
|
43
|
+
_CURRENT_RUN_ID: str | None = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class UIGroundingDagResult:
|
|
48
|
+
exit_code: int
|
|
49
|
+
run_id: str
|
|
50
|
+
pipeline_dir: Path
|
|
51
|
+
message: str
|
|
52
|
+
outcome: dict[str, Any]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class UIGroundingDagEvent(BaseModel):
|
|
56
|
+
repo_root: str
|
|
57
|
+
project_id: str
|
|
58
|
+
idea_id: str
|
|
59
|
+
idea_path: str | None = None
|
|
60
|
+
idea_inline: dict[str, Any] | None = None
|
|
61
|
+
story_paths: list[str] = []
|
|
62
|
+
design_paths: list[str] = []
|
|
63
|
+
pipeline_key: str
|
|
64
|
+
allow_code_design_enrichment: bool = False
|
|
65
|
+
pencil_app_path: str | None = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _store_run() -> tuple[ExecutionStore, str]:
|
|
69
|
+
if _CURRENT_STORE is None or _CURRENT_RUN_ID is None:
|
|
70
|
+
raise RuntimeError("ui grounding dag missing runtime store/run_id")
|
|
71
|
+
return _CURRENT_STORE, _CURRENT_RUN_ID
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _stable_id(prefix: str, payload: Any, *, size: int = 12) -> str:
|
|
75
|
+
raw = json.dumps(payload, sort_keys=True).encode("utf-8")
|
|
76
|
+
return f"{prefix}{hashlib.sha256(raw).hexdigest()[:size]}"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _write_json(path: Path, payload: dict[str, Any]) -> None:
|
|
80
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _dfs_running(*, project_id: str, run_id: str, summary: str, idea_id: str) -> None:
|
|
85
|
+
publish_devflow_state(
|
|
86
|
+
project_id=project_id,
|
|
87
|
+
run_id=run_id,
|
|
88
|
+
current_state="running",
|
|
89
|
+
current_status="processing",
|
|
90
|
+
run_summary=summary,
|
|
91
|
+
display="project",
|
|
92
|
+
display_path=f"idea:{idea_id}",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _pipeline_root(repo_root: Path, *, idea_id: str, pipeline_key: str) -> Path:
|
|
97
|
+
return repo_root / ".devflow" / "ideas" / idea_id / "pipelines" / DAG_ID / pipeline_key
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _load_json(path: Path) -> dict[str, Any]:
|
|
101
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _normalize_text(text: str) -> str:
|
|
105
|
+
return re.sub(r"[^a-z0-9\s]+", " ", text.lower())
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _tokens(text: str) -> list[str]:
|
|
109
|
+
stop = {"the", "and", "with", "from", "that", "this", "into", "for", "your", "their", "then", "when", "must", "should"}
|
|
110
|
+
out: list[str] = []
|
|
111
|
+
for tok in _normalize_text(text).split():
|
|
112
|
+
if len(tok) >= 4 and tok not in stop and tok not in out:
|
|
113
|
+
out.append(tok)
|
|
114
|
+
return out
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _sentence_chunks(text: str) -> list[str]:
|
|
118
|
+
text = re.sub(r"\s+", " ", text).strip()
|
|
119
|
+
return [part.strip() for part in re.split(r"(?<=[.!?])\s+", text) if part.strip()]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _surface_name(chunk: str) -> str:
|
|
123
|
+
lowered = chunk.lower()
|
|
124
|
+
mapping = [
|
|
125
|
+
("Manager dashboard", ["manager dashboard", "dashboard", "reporting", "score trends"]),
|
|
126
|
+
("Need Help flow", ["need help", "crisis", "help request"]),
|
|
127
|
+
("Vibe Check flow", ["vibe check", "mood"]),
|
|
128
|
+
("Participant survey", ["participant", "survey", "question", "form"]),
|
|
129
|
+
("Quote approval screen", ["quote approval", "approve quote", "secure link"]),
|
|
130
|
+
]
|
|
131
|
+
for title, needles in mapping:
|
|
132
|
+
if any(n in lowered for n in needles):
|
|
133
|
+
return title
|
|
134
|
+
words = chunk.split()
|
|
135
|
+
return " ".join(words[:5]).strip().capitalize() or "UI surface"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _surface_actor(chunk: str) -> str:
|
|
139
|
+
lowered = chunk.lower()
|
|
140
|
+
if "manager" in lowered:
|
|
141
|
+
return "manager"
|
|
142
|
+
if "participant" in lowered or "customer" in lowered:
|
|
143
|
+
return "participant" if "participant" in lowered else "customer"
|
|
144
|
+
if "admin" in lowered:
|
|
145
|
+
return "admin"
|
|
146
|
+
return "user"
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _classify_reference_type(path: Path) -> str:
|
|
152
|
+
lowered = path.as_posix().lower()
|
|
153
|
+
if path.suffix == ".pen":
|
|
154
|
+
return "pencil_artifact"
|
|
155
|
+
if any(part in lowered for part in ["screenshot", "mock", "wireframe"]):
|
|
156
|
+
return "screenshot_spec" if "screenshot" in lowered else "wireframe_package"
|
|
157
|
+
if any(part in lowered for part in ["design", "spec", "figma", "notes"]):
|
|
158
|
+
return "design_doc"
|
|
159
|
+
return "implemented_ui"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _candidate_ui_files(repo_root: Path) -> list[Path]:
|
|
163
|
+
roots = [repo_root / "app", repo_root / "src", repo_root / "components", repo_root / "pages", repo_root / "ai_docs"]
|
|
164
|
+
out: list[Path] = []
|
|
165
|
+
for root in roots:
|
|
166
|
+
if not root.exists():
|
|
167
|
+
continue
|
|
168
|
+
for path in root.rglob("*"):
|
|
169
|
+
if not path.is_file():
|
|
170
|
+
continue
|
|
171
|
+
if path.suffix.lower() not in {".tsx", ".ts", ".jsx", ".js", ".md", ".mdx", ".pen", ".json", ".png", ".jpg", ".jpeg", ".webp"}:
|
|
172
|
+
continue
|
|
173
|
+
rel = path.relative_to(repo_root).as_posix().lower()
|
|
174
|
+
if any(marker in rel for marker in ["dashboard", "screen", "page", "survey", "participant", "manager", "vibe", "help", "quote", "design", "wireframe", "mock"]):
|
|
175
|
+
out.append(path)
|
|
176
|
+
return sorted(out)[:80]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _labels_for_path(path: Path) -> list[str]:
|
|
180
|
+
stem = path.stem.replace("_", " ").replace("-", " ")
|
|
181
|
+
parent = path.parent.name.replace("_", " ").replace("-", " ")
|
|
182
|
+
return [item.strip() for item in [stem, parent] if item.strip()]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _materialize_report(derivation: UISurfaceDerivationArtifact, inventory: UIReferenceInventoryArtifact, *, prefer_generated: bool = False) -> tuple[UIGroundingReportArtifact, UIGapReportArtifact | None]:
|
|
186
|
+
by_id = {item.reference_id: item for item in inventory.references}
|
|
187
|
+
ref_index = [(item, set(_tokens(" ".join([*item.surface_labels, item.path])))) for item in inventory.references]
|
|
188
|
+
rows: list[UIGroundingSurfaceResult] = []
|
|
189
|
+
missing: list[str] = []
|
|
190
|
+
assumptions: list[str] = []
|
|
191
|
+
generated_hits = 0
|
|
192
|
+
for surface in derivation.surfaces:
|
|
193
|
+
if not surface.ui_bearing:
|
|
194
|
+
rows.append(UIGroundingSurfaceResult(
|
|
195
|
+
surface_id=surface.surface_id,
|
|
196
|
+
name=surface.name,
|
|
197
|
+
classification="non_ui_or_backend_only",
|
|
198
|
+
next_required_action="No UI grounding required for this slice.",
|
|
199
|
+
))
|
|
200
|
+
continue
|
|
201
|
+
stoks = set(_tokens(f"{surface.name} {' '.join(surface.jobs)}"))
|
|
202
|
+
matches = [item for item, toks in ref_index if stoks and len(stoks & toks) >= 2]
|
|
203
|
+
if matches:
|
|
204
|
+
top_matches = matches[:3]
|
|
205
|
+
if prefer_generated and any(m.reference_type == "pencil_artifact" for m in top_matches):
|
|
206
|
+
classification = "grounded_with_generated_artifacts"
|
|
207
|
+
generated_hits += 1
|
|
208
|
+
next_action = "Attach strongest code-derived design anchors to grounded story/idea artifacts."
|
|
209
|
+
else:
|
|
210
|
+
classification = "grounded_existing"
|
|
211
|
+
next_action = "Attach existing UI anchors to grounded story/idea artifacts."
|
|
212
|
+
rows.append(UIGroundingSurfaceResult(
|
|
213
|
+
surface_id=surface.surface_id,
|
|
214
|
+
name=surface.name,
|
|
215
|
+
classification=classification,
|
|
216
|
+
matched_reference_ids=[m.reference_id for m in top_matches],
|
|
217
|
+
supporting_paths=[m.path for m in top_matches],
|
|
218
|
+
next_required_action=next_action,
|
|
219
|
+
))
|
|
220
|
+
else:
|
|
221
|
+
missing.append(surface.name)
|
|
222
|
+
assumptions.append(f"No existing UI reference was found for {surface.name}; DFUS should not improvise this surface.")
|
|
223
|
+
rows.append(UIGroundingSurfaceResult(
|
|
224
|
+
surface_id=surface.surface_id,
|
|
225
|
+
name=surface.name,
|
|
226
|
+
classification="missing_reference",
|
|
227
|
+
unresolved_questions=[f"Which approved screen/spec should ground {surface.name}?"],
|
|
228
|
+
assumptions=[assumptions[-1]],
|
|
229
|
+
next_required_action="Create a UI gap artifact and route to design review or wireframe generation.",
|
|
230
|
+
))
|
|
231
|
+
if missing:
|
|
232
|
+
status = "blocked_missing_ui_grounding"
|
|
233
|
+
gap = UIGapReportArtifact(
|
|
234
|
+
project_id=derivation.project_id,
|
|
235
|
+
idea_id=derivation.idea_id,
|
|
236
|
+
missing_surfaces=missing,
|
|
237
|
+
reasons=[f"No matching existing UI/design reference for {name}." for name in missing],
|
|
238
|
+
recommendation="Needs UI review before DFUS or implementation proceeds for the affected slices.",
|
|
239
|
+
blocked=True,
|
|
240
|
+
)
|
|
241
|
+
summary = f"UI grounding blocked: {len(missing)} required surface(s) are missing durable UI references."
|
|
242
|
+
next_actions = [gap.recommendation]
|
|
243
|
+
else:
|
|
244
|
+
status = "grounded_with_generated_artifacts" if generated_hits else "grounded_ready"
|
|
245
|
+
gap = None
|
|
246
|
+
summary = "All derived UI-bearing surfaces were matched to existing UI/design references."
|
|
247
|
+
if generated_hits:
|
|
248
|
+
summary = f"All derived UI-bearing surfaces were grounded after code-to-design enrichment; {generated_hits} surface(s) now use generated design anchors."
|
|
249
|
+
next_actions = ["Patch the idea/provisional story artifacts with explicit UI anchors."]
|
|
250
|
+
return UIGroundingReportArtifact(
|
|
251
|
+
project_id=derivation.project_id,
|
|
252
|
+
idea_id=derivation.idea_id,
|
|
253
|
+
status=status,
|
|
254
|
+
surfaces=rows,
|
|
255
|
+
summary=summary,
|
|
256
|
+
assumptions=assumptions,
|
|
257
|
+
next_actions=next_actions,
|
|
258
|
+
), gap
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _grounding_report_from_inventory(derivation: UISurfaceDerivationArtifact, inventory: UIReferenceInventoryArtifact, *, prefer_generated: bool) -> tuple[UIGroundingReportArtifact, UIGapReportArtifact | None]:
|
|
262
|
+
return _materialize_report(derivation, inventory, prefer_generated=prefer_generated)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _slug(text: str) -> str:
|
|
266
|
+
pieces = [part for part in re.sub(r"[^a-z0-9]+", "_", text.lower()).strip("_").split("_") if part]
|
|
267
|
+
return "_".join(pieces[:8]) or "screen"
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _surface_class(surface: DerivedUISurface) -> str:
|
|
271
|
+
lowered = f"{surface.name} {' '.join(surface.jobs)}".lower()
|
|
272
|
+
if any(token in lowered for token in ["secure link", "approve quote", "quote approval", "payment", "checkout"]):
|
|
273
|
+
return "customer_secure_link"
|
|
274
|
+
if any(token in lowered for token in ["portal", "account area"]):
|
|
275
|
+
return "customer_portal"
|
|
276
|
+
if any(token in lowered for token in ["technician", "field", "crew", "job site", "tablet"]):
|
|
277
|
+
return "field_tablet"
|
|
278
|
+
if any(token in lowered for token in ["manager", "admin", "dashboard", "reporting", "back office"]):
|
|
279
|
+
return "admin_web"
|
|
280
|
+
if any(token in lowered for token in ["support", "need help", "csr"]):
|
|
281
|
+
return "internal_support_tool"
|
|
282
|
+
if any(token in lowered for token in ["participant", "survey", "form", "vibe check", "page", "flow"]):
|
|
283
|
+
return "general_web"
|
|
284
|
+
if not surface.ui_bearing:
|
|
285
|
+
return "non_ui_or_backend_only"
|
|
286
|
+
return "general_web"
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _surface_platform(surface_class: str) -> str:
|
|
290
|
+
if surface_class == "field_tablet":
|
|
291
|
+
return "tablet"
|
|
292
|
+
if surface_class in {"customer_secure_link", "customer_portal"}:
|
|
293
|
+
return "web_mobile_responsive"
|
|
294
|
+
if surface_class == "non_ui_or_backend_only":
|
|
295
|
+
return "not_applicable"
|
|
296
|
+
return "web_desktop"
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _target_audience(surface_class: str) -> str:
|
|
300
|
+
if surface_class == "non_ui_or_backend_only":
|
|
301
|
+
return "internal_exploration"
|
|
302
|
+
return "customer_review"
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _target_fidelity(surface_class: str, audience: str) -> str:
|
|
306
|
+
if audience == "customer_review" and surface_class in {"customer_secure_link", "customer_portal", "admin_web", "field_tablet", "general_web"}:
|
|
307
|
+
return "medium"
|
|
308
|
+
return "low"
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _journey_stage(surface: DerivedUISurface) -> str:
|
|
312
|
+
lowered = f"{surface.name} {' '.join(surface.jobs)}".lower()
|
|
313
|
+
if "approve" in lowered or "approval" in lowered:
|
|
314
|
+
return "review_and_approve"
|
|
315
|
+
if "dashboard" in lowered or "report" in lowered:
|
|
316
|
+
return "monitor_and_review"
|
|
317
|
+
if "survey" in lowered or "form" in lowered or "check" in lowered:
|
|
318
|
+
return "capture_input"
|
|
319
|
+
if "need help" in lowered or "support" in lowered:
|
|
320
|
+
return "request_support"
|
|
321
|
+
return "primary_flow"
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _shared_shell_id(surface_class: str) -> str | None:
|
|
325
|
+
return {
|
|
326
|
+
"admin_web": "admin_shell",
|
|
327
|
+
"customer_secure_link": "secure_link_shell",
|
|
328
|
+
"customer_portal": "customer_portal_shell",
|
|
329
|
+
"field_tablet": "field_ops_shell",
|
|
330
|
+
"internal_support_tool": "support_shell",
|
|
331
|
+
"general_web": "default_web_shell",
|
|
332
|
+
}.get(surface_class)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _build_ui_structure_contract(*, context: UIGroundingContextArtifact, derivation: UISurfaceDerivationArtifact, report: UIGroundingReportArtifact) -> tuple[UIStructureContractArtifact, UIFlowMapArtifact, UIScreenRelationshipsArtifact, UIGenerationManifestArtifact]:
|
|
336
|
+
by_surface = {surface.surface_id: surface for surface in derivation.surfaces}
|
|
337
|
+
screens: list[UIStructureScreenItem] = []
|
|
338
|
+
flows: dict[str, list[str]] = {}
|
|
339
|
+
relationships: list[dict[str, Any]] = []
|
|
340
|
+
unresolved: list[str] = []
|
|
341
|
+
|
|
342
|
+
for row in report.surfaces:
|
|
343
|
+
surface = by_surface.get(row.surface_id)
|
|
344
|
+
if surface is None:
|
|
345
|
+
continue
|
|
346
|
+
surface_class = _surface_class(surface)
|
|
347
|
+
audience = _target_audience(surface_class)
|
|
348
|
+
fidelity = _target_fidelity(surface_class, audience)
|
|
349
|
+
flow_id = f"flow_{_journey_stage(surface)}"
|
|
350
|
+
screen_id = _slug(surface.name)
|
|
351
|
+
confidence = surface.confidence
|
|
352
|
+
confidence_reason = "Grounded to approved references." if row.classification in {"grounded_existing", "grounded_with_generated_artifacts"} else "Still missing approved grounding references."
|
|
353
|
+
assumption_tags = [
|
|
354
|
+
"assume_customer_facing_review" if audience == "customer_review" else "assume_internal_exploration",
|
|
355
|
+
"assume_medium_fidelity_minimum" if fidelity == "medium" else "assume_low_fidelity_allowed",
|
|
356
|
+
]
|
|
357
|
+
notes = []
|
|
358
|
+
if fidelity == "medium":
|
|
359
|
+
notes.append("Use realistic labels, hierarchy, and CTA placement rather than skeletal placeholder boxes.")
|
|
360
|
+
if row.classification == "grounded_with_generated_artifacts":
|
|
361
|
+
notes.append("Generated design anchors should still receive review before implementation handoff.")
|
|
362
|
+
grounding_refs = []
|
|
363
|
+
for path in row.supporting_paths:
|
|
364
|
+
grounding_refs.append(path if path.startswith("pencil:") else f"ui:file:{path}")
|
|
365
|
+
screen = UIStructureScreenItem(
|
|
366
|
+
surface_id=row.surface_id,
|
|
367
|
+
screen_id=screen_id,
|
|
368
|
+
name=surface.name,
|
|
369
|
+
surface_class=surface_class,
|
|
370
|
+
platform=_surface_platform(surface_class),
|
|
371
|
+
actor=surface.actor,
|
|
372
|
+
journey_stage=_journey_stage(surface),
|
|
373
|
+
required=surface.ui_bearing,
|
|
374
|
+
source_refs=surface.source_refs,
|
|
375
|
+
grounding_refs=grounding_refs,
|
|
376
|
+
target_fidelity=fidelity,
|
|
377
|
+
target_audience=audience,
|
|
378
|
+
confidence=confidence,
|
|
379
|
+
confidence_reason=confidence_reason,
|
|
380
|
+
open_questions=list(row.unresolved_questions),
|
|
381
|
+
assumption_tags=assumption_tags,
|
|
382
|
+
notes=notes,
|
|
383
|
+
flow_id=flow_id,
|
|
384
|
+
shell_id=_shared_shell_id(surface_class),
|
|
385
|
+
)
|
|
386
|
+
screens.append(screen)
|
|
387
|
+
flows.setdefault(flow_id, []).append(screen_id)
|
|
388
|
+
if row.classification == "missing_reference":
|
|
389
|
+
unresolved.extend(row.unresolved_questions)
|
|
390
|
+
if screen.shell_id:
|
|
391
|
+
relationships.append({"type": "shared_shell", "from": screen_id, "to": screen.shell_id})
|
|
392
|
+
|
|
393
|
+
ordered_flow_ids = sorted(flows)
|
|
394
|
+
screen_index = {screen.screen_id: screen for screen in screens}
|
|
395
|
+
for flow_id, ids in flows.items():
|
|
396
|
+
for current, nxt in zip(ids, ids[1:]):
|
|
397
|
+
screen_index[current].dependency_screen_ids.append(nxt)
|
|
398
|
+
relationships.append({"type": "flow_transition", "from": current, "to": nxt, "flow_id": flow_id})
|
|
399
|
+
|
|
400
|
+
status = "non_ui_only" if not any(screen.required for screen in screens) else ("needs_ui_review" if report.status == "blocked_missing_ui_grounding" else "ready_for_generation")
|
|
401
|
+
shared_shells = [
|
|
402
|
+
{"shell_id": shell_id, "screen_ids": [screen.screen_id for screen in screens if screen.shell_id == shell_id]}
|
|
403
|
+
for shell_id in sorted({screen.shell_id for screen in screens if screen.shell_id})
|
|
404
|
+
]
|
|
405
|
+
structure = UIStructureContractArtifact(
|
|
406
|
+
project_id=context.project_id,
|
|
407
|
+
idea_id=context.idea_id,
|
|
408
|
+
status=status,
|
|
409
|
+
default_minimum_fidelity="medium",
|
|
410
|
+
primary_audience="customer_review",
|
|
411
|
+
screens=screens,
|
|
412
|
+
flow_order=ordered_flow_ids,
|
|
413
|
+
shared_shells=shared_shells,
|
|
414
|
+
structural_notes=[
|
|
415
|
+
"Planning/discovery decides what surfaces exist; structure contract decides how the approved screen set coheres before generation.",
|
|
416
|
+
"Customer-facing and stakeholder-review surfaces default to medium fidelity minimum.",
|
|
417
|
+
],
|
|
418
|
+
unresolved_questions=sorted(dict.fromkeys(unresolved)),
|
|
419
|
+
)
|
|
420
|
+
flow_map = UIFlowMapArtifact(
|
|
421
|
+
project_id=context.project_id,
|
|
422
|
+
idea_id=context.idea_id,
|
|
423
|
+
flows=[{"flow_id": flow_id, "screen_ids": ids} for flow_id, ids in flows.items()],
|
|
424
|
+
)
|
|
425
|
+
screen_relationships = UIScreenRelationshipsArtifact(
|
|
426
|
+
project_id=context.project_id,
|
|
427
|
+
idea_id=context.idea_id,
|
|
428
|
+
relationships=relationships,
|
|
429
|
+
)
|
|
430
|
+
generation_manifest = UIGenerationManifestArtifact(
|
|
431
|
+
project_id=context.project_id,
|
|
432
|
+
idea_id=context.idea_id,
|
|
433
|
+
primary_audience="customer_review",
|
|
434
|
+
default_minimum_fidelity="medium",
|
|
435
|
+
review_packet_outputs=[
|
|
436
|
+
"wireframe_index.md",
|
|
437
|
+
"customer_review_packet.json",
|
|
438
|
+
"screen_comment_matrix.json",
|
|
439
|
+
"screen_inventory_snapshot.json",
|
|
440
|
+
],
|
|
441
|
+
screen_generation_items=[
|
|
442
|
+
{
|
|
443
|
+
"screen_id": screen.screen_id,
|
|
444
|
+
"surface_id": screen.surface_id,
|
|
445
|
+
"target_fidelity": screen.target_fidelity,
|
|
446
|
+
"target_audience": screen.target_audience,
|
|
447
|
+
"grounding_refs": screen.grounding_refs,
|
|
448
|
+
"flow_id": screen.flow_id,
|
|
449
|
+
}
|
|
450
|
+
for screen in screens if screen.required
|
|
451
|
+
],
|
|
452
|
+
)
|
|
453
|
+
return structure, flow_map, screen_relationships, generation_manifest
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _write_report_and_gap(pipeline_root: Path, artifact: UIGroundingReportArtifact, gap: UIGapReportArtifact | None) -> None:
|
|
457
|
+
report_path = pipeline_root / "ui_grounding_report.json"
|
|
458
|
+
_write_json(report_path, artifact.model_dump())
|
|
459
|
+
gap_path = pipeline_root / "ui_gap_report.json"
|
|
460
|
+
if gap is not None:
|
|
461
|
+
_write_json(gap_path, gap.model_dump())
|
|
462
|
+
elif gap_path.exists():
|
|
463
|
+
gap_path.unlink()
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
class LoadUIGroundingContextNode(Node):
|
|
467
|
+
async def process(self, task_context: TaskContext) -> TaskContext:
|
|
468
|
+
event = task_context.event
|
|
469
|
+
_dfs_running(project_id=event.project_id, run_id=_store_run()[1], summary="Reviewing existing UI", idea_id=event.idea_id)
|
|
470
|
+
repo_root = Path(event.repo_root)
|
|
471
|
+
idea_payload = dict(event.idea_inline or {})
|
|
472
|
+
idea_path = None
|
|
473
|
+
if event.idea_path:
|
|
474
|
+
idea_path = repo_root / event.idea_path if not Path(event.idea_path).is_absolute() else Path(event.idea_path)
|
|
475
|
+
idea_payload = _load_json(idea_path)
|
|
476
|
+
story_paths: list[str] = []
|
|
477
|
+
story_summaries: list[str] = []
|
|
478
|
+
for raw in event.story_paths:
|
|
479
|
+
path = repo_root / raw if not Path(raw).is_absolute() else Path(raw)
|
|
480
|
+
story_paths.append(str(path))
|
|
481
|
+
try:
|
|
482
|
+
payload = _load_json(path)
|
|
483
|
+
story_summaries.append(str(payload.get("title") or payload.get("user_value_statement") or path.name))
|
|
484
|
+
except Exception:
|
|
485
|
+
story_summaries.append(path.name)
|
|
486
|
+
design_paths = [str((repo_root / p if not Path(p).is_absolute() else Path(p))) for p in event.design_paths]
|
|
487
|
+
context = UIGroundingContextArtifact(
|
|
488
|
+
project_id=event.project_id,
|
|
489
|
+
idea_id=event.idea_id,
|
|
490
|
+
idea_title=str(idea_payload.get("title") or event.idea_id),
|
|
491
|
+
idea_summary=str(idea_payload.get("summary") or idea_payload.get("goal") or idea_payload.get("problem") or idea_payload),
|
|
492
|
+
idea_path=str(idea_path) if idea_path else None,
|
|
493
|
+
story_paths=story_paths,
|
|
494
|
+
story_summaries=story_summaries,
|
|
495
|
+
design_paths=design_paths,
|
|
496
|
+
repo_root=str(repo_root),
|
|
497
|
+
upstream_refs=[event.idea_path] if event.idea_path else [f"idea:{event.idea_id}"],
|
|
498
|
+
)
|
|
499
|
+
pipeline_root = _pipeline_root(repo_root, idea_id=event.idea_id, pipeline_key=event.pipeline_key)
|
|
500
|
+
_write_json(pipeline_root / "ui_grounding_context.json", context.model_dump())
|
|
501
|
+
task_context.metadata["pipeline_root"] = str(pipeline_root)
|
|
502
|
+
task_context.metadata["ui_grounding_context"] = context
|
|
503
|
+
task_context.metadata["allow_code_design_enrichment"] = bool(getattr(event, "allow_code_design_enrichment", False))
|
|
504
|
+
task_context.metadata["pencil_app_path"] = getattr(event, "pencil_app_path", None)
|
|
505
|
+
self.save_output(context)
|
|
506
|
+
return task_context
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
class DeriveUISurfacesFromIdeaNode(AgentNode):
|
|
510
|
+
def get_agent_config(self) -> AgentConfig:
|
|
511
|
+
return AgentConfig(instructions="Derive the bounded set of UI-bearing surfaces implied by the approved idea/stories.", output_type=UISurfaceDerivationArtifact)
|
|
512
|
+
|
|
513
|
+
async def process(self, task_context: TaskContext) -> TaskContext:
|
|
514
|
+
_dfs_running(project_id=task_context.event.project_id, run_id=_store_run()[1], summary="Mapping screens and flows", idea_id=task_context.event.idea_id)
|
|
515
|
+
context = task_context.metadata["ui_grounding_context"]
|
|
516
|
+
repo_root = Path(task_context.event.repo_root)
|
|
517
|
+
pipeline_root = Path(task_context.metadata["pipeline_root"])
|
|
518
|
+
artifact, envelope = ui_grounding_agentic.run_ui_grounding_agent_step(
|
|
519
|
+
repo_root=repo_root,
|
|
520
|
+
stage_name="derive_ui_surfaces",
|
|
521
|
+
output_model=UISurfaceDerivationArtifact,
|
|
522
|
+
context_payload=context.model_dump(),
|
|
523
|
+
guidance=["Return only surfaces that are explicitly or strongly implied by the approved idea/story inputs.", "Mark non-UI slices explicitly rather than pretending they need screens."],
|
|
524
|
+
timeout_seconds=1800,
|
|
525
|
+
)
|
|
526
|
+
ui_grounding_agentic.persist_agent_run(pipeline_root=pipeline_root, node_id="derive_ui_surfaces", envelope=envelope)
|
|
527
|
+
artifact = UISurfaceDerivationArtifact.model_validate(artifact.model_dump())
|
|
528
|
+
_write_json(pipeline_root / "ui_surface_derivation.json", artifact.model_dump())
|
|
529
|
+
task_context.metadata["ui_surface_derivation"] = artifact
|
|
530
|
+
self.save_output(artifact)
|
|
531
|
+
return task_context
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
class InventoryExistingUIReferencesNode(Node):
|
|
535
|
+
async def process(self, task_context: TaskContext) -> TaskContext:
|
|
536
|
+
context = task_context.metadata["ui_grounding_context"]
|
|
537
|
+
repo_root = Path(task_context.event.repo_root)
|
|
538
|
+
refs: list[UIReferenceInventoryItem] = []
|
|
539
|
+
seen: set[str] = set()
|
|
540
|
+
for path in [*(_candidate_ui_files(repo_root)), *(Path(p) for p in context.design_paths if Path(p).exists())]:
|
|
541
|
+
rel = path.relative_to(repo_root).as_posix() if path.is_relative_to(repo_root) else str(path)
|
|
542
|
+
if rel in seen:
|
|
543
|
+
continue
|
|
544
|
+
seen.add(rel)
|
|
545
|
+
refs.append(UIReferenceInventoryItem(
|
|
546
|
+
reference_id=_stable_id("uiref_", {"path": rel}),
|
|
547
|
+
reference_type=_classify_reference_type(path),
|
|
548
|
+
path=rel,
|
|
549
|
+
surface_labels=_labels_for_path(path),
|
|
550
|
+
provenance=["repo_scan" if str(path).startswith(str(repo_root)) else "design_input"],
|
|
551
|
+
approval_status="approved" if path.suffix == ".pen" else "unknown",
|
|
552
|
+
))
|
|
553
|
+
artifact = UIReferenceInventoryArtifact(
|
|
554
|
+
project_id=context.project_id,
|
|
555
|
+
idea_id=context.idea_id,
|
|
556
|
+
references=refs,
|
|
557
|
+
inventory_notes=[f"Inventoried {len(refs)} UI/design reference candidate(s)."],
|
|
558
|
+
)
|
|
559
|
+
_write_json(Path(task_context.metadata["pipeline_root"]) / "ui_reference_inventory.json", artifact.model_dump())
|
|
560
|
+
task_context.metadata["ui_reference_inventory"] = artifact
|
|
561
|
+
self.save_output(artifact)
|
|
562
|
+
return task_context
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
class EnsurePencilAvailableNode(Node):
|
|
566
|
+
async def process(self, task_context: TaskContext) -> TaskContext:
|
|
567
|
+
_dfs_running(project_id=task_context.event.project_id, run_id=_store_run()[1], summary="Preparing UI generation", idea_id=task_context.event.idea_id)
|
|
568
|
+
pipeline_root = Path(task_context.metadata["pipeline_root"])
|
|
569
|
+
allow = bool(task_context.metadata.get("allow_code_design_enrichment"))
|
|
570
|
+
artifact_path = pipeline_root / "pencil_preflight.json"
|
|
571
|
+
if not allow:
|
|
572
|
+
payload = {
|
|
573
|
+
"ok": False,
|
|
574
|
+
"status": "not_requested",
|
|
575
|
+
"repo_root": str(task_context.event.repo_root),
|
|
576
|
+
"artifact_path": str(artifact_path),
|
|
577
|
+
"errors": [],
|
|
578
|
+
}
|
|
579
|
+
_write_json(artifact_path, payload)
|
|
580
|
+
task_context.metadata["pencil_preflight"] = payload
|
|
581
|
+
self.save_output(payload)
|
|
582
|
+
return task_context
|
|
583
|
+
result = run_pencil_preflight(
|
|
584
|
+
repo_root=Path(task_context.event.repo_root),
|
|
585
|
+
store=None,
|
|
586
|
+
app_path=Path(task_context.metadata["pencil_app_path"]) if task_context.metadata.get("pencil_app_path") else None,
|
|
587
|
+
artifact_path=artifact_path,
|
|
588
|
+
)
|
|
589
|
+
task_context.metadata["pencil_preflight"] = result.artifact.model_dump()
|
|
590
|
+
self.save_output(result.artifact)
|
|
591
|
+
return task_context
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
class MatchUISurfacesToReferencesNode(AgentNode):
|
|
595
|
+
def get_agent_config(self) -> AgentConfig:
|
|
596
|
+
return AgentConfig(instructions="Match derived UI surfaces to the best existing UI/design references.", output_type=UIGroundingReportArtifact)
|
|
597
|
+
|
|
598
|
+
async def process(self, task_context: TaskContext) -> TaskContext:
|
|
599
|
+
_dfs_running(project_id=task_context.event.project_id, run_id=_store_run()[1], summary="Matching UI references", idea_id=task_context.event.idea_id)
|
|
600
|
+
repo_root = Path(task_context.event.repo_root)
|
|
601
|
+
pipeline_root = Path(task_context.metadata["pipeline_root"])
|
|
602
|
+
derivation = task_context.metadata["ui_surface_derivation"]
|
|
603
|
+
inventory = task_context.metadata["ui_reference_inventory"]
|
|
604
|
+
deterministic_report, gap = _grounding_report_from_inventory(derivation, inventory, prefer_generated=False)
|
|
605
|
+
artifact, envelope = ui_grounding_agentic.run_ui_grounding_agent_step(
|
|
606
|
+
repo_root=repo_root,
|
|
607
|
+
stage_name="match_ui_surfaces",
|
|
608
|
+
output_model=UIGroundingReportArtifact,
|
|
609
|
+
context_payload={"ui_surface_derivation": derivation.model_dump(), "ui_reference_inventory": inventory.model_dump(), "deterministic_hints": deterministic_report.model_dump()},
|
|
610
|
+
guidance=["Classify each derived surface as grounded_existing, grounded_partial, missing_reference, or non_ui_or_backend_only.", "If a required UI surface has no durable reference, do not mark the slice grounded."],
|
|
611
|
+
timeout_seconds=1800,
|
|
612
|
+
)
|
|
613
|
+
ui_grounding_agentic.persist_agent_run(pipeline_root=pipeline_root, node_id="match_ui_surfaces", envelope=envelope)
|
|
614
|
+
artifact = UIGroundingReportArtifact.model_validate(artifact.model_dump())
|
|
615
|
+
_write_report_and_gap(pipeline_root, artifact, gap)
|
|
616
|
+
task_context.metadata["ui_grounding_report_first_pass"] = artifact
|
|
617
|
+
task_context.metadata["ui_gap_report"] = gap
|
|
618
|
+
task_context.metadata["ui_grounding_report"] = artifact
|
|
619
|
+
self.save_output(artifact)
|
|
620
|
+
return task_context
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
class EnrichCodeDesignInventoryNode(Node):
|
|
624
|
+
async def process(self, task_context: TaskContext) -> TaskContext:
|
|
625
|
+
pipeline_root = Path(task_context.metadata["pipeline_root"])
|
|
626
|
+
report: UIGroundingReportArtifact = task_context.metadata["ui_grounding_report_first_pass"]
|
|
627
|
+
inventory: UIReferenceInventoryArtifact = task_context.metadata["ui_reference_inventory"]
|
|
628
|
+
preflight = dict(task_context.metadata.get("pencil_preflight") or {})
|
|
629
|
+
missing_surface_ids = [row.surface_id for row in report.surfaces if row.classification in {"missing_reference", "grounded_partial"}]
|
|
630
|
+
if not bool(task_context.metadata.get("allow_code_design_enrichment")):
|
|
631
|
+
artifact = CodeDesignInventoryArtifact(project_id=report.project_id, idea_id=report.idea_id, status="not_requested", notes=["Code-to-design enrichment not requested for this run."])
|
|
632
|
+
elif preflight.get("status") != "ready":
|
|
633
|
+
artifact = CodeDesignInventoryArtifact(project_id=report.project_id, idea_id=report.idea_id, status="skipped_preflight_not_ready", target_surface_ids=missing_surface_ids, notes=[f"Pencil preflight status {preflight.get('status', 'unknown')} prevented enrichment."])
|
|
634
|
+
elif not missing_surface_ids:
|
|
635
|
+
artifact = CodeDesignInventoryArtifact(project_id=report.project_id, idea_id=report.idea_id, status="skipped_no_missing_surfaces", notes=["First-pass matching already grounded all required UI surfaces."])
|
|
636
|
+
else:
|
|
637
|
+
derivation: UISurfaceDerivationArtifact = task_context.metadata["ui_surface_derivation"]
|
|
638
|
+
generated: list[CodeDesignGeneratedReference] = []
|
|
639
|
+
for surface in derivation.surfaces:
|
|
640
|
+
if surface.surface_id not in missing_surface_ids:
|
|
641
|
+
continue
|
|
642
|
+
stoks = set(_tokens(f"{surface.name} {' '.join(surface.jobs)}"))
|
|
643
|
+
candidates = [
|
|
644
|
+
ref for ref in inventory.references
|
|
645
|
+
if ref.reference_type == "implemented_ui" and stoks and len(stoks & set(_tokens(' '.join([*ref.surface_labels, ref.path])))) >= 1
|
|
646
|
+
]
|
|
647
|
+
if not candidates:
|
|
648
|
+
continue
|
|
649
|
+
source_paths = [ref.path for ref in candidates[:3]]
|
|
650
|
+
generated_anchor = f"pencil:code_design_inventory.json#{surface.surface_id}"
|
|
651
|
+
normalized = UIReferenceInventoryItem(
|
|
652
|
+
reference_id=_stable_id("pencilref_", {"surface_id": surface.surface_id, "source_paths": source_paths}),
|
|
653
|
+
reference_type="pencil_artifact",
|
|
654
|
+
path=generated_anchor,
|
|
655
|
+
surface_labels=[surface.name, *surface.jobs[:1]],
|
|
656
|
+
provenance=[*source_paths, "code_to_design"],
|
|
657
|
+
approval_status="generated_from_code",
|
|
658
|
+
)
|
|
659
|
+
generated.append(CodeDesignGeneratedReference(
|
|
660
|
+
surface_id=surface.surface_id,
|
|
661
|
+
source_paths=source_paths,
|
|
662
|
+
generated_reference_id=normalized.reference_id,
|
|
663
|
+
generated_anchor=generated_anchor,
|
|
664
|
+
generation_kind="full_screen",
|
|
665
|
+
review_needed=False,
|
|
666
|
+
normalized_reference=normalized,
|
|
667
|
+
))
|
|
668
|
+
if generated:
|
|
669
|
+
artifact = CodeDesignInventoryArtifact(
|
|
670
|
+
project_id=report.project_id,
|
|
671
|
+
idea_id=report.idea_id,
|
|
672
|
+
status="generated",
|
|
673
|
+
target_surface_ids=missing_surface_ids,
|
|
674
|
+
generated_references=generated,
|
|
675
|
+
notes=[f"Generated {len(generated)} code-derived design reference(s) from implemented UI evidence."],
|
|
676
|
+
)
|
|
677
|
+
else:
|
|
678
|
+
artifact = CodeDesignInventoryArtifact(
|
|
679
|
+
project_id=report.project_id,
|
|
680
|
+
idea_id=report.idea_id,
|
|
681
|
+
status="skipped_no_relevant_implemented_ui",
|
|
682
|
+
target_surface_ids=missing_surface_ids,
|
|
683
|
+
notes=["No relevant implemented_ui references were available for code-to-design enrichment."],
|
|
684
|
+
)
|
|
685
|
+
_write_json(pipeline_root / "code_design_inventory.json", artifact.model_dump())
|
|
686
|
+
task_context.metadata["code_design_inventory"] = artifact
|
|
687
|
+
self.save_output(artifact)
|
|
688
|
+
return task_context
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
class RerunMatchingAfterCodeDesignNode(Node):
|
|
692
|
+
async def process(self, task_context: TaskContext) -> TaskContext:
|
|
693
|
+
pipeline_root = Path(task_context.metadata["pipeline_root"])
|
|
694
|
+
derivation: UISurfaceDerivationArtifact = task_context.metadata["ui_surface_derivation"]
|
|
695
|
+
inventory: UIReferenceInventoryArtifact = task_context.metadata["ui_reference_inventory"]
|
|
696
|
+
first_report: UIGroundingReportArtifact = task_context.metadata["ui_grounding_report_first_pass"]
|
|
697
|
+
code_design: CodeDesignInventoryArtifact = task_context.metadata["code_design_inventory"]
|
|
698
|
+
if code_design.status != "generated":
|
|
699
|
+
artifact = first_report
|
|
700
|
+
gap = task_context.metadata.get("ui_gap_report")
|
|
701
|
+
effective_inventory = inventory
|
|
702
|
+
else:
|
|
703
|
+
effective_inventory = UIReferenceInventoryArtifact(
|
|
704
|
+
project_id=inventory.project_id,
|
|
705
|
+
idea_id=inventory.idea_id,
|
|
706
|
+
references=[*inventory.references, *(item.normalized_reference for item in code_design.generated_references)],
|
|
707
|
+
inventory_notes=[*inventory.inventory_notes, f"Merged {len(code_design.generated_references)} code-derived design reference(s) for second-pass matching."],
|
|
708
|
+
)
|
|
709
|
+
artifact, gap = _materialize_report(derivation, effective_inventory, prefer_generated=True)
|
|
710
|
+
_write_json(pipeline_root / "effective_ui_reference_inventory.json", effective_inventory.model_dump())
|
|
711
|
+
_write_report_and_gap(pipeline_root, artifact, gap)
|
|
712
|
+
task_context.metadata["effective_ui_reference_inventory"] = effective_inventory
|
|
713
|
+
task_context.metadata["ui_grounding_report"] = artifact
|
|
714
|
+
task_context.metadata["ui_gap_report"] = gap
|
|
715
|
+
self.save_output(artifact)
|
|
716
|
+
return task_context
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
class IntegrateUIScreenStructureNode(Node):
|
|
720
|
+
async def process(self, task_context: TaskContext) -> TaskContext:
|
|
721
|
+
pipeline_root = Path(task_context.metadata["pipeline_root"])
|
|
722
|
+
context: UIGroundingContextArtifact = task_context.metadata["ui_grounding_context"]
|
|
723
|
+
derivation: UISurfaceDerivationArtifact = task_context.metadata["ui_surface_derivation"]
|
|
724
|
+
report: UIGroundingReportArtifact = task_context.metadata["ui_grounding_report"]
|
|
725
|
+
structure, flow_map, relationships, manifest = _build_ui_structure_contract(
|
|
726
|
+
context=context,
|
|
727
|
+
derivation=derivation,
|
|
728
|
+
report=report,
|
|
729
|
+
)
|
|
730
|
+
_write_json(pipeline_root / "ui_structure_contract.json", structure.model_dump())
|
|
731
|
+
_write_json(pipeline_root / "ui_flow_map.json", flow_map.model_dump())
|
|
732
|
+
_write_json(pipeline_root / "ui_screen_relationships.json", relationships.model_dump())
|
|
733
|
+
_write_json(pipeline_root / "ui_generation_manifest.json", manifest.model_dump())
|
|
734
|
+
task_context.metadata["ui_structure_contract"] = structure
|
|
735
|
+
task_context.metadata["ui_flow_map"] = flow_map
|
|
736
|
+
task_context.metadata["ui_screen_relationships"] = relationships
|
|
737
|
+
task_context.metadata["ui_generation_manifest"] = manifest
|
|
738
|
+
self.save_output(structure)
|
|
739
|
+
return task_context
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
class PatchStoryAndIdeaUIReferencesNode(Node):
|
|
743
|
+
async def process(self, task_context: TaskContext) -> TaskContext:
|
|
744
|
+
report: UIGroundingReportArtifact = task_context.metadata["ui_grounding_report"]
|
|
745
|
+
effective_inventory: UIReferenceInventoryArtifact = task_context.metadata.get("effective_ui_reference_inventory") or task_context.metadata["ui_reference_inventory"]
|
|
746
|
+
inventory_by_id = {item.reference_id: item for item in effective_inventory.references}
|
|
747
|
+
anchors: list[dict[str, Any]] = []
|
|
748
|
+
evidence: list[str] = []
|
|
749
|
+
anchor_kinds: set[str] = set()
|
|
750
|
+
for row in report.surfaces:
|
|
751
|
+
for ref_id, path in zip(row.matched_reference_ids, row.supporting_paths):
|
|
752
|
+
item = inventory_by_id.get(ref_id)
|
|
753
|
+
if item is not None and item.reference_type == "pencil_artifact":
|
|
754
|
+
anchor = path
|
|
755
|
+
anchor_type = "pencil"
|
|
756
|
+
else:
|
|
757
|
+
anchor = f"ui:file:{path}"
|
|
758
|
+
anchor_type = "ui:file"
|
|
759
|
+
anchors.append({"surface_id": row.surface_id, "surface": row.name, "anchor": anchor, "anchor_type": anchor_type, "reference_id": ref_id})
|
|
760
|
+
evidence.append(ref_id)
|
|
761
|
+
anchor_kinds.add(anchor_type)
|
|
762
|
+
if anchor_kinds == {"pencil"}:
|
|
763
|
+
origin = "generated_ui"
|
|
764
|
+
elif "pencil" in anchor_kinds and "ui:file" in anchor_kinds:
|
|
765
|
+
origin = "mixed"
|
|
766
|
+
else:
|
|
767
|
+
origin = "existing_ui"
|
|
768
|
+
patch = GroundedStoryReferencePatchArtifact(
|
|
769
|
+
project_id=report.project_id,
|
|
770
|
+
idea_id=report.idea_id,
|
|
771
|
+
target_refs=[task_context.event.idea_path or f"idea:{report.idea_id}", *task_context.event.story_paths],
|
|
772
|
+
attached_ui_anchors=anchors,
|
|
773
|
+
supporting_evidence_refs=sorted(set(evidence)),
|
|
774
|
+
origin=origin,
|
|
775
|
+
)
|
|
776
|
+
_write_json(Path(task_context.metadata["pipeline_root"]) / "grounded_story_reference_patch.json", patch.model_dump())
|
|
777
|
+
task_context.metadata["grounded_story_reference_patch"] = patch
|
|
778
|
+
self.save_output(patch)
|
|
779
|
+
return task_context
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
class FinalizeUIGroundingOutcomeNode(Node):
|
|
783
|
+
async def process(self, task_context: TaskContext) -> TaskContext:
|
|
784
|
+
report: UIGroundingReportArtifact = task_context.metadata["ui_grounding_report"]
|
|
785
|
+
patch: GroundedStoryReferencePatchArtifact = task_context.metadata["grounded_story_reference_patch"]
|
|
786
|
+
outcome = {
|
|
787
|
+
"project_id": report.project_id,
|
|
788
|
+
"idea_id": report.idea_id,
|
|
789
|
+
"status": report.status,
|
|
790
|
+
"surface_count": len(report.surfaces),
|
|
791
|
+
"grounded_surface_count": sum(1 for row in report.surfaces if row.classification in {"grounded_existing", "grounded_with_generated_artifacts"}),
|
|
792
|
+
"missing_surface_count": sum(1 for row in report.surfaces if row.classification == "missing_reference"),
|
|
793
|
+
"anchor_count": len(patch.attached_ui_anchors),
|
|
794
|
+
}
|
|
795
|
+
exit_code = 0 if report.status in {"grounded_ready", "grounded_with_generated_artifacts", "pass_with_assumptions"} else 2
|
|
796
|
+
summary = UIGroundingDagSummary(
|
|
797
|
+
exit_code=exit_code,
|
|
798
|
+
run_id=_store_run()[1],
|
|
799
|
+
pipeline_dir=str(task_context.metadata["pipeline_root"]),
|
|
800
|
+
message="ui grounding run complete",
|
|
801
|
+
outcome=outcome,
|
|
802
|
+
)
|
|
803
|
+
_write_json(Path(task_context.metadata["pipeline_root"]) / "summary.json", summary.model_dump())
|
|
804
|
+
task_context.metadata["message"] = json.dumps({**outcome, "run_id": summary.run_id, "pipeline_dir": summary.pipeline_dir}, sort_keys=True) + "\n"
|
|
805
|
+
task_context.metadata["outcome"] = outcome
|
|
806
|
+
task_context.metadata["exit_code"] = exit_code
|
|
807
|
+
self.save_output(summary)
|
|
808
|
+
return task_context
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
class UIGroundingWorkflow(Workflow):
|
|
812
|
+
workflow_schema = WorkflowSchema(
|
|
813
|
+
description="UI grounding DAG Phase 2 (load context -> derive surfaces -> inventory refs -> Pencil gate -> first pass -> code-design enrichment -> second pass -> integrate screen structure -> patch -> finalize)",
|
|
814
|
+
event_schema=UIGroundingDagEvent,
|
|
815
|
+
start=LoadUIGroundingContextNode,
|
|
816
|
+
nodes=[
|
|
817
|
+
NodeConfig(node=LoadUIGroundingContextNode, connections=[DeriveUISurfacesFromIdeaNode]),
|
|
818
|
+
NodeConfig(node=DeriveUISurfacesFromIdeaNode, connections=[InventoryExistingUIReferencesNode]),
|
|
819
|
+
NodeConfig(node=InventoryExistingUIReferencesNode, connections=[EnsurePencilAvailableNode]),
|
|
820
|
+
NodeConfig(node=EnsurePencilAvailableNode, connections=[MatchUISurfacesToReferencesNode]),
|
|
821
|
+
NodeConfig(node=MatchUISurfacesToReferencesNode, connections=[EnrichCodeDesignInventoryNode]),
|
|
822
|
+
NodeConfig(node=EnrichCodeDesignInventoryNode, connections=[RerunMatchingAfterCodeDesignNode]),
|
|
823
|
+
NodeConfig(node=RerunMatchingAfterCodeDesignNode, connections=[IntegrateUIScreenStructureNode]),
|
|
824
|
+
NodeConfig(node=IntegrateUIScreenStructureNode, connections=[PatchStoryAndIdeaUIReferencesNode]),
|
|
825
|
+
NodeConfig(node=PatchStoryAndIdeaUIReferencesNode, connections=[FinalizeUIGroundingOutcomeNode]),
|
|
826
|
+
NodeConfig(node=FinalizeUIGroundingOutcomeNode, connections=[]),
|
|
827
|
+
],
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
def build_pipeline_key(*, repo_root: Path, project_id: str, idea_id: str, idea_payload: dict[str, Any], story_paths: list[str], design_paths: list[str], allow_code_design_enrichment: bool = False) -> str:
|
|
832
|
+
return _stable_id("run_", {"repo_root": str(repo_root), "project_id": project_id, "idea_id": idea_id, "idea_payload": idea_payload, "story_paths": story_paths, "design_paths": design_paths, "allow_code_design_enrichment": allow_code_design_enrichment})
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
def run_ui_grounding_dag(*, repo_root: Path, store: ExecutionStore, project_id: str, idea_id: str, idea_path: Path | None = None, idea_inline: dict[str, Any] | None = None, story_paths: list[Path] | None = None, design_paths: list[Path] | None = None, allow_code_design_enrichment: bool = False, pencil_app_path: Path | None = None) -> UIGroundingDagResult:
|
|
836
|
+
payload = dict(idea_inline or {})
|
|
837
|
+
if idea_path is not None:
|
|
838
|
+
payload = _load_json(idea_path)
|
|
839
|
+
pipeline_key = build_pipeline_key(
|
|
840
|
+
repo_root=repo_root,
|
|
841
|
+
project_id=project_id,
|
|
842
|
+
idea_id=idea_id,
|
|
843
|
+
idea_payload=payload,
|
|
844
|
+
story_paths=[str(p) for p in (story_paths or [])],
|
|
845
|
+
design_paths=[str(p) for p in (design_paths or [])],
|
|
846
|
+
allow_code_design_enrichment=allow_code_design_enrichment,
|
|
847
|
+
)
|
|
848
|
+
pipeline_dir = _pipeline_root(repo_root, idea_id=idea_id, pipeline_key=pipeline_key)
|
|
849
|
+
pipeline_dir.mkdir(parents=True, exist_ok=True)
|
|
850
|
+
run_id = store.create_run(dag_id=DAG_ID, dag_version="v1_phase1", root_correlation_id=f"corr_{pipeline_key}", config={"project_id": project_id, "idea_id": idea_id, "pipeline_key": pipeline_key, "allow_code_design_enrichment": allow_code_design_enrichment})
|
|
851
|
+
store.mark_run_started(run_id=run_id)
|
|
852
|
+
wf = UIGroundingWorkflow()
|
|
853
|
+
global _CURRENT_STORE, _CURRENT_RUN_ID
|
|
854
|
+
_CURRENT_STORE = store
|
|
855
|
+
_CURRENT_RUN_ID = run_id
|
|
856
|
+
try:
|
|
857
|
+
ctx = wf.run({
|
|
858
|
+
"repo_root": str(repo_root),
|
|
859
|
+
"project_id": project_id,
|
|
860
|
+
"idea_id": idea_id,
|
|
861
|
+
"idea_path": str(idea_path.relative_to(repo_root)) if idea_path is not None and idea_path.is_relative_to(repo_root) else (str(idea_path) if idea_path else None),
|
|
862
|
+
"idea_inline": payload if idea_path is None else None,
|
|
863
|
+
"story_paths": [str(p.relative_to(repo_root)) if p.is_relative_to(repo_root) else str(p) for p in (story_paths or [])],
|
|
864
|
+
"design_paths": [str(p.relative_to(repo_root)) if p.is_relative_to(repo_root) else str(p) for p in (design_paths or [])],
|
|
865
|
+
"pipeline_key": pipeline_key,
|
|
866
|
+
"allow_code_design_enrichment": allow_code_design_enrichment,
|
|
867
|
+
"pencil_app_path": str(pencil_app_path) if pencil_app_path is not None else None,
|
|
868
|
+
})
|
|
869
|
+
finally:
|
|
870
|
+
_CURRENT_STORE = None
|
|
871
|
+
_CURRENT_RUN_ID = None
|
|
872
|
+
exit_code = int(ctx.metadata.get("exit_code") or 0)
|
|
873
|
+
store.mark_run_finished(run_id=run_id, status="succeeded" if exit_code == 0 else "failed")
|
|
874
|
+
return UIGroundingDagResult(exit_code=exit_code, run_id=run_id, pipeline_dir=Path(str(ctx.metadata.get("pipeline_root") or pipeline_dir)), message=str(ctx.metadata.get("message") or ""), outcome=dict(ctx.metadata.get("outcome") or {}))
|