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,347 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections import Counter, defaultdict
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def utc_now_iso() -> str:
|
|
11
|
+
return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class DomainState:
|
|
16
|
+
domain: str
|
|
17
|
+
packages: list[str]
|
|
18
|
+
canonical: str | None
|
|
19
|
+
allow_multiple: bool
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def infer_domain_heuristic(pkg: str) -> tuple[str | None, float]:
|
|
23
|
+
"""Heuristic-only domain inference.
|
|
24
|
+
|
|
25
|
+
Returns (domain, confidence).
|
|
26
|
+
|
|
27
|
+
Important: must avoid substring false-positives (e.g. "jsesc" contains "ses").
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
p = pkg.lower().strip()
|
|
31
|
+
|
|
32
|
+
email_markers = {
|
|
33
|
+
"mailjet",
|
|
34
|
+
"nodemailer",
|
|
35
|
+
"postmark",
|
|
36
|
+
"mailgun",
|
|
37
|
+
"@sendgrid/mail",
|
|
38
|
+
"sendgrid",
|
|
39
|
+
"@aws-sdk/client-ses",
|
|
40
|
+
"aws-sdk",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
testing_markers = {
|
|
44
|
+
"pytest",
|
|
45
|
+
"vitest",
|
|
46
|
+
"jest",
|
|
47
|
+
"mocha",
|
|
48
|
+
"ava",
|
|
49
|
+
"cypress",
|
|
50
|
+
"playwright",
|
|
51
|
+
"playwright-core",
|
|
52
|
+
"@playwright/test",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
llm_markers = {
|
|
56
|
+
"openai",
|
|
57
|
+
"anthropic",
|
|
58
|
+
"@anthropic-ai/sdk",
|
|
59
|
+
"@google/generative-ai",
|
|
60
|
+
"vertexai",
|
|
61
|
+
"gemini",
|
|
62
|
+
"mistral",
|
|
63
|
+
"cohere",
|
|
64
|
+
"ollama",
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if p in email_markers or any(p.startswith(m + "/") for m in email_markers if m.startswith("@")):
|
|
68
|
+
return ("email", 0.9)
|
|
69
|
+
if p in testing_markers:
|
|
70
|
+
return ("testing", 0.9)
|
|
71
|
+
if p in llm_markers or p.startswith("@google/"):
|
|
72
|
+
return ("llm", 0.8)
|
|
73
|
+
|
|
74
|
+
return (None, 0.3)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def refresh_policy(conn) -> dict[str, Any]:
|
|
78
|
+
"""Populate/update package_domains/domain_policy/package_status based on imports + allowed_packages.
|
|
79
|
+
|
|
80
|
+
Deterministic rule:
|
|
81
|
+
- domain is inferred via package_domains.manual else heuristic.
|
|
82
|
+
- canonical per domain is package with highest import count in imports(kind='external'), tie-break lexicographically.
|
|
83
|
+
- if allow_multiple=0 and multiple packages exist in a domain => non-canonical packages become heretical.
|
|
84
|
+
|
|
85
|
+
Safety:
|
|
86
|
+
- ignore invalid "package" strings that can arise from scanning vendor/compiled code.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
now = utc_now_iso()
|
|
90
|
+
|
|
91
|
+
def _valid_pkg(s: str) -> bool:
|
|
92
|
+
# npm-style package name (very approximate) with optional scope.
|
|
93
|
+
# Reject templates, paths, and weird punctuation.
|
|
94
|
+
import re
|
|
95
|
+
|
|
96
|
+
return bool(re.match(r"^(@[a-z0-9][\w.-]*/)?[a-z0-9][\w.-]*$", s))
|
|
97
|
+
|
|
98
|
+
# Ensure schema compatibility (older DBs).
|
|
99
|
+
# - add lane column if missing
|
|
100
|
+
# - add strict-domain tables if missing
|
|
101
|
+
try:
|
|
102
|
+
cols = [r[1] for r in conn.execute("pragma table_info(package_status)").fetchall()]
|
|
103
|
+
if "lane" not in cols:
|
|
104
|
+
conn.execute("alter table package_status add column lane text")
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
# Create strict-domain tables if missing (older DBs).
|
|
109
|
+
conn.execute(
|
|
110
|
+
"create table if not exists known_domains(domain text primary key, is_controlled integer not null default 1, updated_at text not null)"
|
|
111
|
+
)
|
|
112
|
+
conn.execute(
|
|
113
|
+
"create table if not exists package_purpose(package text primary key, domain text not null, purpose text not null, decided_by text not null, confidence real, updated_at text not null)"
|
|
114
|
+
)
|
|
115
|
+
conn.execute(
|
|
116
|
+
"create table if not exists package_status_overrides(domain text not null, package text not null, status text not null, updated_at text not null, primary key(domain, package))"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Clear derived tables (we recompute deterministically each refresh).
|
|
120
|
+
conn.execute("delete from package_status")
|
|
121
|
+
|
|
122
|
+
# Import counts per external package
|
|
123
|
+
pkg_counts: Counter[str] = Counter()
|
|
124
|
+
for (resolved_id,) in conn.execute("select resolved_id from imports where kind='external'").fetchall():
|
|
125
|
+
rid = str(resolved_id)
|
|
126
|
+
if rid.startswith("pkg:"):
|
|
127
|
+
pkg = rid.split(":", 1)[1]
|
|
128
|
+
if not _valid_pkg(pkg):
|
|
129
|
+
continue
|
|
130
|
+
pkg_counts[pkg] += 1
|
|
131
|
+
|
|
132
|
+
# Ensure allowed_packages includes anything in imports (belt+braces)
|
|
133
|
+
for pkg in pkg_counts.keys():
|
|
134
|
+
conn.execute("insert or ignore into allowed_packages(name) values (?)", (pkg,))
|
|
135
|
+
|
|
136
|
+
# Load manual domain overrides
|
|
137
|
+
manual_domains: dict[str, str] = {
|
|
138
|
+
str(pkg): str(dom)
|
|
139
|
+
for pkg, dom in conn.execute("select package, domain from package_domain_overrides").fetchall()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# Also respect any previously stored manual entries in package_domains.
|
|
143
|
+
for pkg, dom, inferred_by in conn.execute(
|
|
144
|
+
"select package, domain, inferred_by from package_domains where domain is not null"
|
|
145
|
+
).fetchall():
|
|
146
|
+
if str(inferred_by) == "manual":
|
|
147
|
+
manual_domains.setdefault(str(pkg), str(dom))
|
|
148
|
+
|
|
149
|
+
# Upsert package_domains
|
|
150
|
+
domains_to_pkgs: dict[str, list[str]] = defaultdict(list)
|
|
151
|
+
unknown: list[str] = []
|
|
152
|
+
|
|
153
|
+
# Ensure inferred domains are tracked (controlled-by-default).
|
|
154
|
+
# We'll upsert discovered domains during the loop below.
|
|
155
|
+
|
|
156
|
+
for pkg in sorted({*pkg_counts.keys()}):
|
|
157
|
+
if pkg in manual_domains:
|
|
158
|
+
dom = manual_domains[pkg]
|
|
159
|
+
conf = 1.0
|
|
160
|
+
inferred_by = "manual"
|
|
161
|
+
else:
|
|
162
|
+
dom, conf = infer_domain_heuristic(pkg)
|
|
163
|
+
inferred_by = "heuristic"
|
|
164
|
+
|
|
165
|
+
conn.execute(
|
|
166
|
+
"insert or replace into package_domains(package, domain, inferred_by, confidence, updated_at) values (?,?,?,?,?)",
|
|
167
|
+
(pkg, dom, inferred_by, float(conf), now),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if dom is None:
|
|
171
|
+
unknown.append(pkg)
|
|
172
|
+
else:
|
|
173
|
+
dom_s = str(dom)
|
|
174
|
+
domains_to_pkgs[dom_s].append(pkg)
|
|
175
|
+
# Upsert known domain as controlled-by-default.
|
|
176
|
+
conn.execute(
|
|
177
|
+
"insert or ignore into known_domains(domain, is_controlled, updated_at) values (?,?,?)",
|
|
178
|
+
(dom_s, 1, now),
|
|
179
|
+
)
|
|
180
|
+
# Touch timestamp (keep deterministic but current).
|
|
181
|
+
conn.execute("update known_domains set updated_at=? where domain=?", (now, dom_s))
|
|
182
|
+
|
|
183
|
+
# Drop stale domain_policy rows for domains that no longer exist in this repo.
|
|
184
|
+
current_domains = set(domains_to_pkgs.keys())
|
|
185
|
+
for (dom_existing,) in conn.execute("select domain from domain_policy").fetchall():
|
|
186
|
+
if str(dom_existing) not in current_domains:
|
|
187
|
+
conn.execute("delete from domain_policy where domain=?", (str(dom_existing),))
|
|
188
|
+
|
|
189
|
+
# Determine canonical per domain by import counts
|
|
190
|
+
for dom, pkgs in domains_to_pkgs.items():
|
|
191
|
+
# ensure a policy row exists
|
|
192
|
+
row = conn.execute("select canonical_package, allow_multiple from domain_policy where domain=?", (dom,)).fetchone()
|
|
193
|
+
if row:
|
|
194
|
+
canonical_existing, allow_multiple = row
|
|
195
|
+
allow_multiple = bool(int(allow_multiple))
|
|
196
|
+
else:
|
|
197
|
+
canonical_existing, allow_multiple = None, False
|
|
198
|
+
|
|
199
|
+
# If canonical is unset, compute deterministically
|
|
200
|
+
canonical = str(canonical_existing) if canonical_existing else None
|
|
201
|
+
if canonical is None:
|
|
202
|
+
ranked = sorted(pkgs, key=lambda p: (-pkg_counts.get(p, 0), p))
|
|
203
|
+
canonical = ranked[0] if ranked else None
|
|
204
|
+
|
|
205
|
+
conn.execute(
|
|
206
|
+
"insert or replace into domain_policy(domain, canonical_package, allow_multiple, updated_at) values (?,?,?,?)",
|
|
207
|
+
(dom, canonical, 1 if allow_multiple else 0, now),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Update package_status for packages in the domain
|
|
211
|
+
# Load any lane assignments
|
|
212
|
+
lanes: dict[str, str] = {
|
|
213
|
+
str(pkg): str(lane)
|
|
214
|
+
for pkg, lane in conn.execute(
|
|
215
|
+
"select package, lane from package_lanes where domain=?",
|
|
216
|
+
(dom,),
|
|
217
|
+
).fetchall()
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# Load any status overrides
|
|
221
|
+
overrides: dict[str, str] = {
|
|
222
|
+
str(pkg): str(st)
|
|
223
|
+
for pkg, st in conn.execute(
|
|
224
|
+
"select package, status from package_status_overrides where domain=?",
|
|
225
|
+
(dom,),
|
|
226
|
+
).fetchall()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for p in pkgs:
|
|
230
|
+
if p in overrides:
|
|
231
|
+
status = overrides[p]
|
|
232
|
+
else:
|
|
233
|
+
status = "canonical" if canonical and p == canonical else "allowed" if allow_multiple else "heretical"
|
|
234
|
+
|
|
235
|
+
reason = None
|
|
236
|
+
if status == "heretical":
|
|
237
|
+
reason = f"heretical (non-canonical) in domain {dom}"
|
|
238
|
+
|
|
239
|
+
lane = lanes.get(p)
|
|
240
|
+
conn.execute(
|
|
241
|
+
"insert or replace into package_status(package, domain, status, lane, reason, updated_at) values (?,?,?,?,?,?)",
|
|
242
|
+
(p, dom, status, lane, reason, now),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Unknown packages
|
|
246
|
+
for pkg in unknown:
|
|
247
|
+
conn.execute(
|
|
248
|
+
"insert or replace into package_status(package, domain, status, lane, reason, updated_at) values (?,?,?,?,?,?)",
|
|
249
|
+
(pkg, None, "unknown", None, "domain_unknown", now),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
conn.commit()
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
"unknown": unknown,
|
|
256
|
+
"domains": {d: sorted(ps) for d, ps in domains_to_pkgs.items()},
|
|
257
|
+
"import_counts": dict(pkg_counts),
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def gate_from_db(conn) -> dict[str, Any]:
|
|
262
|
+
"""Compute gate report from DB policy tables (refreshing first if needed)."""
|
|
263
|
+
|
|
264
|
+
# Always refresh; this is cheap relative to registry init, and keeps autonomy.
|
|
265
|
+
refresh_meta = refresh_policy(conn)
|
|
266
|
+
|
|
267
|
+
violations: list[dict[str, Any]] = []
|
|
268
|
+
|
|
269
|
+
# domain -> packages
|
|
270
|
+
domain_rows = conn.execute(
|
|
271
|
+
"select domain, canonical_package, allow_multiple from domain_policy order by domain"
|
|
272
|
+
).fetchall()
|
|
273
|
+
|
|
274
|
+
controlled_map = {
|
|
275
|
+
str(d): bool(int(c))
|
|
276
|
+
for d, c in conn.execute("select domain, is_controlled from known_domains").fetchall()
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
for dom, canonical, allow_multiple in domain_rows:
|
|
280
|
+
dom = str(dom)
|
|
281
|
+
canonical = str(canonical) if canonical else None
|
|
282
|
+
allow_multiple = bool(int(allow_multiple))
|
|
283
|
+
is_controlled = controlled_map.get(dom, True)
|
|
284
|
+
|
|
285
|
+
# Pull statuses+lanes for this domain
|
|
286
|
+
rows = [
|
|
287
|
+
(str(pkg), str(st), (str(lane) if lane is not None else ""))
|
|
288
|
+
for pkg, st, lane in conn.execute(
|
|
289
|
+
"select package, status, lane from package_status where domain=? and status in ('canonical','allowed','heretical') order by package",
|
|
290
|
+
(dom,),
|
|
291
|
+
).fetchall()
|
|
292
|
+
]
|
|
293
|
+
pkgs = [r[0] for r in rows]
|
|
294
|
+
|
|
295
|
+
if not is_controlled:
|
|
296
|
+
continue
|
|
297
|
+
|
|
298
|
+
if len(pkgs) > 1 and not allow_multiple:
|
|
299
|
+
# Strict: must have exactly one non-heretical package.
|
|
300
|
+
non_heresy = [pkg for (pkg, st, _lane) in rows if st in {"canonical", "allowed"}]
|
|
301
|
+
if len(non_heresy) != 1:
|
|
302
|
+
violations.append({"domain": dom, "kind": "domain_not_converged", "packages": rows})
|
|
303
|
+
continue
|
|
304
|
+
|
|
305
|
+
if allow_multiple and pkgs:
|
|
306
|
+
# Lane required for all packages
|
|
307
|
+
missing_lane = [pkg for (pkg, _st, lane) in rows if not lane]
|
|
308
|
+
if missing_lane:
|
|
309
|
+
violations.append({"domain": dom, "kind": "missing_lane", "packages": sorted(missing_lane)})
|
|
310
|
+
continue
|
|
311
|
+
|
|
312
|
+
# Per-lane: exactly one non-heretical.
|
|
313
|
+
by_lane: dict[str, list[tuple[str, str]]] = {}
|
|
314
|
+
for pkg, st, lane in rows:
|
|
315
|
+
by_lane.setdefault(lane, []).append((pkg, st))
|
|
316
|
+
|
|
317
|
+
for lane, items in sorted(by_lane.items()):
|
|
318
|
+
non_heresy = [pkg for (pkg, st) in items if st in {"canonical", "allowed"}]
|
|
319
|
+
if len(non_heresy) != 1:
|
|
320
|
+
violations.append({"domain": dom, "kind": "lane_not_converged", "lane": lane, "packages": items})
|
|
321
|
+
|
|
322
|
+
unknown = [
|
|
323
|
+
str(r[0])
|
|
324
|
+
for r in conn.execute("select package from package_status where status='unknown' order by package").fetchall()
|
|
325
|
+
]
|
|
326
|
+
|
|
327
|
+
# Strict mode: unknown packages are not allowed.
|
|
328
|
+
if unknown:
|
|
329
|
+
violations.append({"kind": "unknown_packages", "packages": unknown})
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
"domains": {
|
|
333
|
+
r[0]: {
|
|
334
|
+
"canonical": r[1],
|
|
335
|
+
"allow_multiple": bool(int(r[2])),
|
|
336
|
+
"controlled": controlled_map.get(str(r[0]), True),
|
|
337
|
+
}
|
|
338
|
+
for r in domain_rows
|
|
339
|
+
},
|
|
340
|
+
"unknown": unknown,
|
|
341
|
+
"violations": violations,
|
|
342
|
+
"refresh": refresh_meta,
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def dump_report(report: dict[str, Any]) -> str:
|
|
347
|
+
return json.dumps(report, indent=2, sort_keys=True) + "\n"
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""Pathway ("module card") detector.
|
|
4
|
+
|
|
5
|
+
This is a lightweight, deterministic analyzer over the internal import graph.
|
|
6
|
+
|
|
7
|
+
Design goals:
|
|
8
|
+
- general signals only (graph structure + optional signatures)
|
|
9
|
+
- stable output ordering
|
|
10
|
+
- easy to test against synthetic graphs
|
|
11
|
+
|
|
12
|
+
It is *not* a policy enforcer.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import Iterable
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class ModuleSignals:
|
|
21
|
+
module_id: str
|
|
22
|
+
in_degree: int
|
|
23
|
+
out_degree: int
|
|
24
|
+
importers: tuple[str, ...]
|
|
25
|
+
internal_deps: tuple[str, ...]
|
|
26
|
+
exports: tuple[str, ...]
|
|
27
|
+
external_pkgs: tuple[str, ...]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _jaccard(a: set[str], b: set[str]) -> float:
|
|
31
|
+
if not a and not b:
|
|
32
|
+
return 1.0
|
|
33
|
+
if not a or not b:
|
|
34
|
+
return 0.0
|
|
35
|
+
return len(a & b) / len(a | b)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _stable_sorted(xs: Iterable[str]) -> list[str]:
|
|
39
|
+
return sorted({str(x) for x in xs if str(x)})
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def build_module_graph(
|
|
43
|
+
*,
|
|
44
|
+
imports_rows: list[tuple[str, str, str, str]],
|
|
45
|
+
module_exports: dict[str, list[str]] | None = None,
|
|
46
|
+
module_external_pkgs: dict[str, list[str]] | None = None,
|
|
47
|
+
) -> dict[str, ModuleSignals]:
|
|
48
|
+
"""Build per-module signals from imports rows.
|
|
49
|
+
|
|
50
|
+
imports_rows schema: (importer, specifier, kind, resolved_id)
|
|
51
|
+
- importer is a file path (repo-relative)
|
|
52
|
+
- resolved_id for internal modules is the module item_id (e.g. module:src/x)
|
|
53
|
+
|
|
54
|
+
module_exports/module_external_pkgs are optional signature maps keyed by module_id.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
module_exports = module_exports or {}
|
|
58
|
+
module_external_pkgs = module_external_pkgs or {}
|
|
59
|
+
|
|
60
|
+
importers: dict[str, set[str]] = {}
|
|
61
|
+
deps: dict[str, set[str]] = {}
|
|
62
|
+
|
|
63
|
+
# Initialize keys for any module we see.
|
|
64
|
+
for importer, _spec, kind, resolved_id in imports_rows:
|
|
65
|
+
if kind != "internal":
|
|
66
|
+
continue
|
|
67
|
+
resolved = str(resolved_id)
|
|
68
|
+
importers.setdefault(resolved, set()).add(str(importer))
|
|
69
|
+
# Track deps per importing module if importer itself is a module id.
|
|
70
|
+
|
|
71
|
+
# Build deps by mapping importer file path -> module id form if present.
|
|
72
|
+
# For synthetic tests we allow importer to already be a module id.
|
|
73
|
+
for importer, _spec, kind, resolved_id in imports_rows:
|
|
74
|
+
if kind != "internal":
|
|
75
|
+
continue
|
|
76
|
+
importer_s = str(importer)
|
|
77
|
+
if importer_s.startswith("module:"):
|
|
78
|
+
importer_id = importer_s
|
|
79
|
+
else:
|
|
80
|
+
# best-effort normalization: treat as module:<path-without-ext>
|
|
81
|
+
importer_id = "module:" + importer_s.rsplit(".", 1)[0]
|
|
82
|
+
deps.setdefault(importer_id, set()).add(str(resolved_id))
|
|
83
|
+
importers.setdefault(importer_id, importers.get(importer_id, set()))
|
|
84
|
+
|
|
85
|
+
out: dict[str, ModuleSignals] = {}
|
|
86
|
+
for mid in sorted(set(importers.keys()) | set(deps.keys())):
|
|
87
|
+
imp = importers.get(mid, set())
|
|
88
|
+
d = deps.get(mid, set())
|
|
89
|
+
exp = _stable_sorted(module_exports.get(mid, []))
|
|
90
|
+
pkgs = _stable_sorted(module_external_pkgs.get(mid, []))
|
|
91
|
+
out[mid] = ModuleSignals(
|
|
92
|
+
module_id=mid,
|
|
93
|
+
in_degree=len(imp),
|
|
94
|
+
out_degree=len(d),
|
|
95
|
+
importers=tuple(sorted(imp)),
|
|
96
|
+
internal_deps=tuple(sorted(d)),
|
|
97
|
+
exports=tuple(exp),
|
|
98
|
+
external_pkgs=tuple(pkgs),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return out
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _reachable(
|
|
105
|
+
g: dict[str, ModuleSignals], start: str, target: str, *, max_depth: int = 6
|
|
106
|
+
) -> bool:
|
|
107
|
+
if start == target:
|
|
108
|
+
return True
|
|
109
|
+
seen: set[str] = set()
|
|
110
|
+
stack: list[tuple[str, int]] = [(start, 0)]
|
|
111
|
+
while stack:
|
|
112
|
+
cur, depth = stack.pop()
|
|
113
|
+
if cur == target:
|
|
114
|
+
return True
|
|
115
|
+
if cur in seen:
|
|
116
|
+
continue
|
|
117
|
+
seen.add(cur)
|
|
118
|
+
if depth >= max_depth:
|
|
119
|
+
continue
|
|
120
|
+
for nxt in g.get(cur).internal_deps if g.get(cur) else []:
|
|
121
|
+
if nxt not in seen:
|
|
122
|
+
stack.append((nxt, depth + 1))
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def detect_pathways(
|
|
127
|
+
*,
|
|
128
|
+
imports_rows: list[tuple[str, str, str, str]],
|
|
129
|
+
module_exports: dict[str, list[str]] | None = None,
|
|
130
|
+
module_external_pkgs: dict[str, list[str]] | None = None,
|
|
131
|
+
module_effect_tokens: dict[str, list[str]] | None = None,
|
|
132
|
+
min_hub_in_degree: int = 2,
|
|
133
|
+
max_hubs: int = 5000,
|
|
134
|
+
competing_importer_jaccard: float = 0.65,
|
|
135
|
+
competing_export_jaccard: float = 0.40,
|
|
136
|
+
overlap_effect_jaccard: float = 0.6,
|
|
137
|
+
) -> dict[str, object]:
|
|
138
|
+
"""Detect seam candidates + competing seams.
|
|
139
|
+
|
|
140
|
+
Output is stable + deterministic.
|
|
141
|
+
|
|
142
|
+
Heuristics:
|
|
143
|
+
- hub seam: in_degree >= min_hub_in_degree
|
|
144
|
+
- orchestrator seam: imports multiple internal deps AND depends on at least one hub
|
|
145
|
+
- competing seams: hubs that share importer neighborhoods + (optionally) export signature
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
g = build_module_graph(
|
|
149
|
+
imports_rows=imports_rows,
|
|
150
|
+
module_exports=module_exports,
|
|
151
|
+
module_external_pkgs=module_external_pkgs,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
module_effect_tokens = module_effect_tokens or {}
|
|
155
|
+
|
|
156
|
+
# Seam candidates
|
|
157
|
+
hubs = [s for s in g.values() if s.in_degree >= int(min_hub_in_degree)]
|
|
158
|
+
hubs.sort(key=lambda s: (-s.in_degree, s.module_id))
|
|
159
|
+
hubs = hubs[: max(0, int(max_hubs))]
|
|
160
|
+
hub_ids = {h.module_id for h in hubs}
|
|
161
|
+
|
|
162
|
+
seams: list[dict[str, object]] = []
|
|
163
|
+
|
|
164
|
+
for h in hubs:
|
|
165
|
+
seams.append(
|
|
166
|
+
{
|
|
167
|
+
"kind": "hub",
|
|
168
|
+
"module_id": h.module_id,
|
|
169
|
+
"score": float(h.in_degree),
|
|
170
|
+
"evidence": {
|
|
171
|
+
"in_degree": h.in_degree,
|
|
172
|
+
"out_degree": h.out_degree,
|
|
173
|
+
"top_importers": list(h.importers)[:10],
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Orchestrators: modules that fan out to multiple internal modules (esp hubs)
|
|
179
|
+
# and are themselves used (in_degree>0).
|
|
180
|
+
for s in g.values():
|
|
181
|
+
if s.out_degree < 2 or s.in_degree < 1:
|
|
182
|
+
continue
|
|
183
|
+
dep_hubs = [d for d in s.internal_deps if d in hub_ids]
|
|
184
|
+
if not dep_hubs:
|
|
185
|
+
continue
|
|
186
|
+
# Score: fanout weighted by hub-ness.
|
|
187
|
+
score = 0.0
|
|
188
|
+
for d in dep_hubs:
|
|
189
|
+
score += float(g.get(d).in_degree if g.get(d) else 0)
|
|
190
|
+
score = score + 0.1 * float(s.out_degree)
|
|
191
|
+
seams.append(
|
|
192
|
+
{
|
|
193
|
+
"kind": "orchestrator",
|
|
194
|
+
"module_id": s.module_id,
|
|
195
|
+
"score": float(score),
|
|
196
|
+
"evidence": {
|
|
197
|
+
"in_degree": s.in_degree,
|
|
198
|
+
"out_degree": s.out_degree,
|
|
199
|
+
"hub_deps": sorted(dep_hubs),
|
|
200
|
+
},
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
seams.sort(key=lambda r: (-float(r.get("score") or 0.0), str(r.get("kind")), str(r.get("module_id"))))
|
|
205
|
+
|
|
206
|
+
# Competing seams among hubs
|
|
207
|
+
competing: list[dict[str, object]] = []
|
|
208
|
+
for i in range(len(hubs)):
|
|
209
|
+
a = hubs[i]
|
|
210
|
+
a_imps = set(a.importers)
|
|
211
|
+
a_exp = set(a.exports)
|
|
212
|
+
for j in range(i + 1, len(hubs)):
|
|
213
|
+
b = hubs[j]
|
|
214
|
+
b_imps = set(b.importers)
|
|
215
|
+
sim_imp = _jaccard(a_imps, b_imps)
|
|
216
|
+
if sim_imp < float(competing_importer_jaccard):
|
|
217
|
+
continue
|
|
218
|
+
|
|
219
|
+
# Export similarity is optional; if neither has exports, treat as neutral.
|
|
220
|
+
b_exp = set(b.exports)
|
|
221
|
+
sim_exp = _jaccard(a_exp, b_exp) if (a_exp or b_exp) else 1.0
|
|
222
|
+
if sim_exp < float(competing_export_jaccard):
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
competing.append(
|
|
226
|
+
{
|
|
227
|
+
"a": a.module_id,
|
|
228
|
+
"b": b.module_id,
|
|
229
|
+
"similarity": {
|
|
230
|
+
"importers_jaccard": round(sim_imp, 4),
|
|
231
|
+
"exports_jaccard": round(sim_exp, 4),
|
|
232
|
+
},
|
|
233
|
+
"evidence": {
|
|
234
|
+
"a_in_degree": a.in_degree,
|
|
235
|
+
"b_in_degree": b.in_degree,
|
|
236
|
+
"shared_importers": sorted(a_imps & b_imps)[:12],
|
|
237
|
+
},
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
competing.sort(key=lambda r: (-float(r["similarity"]["importers_jaccard"]), str(r["a"]), str(r["b"])))
|
|
242
|
+
|
|
243
|
+
# Effect-signature overlaps.
|
|
244
|
+
# We consider any module that has effect tokens (not just detected seams),
|
|
245
|
+
# to avoid "missing" layered overlaps when the modules aren't hubs/orchestrators.
|
|
246
|
+
seam_ids = sorted({
|
|
247
|
+
*[str(s.get("module_id")) for s in seams if isinstance(s, dict) and s.get("module_id")],
|
|
248
|
+
*[str(k) for k, v in module_effect_tokens.items() if v],
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
overlaps: list[dict[str, object]] = []
|
|
252
|
+
for i in range(len(seam_ids)):
|
|
253
|
+
a = seam_ids[i]
|
|
254
|
+
a_tok = set(module_effect_tokens.get(a, []))
|
|
255
|
+
if not a_tok:
|
|
256
|
+
continue
|
|
257
|
+
for j in range(i + 1, len(seam_ids)):
|
|
258
|
+
b = seam_ids[j]
|
|
259
|
+
b_tok = set(module_effect_tokens.get(b, []))
|
|
260
|
+
if not b_tok:
|
|
261
|
+
continue
|
|
262
|
+
sim = _jaccard(a_tok, b_tok)
|
|
263
|
+
if sim < float(overlap_effect_jaccard):
|
|
264
|
+
continue
|
|
265
|
+
|
|
266
|
+
layered = _reachable(g, a, b) or _reachable(g, b, a)
|
|
267
|
+
overlaps.append(
|
|
268
|
+
{
|
|
269
|
+
"a": a,
|
|
270
|
+
"b": b,
|
|
271
|
+
"similarity": {"effects_jaccard": round(sim, 4)},
|
|
272
|
+
"kind": "layered" if layered else "competing",
|
|
273
|
+
"evidence": {
|
|
274
|
+
"shared_effects": sorted(a_tok & b_tok)[:20],
|
|
275
|
+
"a_effect_count": len(a_tok),
|
|
276
|
+
"b_effect_count": len(b_tok),
|
|
277
|
+
},
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
overlaps.sort(key=lambda r: (-float(r["similarity"]["effects_jaccard"]), str(r["kind"]), str(r["a"]), str(r["b"])))
|
|
282
|
+
|
|
283
|
+
# Union-find clusters of effect-overlap (competing or layered; cluster is informational)
|
|
284
|
+
parent: dict[str, str] = {}
|
|
285
|
+
|
|
286
|
+
def find(x: str) -> str:
|
|
287
|
+
parent.setdefault(x, x)
|
|
288
|
+
while parent[x] != x:
|
|
289
|
+
parent[x] = parent[parent[x]]
|
|
290
|
+
x = parent[x]
|
|
291
|
+
return x
|
|
292
|
+
|
|
293
|
+
def union(a: str, b: str) -> None:
|
|
294
|
+
ra, rb = find(a), find(b)
|
|
295
|
+
if ra != rb:
|
|
296
|
+
parent[rb] = ra
|
|
297
|
+
|
|
298
|
+
for p in overlaps:
|
|
299
|
+
union(str(p["a"]), str(p["b"]))
|
|
300
|
+
|
|
301
|
+
clusters: dict[str, set[str]] = {}
|
|
302
|
+
for mid in parent.keys():
|
|
303
|
+
clusters.setdefault(find(mid), set()).add(mid)
|
|
304
|
+
|
|
305
|
+
overlap_clusters = [sorted(v) for v in clusters.values() if len(v) >= 2]
|
|
306
|
+
overlap_clusters.sort(key=lambda grp: (len(grp), grp))
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
"stats": {
|
|
310
|
+
"modules": len(g),
|
|
311
|
+
"hubs": len(hubs),
|
|
312
|
+
"seams": len(seams),
|
|
313
|
+
"competing_pairs": len(competing),
|
|
314
|
+
"effect_overlap_pairs": len(overlaps),
|
|
315
|
+
"effect_overlap_clusters": len(overlap_clusters),
|
|
316
|
+
},
|
|
317
|
+
"seams": seams,
|
|
318
|
+
"competing": competing,
|
|
319
|
+
"effect_overlap": {
|
|
320
|
+
"pairs": overlaps,
|
|
321
|
+
"clusters": overlap_clusters,
|
|
322
|
+
},
|
|
323
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Review workflow support.
|
|
2
|
+
|
|
3
|
+
DEPRECATED: The `review` CLI surface and this module are non-canonical.
|
|
4
|
+
Automated story execution should use the queue-driven worker pipeline
|
|
5
|
+
(`devflow worker start`). This module is retained for backward compatibility
|
|
6
|
+
and direct debugging only.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .dag import run_review_dag
|
|
10
|
+
|
|
11
|
+
__all__ = ["run_review_dag"]
|