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,150 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""Build a unified enforcement report from the registry DB.
|
|
4
|
+
|
|
5
|
+
Enforcement is gate-only: it must not run LLM classification or change policy.
|
|
6
|
+
It may derive facts from the current import graph + persisted policy.
|
|
7
|
+
|
|
8
|
+
Report format is designed to be consumed by `devflow refactor`.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def build_enforce_report(conn, *, changed_importers: set[str] | None = None) -> dict[str, object]:
|
|
15
|
+
"""Return a machine-readable enforcement report.
|
|
16
|
+
|
|
17
|
+
conn is a sqlite3 connection with registry schema.
|
|
18
|
+
changed_importers (optional) restricts the report to imports originating from
|
|
19
|
+
those importer file paths (repo-relative).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
changed_importers = {str(p) for p in (changed_importers or set())}
|
|
23
|
+
|
|
24
|
+
def _importer_ok(importer: str) -> bool:
|
|
25
|
+
return (not changed_importers) or (importer in changed_importers)
|
|
26
|
+
|
|
27
|
+
# Missing registration (existing helper query semantics)
|
|
28
|
+
missing: list[dict[str, str]] = []
|
|
29
|
+
|
|
30
|
+
# external missing
|
|
31
|
+
for importer, specifier, resolved_id in conn.execute(
|
|
32
|
+
"""
|
|
33
|
+
SELECT importer, specifier, resolved_id
|
|
34
|
+
FROM imports
|
|
35
|
+
WHERE kind='external'
|
|
36
|
+
AND substr(resolved_id, 5) NOT IN (SELECT name FROM allowed_packages)
|
|
37
|
+
ORDER BY importer, specifier
|
|
38
|
+
"""
|
|
39
|
+
).fetchall():
|
|
40
|
+
if not _importer_ok(str(importer)):
|
|
41
|
+
continue
|
|
42
|
+
missing.append({"importer": str(importer), "specifier": str(specifier), "resolved_id": str(resolved_id), "kind": "external"})
|
|
43
|
+
|
|
44
|
+
# internal missing
|
|
45
|
+
for importer, specifier, resolved_id in conn.execute(
|
|
46
|
+
"""
|
|
47
|
+
SELECT importer, specifier, resolved_id
|
|
48
|
+
FROM imports
|
|
49
|
+
WHERE kind='internal'
|
|
50
|
+
AND resolved_id NOT IN (SELECT item_id FROM registered_items)
|
|
51
|
+
ORDER BY importer, specifier
|
|
52
|
+
"""
|
|
53
|
+
).fetchall():
|
|
54
|
+
if not _importer_ok(str(importer)):
|
|
55
|
+
continue
|
|
56
|
+
missing.append({"importer": str(importer), "specifier": str(specifier), "resolved_id": str(resolved_id), "kind": "internal"})
|
|
57
|
+
|
|
58
|
+
# Canonical refactor directives
|
|
59
|
+
directives: list[dict[str, object]] = []
|
|
60
|
+
|
|
61
|
+
# Packages: if a package is marked heretical, tell the user to refactor to the canonical for that domain/lane.
|
|
62
|
+
pkg_status: dict[str, tuple[str, str, str]] = {}
|
|
63
|
+
for package, domain, status, lane in conn.execute(
|
|
64
|
+
"select package, domain, status, coalesce(lane,'') from package_status where domain is not null"
|
|
65
|
+
).fetchall():
|
|
66
|
+
pkg_status[str(package)] = (str(domain), str(status), str(lane or ""))
|
|
67
|
+
|
|
68
|
+
canonical_by_domain_lane: dict[tuple[str, str], str] = {}
|
|
69
|
+
for package, domain, status, lane in conn.execute(
|
|
70
|
+
"select package, domain, status, coalesce(lane,'') from package_status where status='canonical' and domain is not null"
|
|
71
|
+
).fetchall():
|
|
72
|
+
canonical_by_domain_lane[(str(domain), str(lane or ""))] = str(package)
|
|
73
|
+
|
|
74
|
+
for importer, specifier, resolved_id in conn.execute(
|
|
75
|
+
"select importer, specifier, resolved_id from imports where kind='external' order by importer, specifier"
|
|
76
|
+
).fetchall():
|
|
77
|
+
if not _importer_ok(str(importer)):
|
|
78
|
+
continue
|
|
79
|
+
pkg = str(resolved_id).split(":", 1)[1]
|
|
80
|
+
st = pkg_status.get(pkg)
|
|
81
|
+
if not st:
|
|
82
|
+
continue
|
|
83
|
+
domain, status, lane = st
|
|
84
|
+
if status != "heretical":
|
|
85
|
+
continue
|
|
86
|
+
canon = canonical_by_domain_lane.get((domain, lane)) or canonical_by_domain_lane.get((domain, ""))
|
|
87
|
+
if not canon or canon == pkg:
|
|
88
|
+
continue
|
|
89
|
+
directives.append(
|
|
90
|
+
{
|
|
91
|
+
"kind": "refactor_external_package",
|
|
92
|
+
"domain": domain,
|
|
93
|
+
"lane": lane,
|
|
94
|
+
"from": pkg,
|
|
95
|
+
"to": canon,
|
|
96
|
+
"evidence": {"importer": str(importer), "specifier": str(specifier)},
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Modules: if an imported module is heretical in an existing (domain, card_id, lane), refactor to canonical.
|
|
101
|
+
canon_module_by_key: dict[tuple[str, str, str], str] = {}
|
|
102
|
+
for domain, card_id, lane, module_id in conn.execute(
|
|
103
|
+
"select domain, card_id, lane, module_id from module_card_membership where status='canonical'"
|
|
104
|
+
).fetchall():
|
|
105
|
+
canon_module_by_key[(str(domain), str(card_id), str(lane))] = str(module_id)
|
|
106
|
+
|
|
107
|
+
membership: dict[tuple[str, str, str, str], str] = {}
|
|
108
|
+
for domain, card_id, lane, module_id, status in conn.execute(
|
|
109
|
+
"select domain, card_id, lane, module_id, status from module_card_membership"
|
|
110
|
+
).fetchall():
|
|
111
|
+
membership[(str(domain), str(card_id), str(lane), str(module_id))] = str(status)
|
|
112
|
+
|
|
113
|
+
# Build quick module -> keys where it is heretical.
|
|
114
|
+
heretical_keys_by_module: dict[str, list[tuple[str, str, str]]] = defaultdict(list)
|
|
115
|
+
for (domain, card_id, lane, module_id), status in membership.items():
|
|
116
|
+
if status == "heretical":
|
|
117
|
+
heretical_keys_by_module[module_id].append((domain, card_id, lane))
|
|
118
|
+
|
|
119
|
+
for importer, specifier, resolved_id in conn.execute(
|
|
120
|
+
"select importer, specifier, resolved_id from imports where kind='internal' order by importer, specifier"
|
|
121
|
+
).fetchall():
|
|
122
|
+
if not _importer_ok(str(importer)):
|
|
123
|
+
continue
|
|
124
|
+
mid = str(resolved_id)
|
|
125
|
+
for key in heretical_keys_by_module.get(mid, []):
|
|
126
|
+
canon = canon_module_by_key.get(key)
|
|
127
|
+
if not canon or canon == mid:
|
|
128
|
+
continue
|
|
129
|
+
domain, card_id, lane = key
|
|
130
|
+
directives.append(
|
|
131
|
+
{
|
|
132
|
+
"kind": "refactor_internal_module",
|
|
133
|
+
"domain": domain,
|
|
134
|
+
"card_id": card_id,
|
|
135
|
+
"lane": lane,
|
|
136
|
+
"from": mid,
|
|
137
|
+
"to": canon,
|
|
138
|
+
"evidence": {"importer": str(importer), "specifier": str(specifier)},
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
directives.sort(key=lambda d: (str(d.get("kind")), str(d.get("domain")), str(d.get("from")), str(d.get("to")), str(d.get("evidence", {}).get("importer"))))
|
|
143
|
+
|
|
144
|
+
ok = (not missing) and (not directives)
|
|
145
|
+
return {
|
|
146
|
+
"ok": bool(ok),
|
|
147
|
+
"missing": missing,
|
|
148
|
+
"directives": directives,
|
|
149
|
+
"stats": {"missing": len(missing), "directives": len(directives)},
|
|
150
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""Module-card classification (non-destructive).
|
|
4
|
+
|
|
5
|
+
Takes a draft module-card plan and produces a classified plan:
|
|
6
|
+
- assigns each card a domain
|
|
7
|
+
- assigns each module within card a lane
|
|
8
|
+
- marks exactly one canonical per (domain, card_id, lane)
|
|
9
|
+
|
|
10
|
+
This mirrors the package policy model (domain + lane + canonical/heretical),
|
|
11
|
+
but remains file-based until we decide to persist it.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class ClassifiedAssignment:
|
|
20
|
+
card_id: str
|
|
21
|
+
domain: str
|
|
22
|
+
module_id: str
|
|
23
|
+
lane: str
|
|
24
|
+
status: str # canonical|heretical
|
|
25
|
+
reason: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _extract_json(text: str) -> str | None:
|
|
29
|
+
# Common case: model wraps JSON in ```json fences.
|
|
30
|
+
if "```" in text:
|
|
31
|
+
parts = text.split("```")
|
|
32
|
+
for i in range(len(parts) - 1):
|
|
33
|
+
body = parts[i + 1]
|
|
34
|
+
body_lines = body.splitlines()
|
|
35
|
+
if body_lines and body_lines[0].strip().lower() in {"json", "javascript"}:
|
|
36
|
+
body = "\n".join(body_lines[1:])
|
|
37
|
+
body = body.strip()
|
|
38
|
+
if body.startswith("{") and body.endswith("}"):
|
|
39
|
+
return body
|
|
40
|
+
|
|
41
|
+
start = text.find("{")
|
|
42
|
+
end = text.rfind("}")
|
|
43
|
+
if start != -1 and end != -1 and end > start:
|
|
44
|
+
return text[start : end + 1]
|
|
45
|
+
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def build_classify_prompt(*, draft: dict[str, object], domain_enum: list[str] | None = None, allow_new_domains: bool = True) -> str:
|
|
50
|
+
cards = draft.get("cards") if isinstance(draft, dict) else None
|
|
51
|
+
if not isinstance(cards, list):
|
|
52
|
+
cards = []
|
|
53
|
+
|
|
54
|
+
# Keep prompt small: only include cards with >1 module (i.e., overlaps).
|
|
55
|
+
overlap_cards: list[dict[str, object]] = []
|
|
56
|
+
for c in cards:
|
|
57
|
+
if not isinstance(c, dict):
|
|
58
|
+
continue
|
|
59
|
+
canon = str(c.get("canonical_module_id") or "").strip()
|
|
60
|
+
comps = c.get("competing_module_ids")
|
|
61
|
+
if not canon:
|
|
62
|
+
continue
|
|
63
|
+
if not isinstance(comps, list) or not comps:
|
|
64
|
+
continue
|
|
65
|
+
overlap_cards.append(
|
|
66
|
+
{
|
|
67
|
+
"card_id": str(c.get("card_id") or "").strip(),
|
|
68
|
+
"canonical_module_id": canon,
|
|
69
|
+
"competing_module_ids": [str(x) for x in comps if str(x)],
|
|
70
|
+
"evidence": c.get("evidence") if isinstance(c.get("evidence"), dict) else {},
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
instructions = [
|
|
75
|
+
"Return JSON only (no markdown, no code fences).",
|
|
76
|
+
"For each card, choose a domain.",
|
|
77
|
+
"Lane assignment is the primary goal. Prefer splitting into semantically distinct lanes over marking heresy.",
|
|
78
|
+
"For each module in a card (canonical + competing), assign a lane string.",
|
|
79
|
+
"If two modules are different abstraction levels (primitive vs composite), different surfaces (provider vs consumer), or different sub-flows, they should be in different lanes.",
|
|
80
|
+
"Only when two modules genuinely represent the same concern within the same lane should you mark one canonical and all others heretical.",
|
|
81
|
+
"Within each lane of a card, mark exactly one module as canonical and all other modules in that lane heretical.",
|
|
82
|
+
"Minimize heretical assignments; use them only when lane splitting cannot be justified.",
|
|
83
|
+
"Keep lane names short but expressive; hierarchical lanes are allowed (e.g. navigation.top, navigation.bottom, controls.primitive, controls.composite, context.provider, page.consumer).",
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
if allow_new_domains:
|
|
87
|
+
instructions.extend(
|
|
88
|
+
[
|
|
89
|
+
"You MAY propose a new domain if none of domain_enum fit.",
|
|
90
|
+
"If proposing a new domain, include new_domain with that value (and also set domain to that value).",
|
|
91
|
+
"New domains must be 1-2 words, general (not project-specific), lowercase, and avoid version suffixes.",
|
|
92
|
+
]
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
instructions.extend(
|
|
96
|
+
[
|
|
97
|
+
"You MUST choose domain from domain_enum. Do not create new domains.",
|
|
98
|
+
]
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
payload: dict[str, object] = {
|
|
102
|
+
"task": "classify_module_cards",
|
|
103
|
+
"domain_enum": domain_enum or [],
|
|
104
|
+
"allow_new_domains": bool(allow_new_domains),
|
|
105
|
+
"cards": overlap_cards,
|
|
106
|
+
"instructions": instructions,
|
|
107
|
+
"output_schema": {
|
|
108
|
+
"assignments": [
|
|
109
|
+
{
|
|
110
|
+
"card_id": "string",
|
|
111
|
+
"domain": "string",
|
|
112
|
+
"new_domain": "string|null",
|
|
113
|
+
"module_id": "string",
|
|
114
|
+
"lane": "string",
|
|
115
|
+
"status": "canonical|heretical",
|
|
116
|
+
"reason": "string",
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return json.dumps(payload, indent=2, sort_keys=True)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def parse_classified_output(*, out_text: str) -> dict[str, object]:
|
|
126
|
+
raw = _extract_json(out_text)
|
|
127
|
+
if raw is None:
|
|
128
|
+
raise ValueError("Failed to locate JSON in LLM output")
|
|
129
|
+
parsed = json.loads(raw)
|
|
130
|
+
if not isinstance(parsed, dict):
|
|
131
|
+
raise ValueError("LLM output must be a JSON object")
|
|
132
|
+
assignments = parsed.get("assignments")
|
|
133
|
+
if not isinstance(assignments, list):
|
|
134
|
+
raise ValueError("LLM output missing assignments[]")
|
|
135
|
+
|
|
136
|
+
cleaned: list[dict[str, object]] = []
|
|
137
|
+
for a in assignments:
|
|
138
|
+
if not isinstance(a, dict):
|
|
139
|
+
continue
|
|
140
|
+
card_id = str(a.get("card_id") or "").strip()
|
|
141
|
+
domain = str(a.get("domain") or "").strip()
|
|
142
|
+
new_domain = str(a.get("new_domain") or "").strip() or None
|
|
143
|
+
module_id = str(a.get("module_id") or "").strip()
|
|
144
|
+
lane = str(a.get("lane") or "").strip()
|
|
145
|
+
status = str(a.get("status") or "").strip().lower()
|
|
146
|
+
reason = str(a.get("reason") or "").strip()
|
|
147
|
+
if not (card_id and domain and module_id and lane and status in {"canonical", "heretical"}):
|
|
148
|
+
continue
|
|
149
|
+
cleaned.append(
|
|
150
|
+
{
|
|
151
|
+
"card_id": card_id,
|
|
152
|
+
"domain": domain,
|
|
153
|
+
"new_domain": new_domain,
|
|
154
|
+
"module_id": module_id,
|
|
155
|
+
"lane": lane,
|
|
156
|
+
"status": status,
|
|
157
|
+
"reason": reason,
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
cleaned.sort(key=lambda r: (str(r["domain"]), str(r["card_id"]), str(r["lane"]), str(r["status"]), str(r["module_id"])))
|
|
162
|
+
|
|
163
|
+
new_domains = sorted({str(r["new_domain"]) for r in cleaned if r.get("new_domain")})
|
|
164
|
+
return {"version": 2, "new_domains": new_domains, "assignments": cleaned}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""Draft module-card registration artifacts.
|
|
4
|
+
|
|
5
|
+
This is intentionally *non-destructive*: it does not write to the registry DB.
|
|
6
|
+
It produces a stable JSON draft you can inspect, edit, and later "apply".
|
|
7
|
+
|
|
8
|
+
A draft card is keyed by (domain, card_id, lane) and has exactly one canonical seam.
|
|
9
|
+
In v0 drafts, domain is "unknown" and lane is "default".
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
|
|
15
|
+
from .pathways import ModuleSignals, build_module_graph, detect_pathways
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class DraftCard:
|
|
20
|
+
card_id: str
|
|
21
|
+
domain: str
|
|
22
|
+
lane: str
|
|
23
|
+
canonical_module_id: str
|
|
24
|
+
competing_module_ids: tuple[str, ...]
|
|
25
|
+
evidence: dict[str, object]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _slugify(text: str) -> str:
|
|
29
|
+
t = text.strip().lower()
|
|
30
|
+
t = re.sub(r"[^a-z0-9]+", "-", t)
|
|
31
|
+
t = re.sub(r"-+", "-", t).strip("-")
|
|
32
|
+
return t or "card"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _module_basename(module_id: str) -> str:
|
|
36
|
+
s = module_id.split(":", 1)[-1]
|
|
37
|
+
return s.split("/")[-1]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _choose_canonical(module_ids: list[str], g: dict[str, ModuleSignals]) -> str:
|
|
41
|
+
# Deterministic: highest in_degree, tie-break by module_id.
|
|
42
|
+
return sorted(module_ids, key=lambda mid: (-(g.get(mid).in_degree if g.get(mid) else 0), mid))[0]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def draft_module_cards(
|
|
46
|
+
*,
|
|
47
|
+
imports_rows: list[tuple[str, str, str, str]],
|
|
48
|
+
module_exports: dict[str, list[str]] | None = None,
|
|
49
|
+
module_external_pkgs: dict[str, list[str]] | None = None,
|
|
50
|
+
module_effect_tokens: dict[str, list[str]] | None = None,
|
|
51
|
+
focus_module_ids: set[str] | None = None,
|
|
52
|
+
domain_default: str = "unknown",
|
|
53
|
+
lane_default: str = "default",
|
|
54
|
+
) -> dict[str, object]:
|
|
55
|
+
"""Generate a stable draft module-card plan from import graph signals."""
|
|
56
|
+
|
|
57
|
+
report = detect_pathways(
|
|
58
|
+
imports_rows=imports_rows,
|
|
59
|
+
module_exports=module_exports,
|
|
60
|
+
module_external_pkgs=module_external_pkgs,
|
|
61
|
+
module_effect_tokens=module_effect_tokens,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
g = build_module_graph(
|
|
65
|
+
imports_rows=imports_rows,
|
|
66
|
+
module_exports=module_exports,
|
|
67
|
+
module_external_pkgs=module_external_pkgs,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Build undirected components from competing pairs.
|
|
71
|
+
# We include both:
|
|
72
|
+
# - "competing" hubs (shared importer neighborhoods)
|
|
73
|
+
# - "effect_overlap" competing pairs (shared effect signatures)
|
|
74
|
+
adj: dict[str, set[str]] = {}
|
|
75
|
+
|
|
76
|
+
def _add_edge(a: str, b: str) -> None:
|
|
77
|
+
if not a or not b:
|
|
78
|
+
return
|
|
79
|
+
adj.setdefault(a, set()).add(b)
|
|
80
|
+
adj.setdefault(b, set()).add(a)
|
|
81
|
+
|
|
82
|
+
for p in report.get("competing", []):
|
|
83
|
+
_add_edge(str(p.get("a") or ""), str(p.get("b") or ""))
|
|
84
|
+
|
|
85
|
+
eo = report.get("effect_overlap", {})
|
|
86
|
+
if isinstance(eo, dict):
|
|
87
|
+
for p in eo.get("pairs", []) if isinstance(eo.get("pairs"), list) else []:
|
|
88
|
+
if not isinstance(p, dict):
|
|
89
|
+
continue
|
|
90
|
+
if str(p.get("kind") or "") != "competing":
|
|
91
|
+
continue
|
|
92
|
+
_add_edge(str(p.get("a") or ""), str(p.get("b") or ""))
|
|
93
|
+
|
|
94
|
+
visited: set[str] = set()
|
|
95
|
+
groups: list[list[str]] = []
|
|
96
|
+
|
|
97
|
+
for n in sorted(adj.keys()):
|
|
98
|
+
if n in visited:
|
|
99
|
+
continue
|
|
100
|
+
stack = [n]
|
|
101
|
+
comp: set[str] = set()
|
|
102
|
+
while stack:
|
|
103
|
+
cur = stack.pop()
|
|
104
|
+
if cur in visited:
|
|
105
|
+
continue
|
|
106
|
+
visited.add(cur)
|
|
107
|
+
comp.add(cur)
|
|
108
|
+
for nxt in adj.get(cur, set()):
|
|
109
|
+
if nxt not in visited:
|
|
110
|
+
stack.append(nxt)
|
|
111
|
+
if len(comp) >= 2:
|
|
112
|
+
groups.append(sorted(comp))
|
|
113
|
+
|
|
114
|
+
# Any hub seams not in a competing group become singleton cards.
|
|
115
|
+
hub_ids: list[str] = []
|
|
116
|
+
for s in report.get("seams", []):
|
|
117
|
+
if isinstance(s, dict) and s.get("kind") == "hub":
|
|
118
|
+
hub_ids.append(str(s.get("module_id") or ""))
|
|
119
|
+
hub_ids = [h for h in hub_ids if h]
|
|
120
|
+
|
|
121
|
+
grouped = {m for grp in groups for m in grp}
|
|
122
|
+
for h in sorted(set(hub_ids)):
|
|
123
|
+
if h not in grouped:
|
|
124
|
+
groups.append([h])
|
|
125
|
+
|
|
126
|
+
focus_module_ids = focus_module_ids or None
|
|
127
|
+
|
|
128
|
+
# Draft cards
|
|
129
|
+
cards: list[dict[str, object]] = []
|
|
130
|
+
for grp in groups:
|
|
131
|
+
if focus_module_ids is not None:
|
|
132
|
+
if not any(m in focus_module_ids for m in grp):
|
|
133
|
+
continue
|
|
134
|
+
canonical = _choose_canonical(grp, g)
|
|
135
|
+
competitors = [m for m in grp if m != canonical]
|
|
136
|
+
|
|
137
|
+
# Stable-ish card id based on canonical basename + first few competitors.
|
|
138
|
+
parts = [_module_basename(canonical)] + [_module_basename(m) for m in competitors[:2]]
|
|
139
|
+
card_id = _slugify("-".join(parts))
|
|
140
|
+
|
|
141
|
+
evidence = {
|
|
142
|
+
"canonical": {
|
|
143
|
+
"module_id": canonical,
|
|
144
|
+
"in_degree": g.get(canonical).in_degree if g.get(canonical) else 0,
|
|
145
|
+
"out_degree": g.get(canonical).out_degree if g.get(canonical) else 0,
|
|
146
|
+
"top_importers": list(g.get(canonical).importers)[:10] if g.get(canonical) else [],
|
|
147
|
+
},
|
|
148
|
+
"competing": [
|
|
149
|
+
{
|
|
150
|
+
"module_id": m,
|
|
151
|
+
"in_degree": g.get(m).in_degree if g.get(m) else 0,
|
|
152
|
+
"out_degree": g.get(m).out_degree if g.get(m) else 0,
|
|
153
|
+
}
|
|
154
|
+
for m in competitors
|
|
155
|
+
],
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
cards.append(
|
|
159
|
+
{
|
|
160
|
+
"card_id": card_id,
|
|
161
|
+
"domain": domain_default,
|
|
162
|
+
"lane": lane_default,
|
|
163
|
+
"canonical_module_id": canonical,
|
|
164
|
+
"competing_module_ids": competitors,
|
|
165
|
+
"evidence": evidence,
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
cards.sort(key=lambda c: (str(c.get("domain")), str(c.get("lane")), str(c.get("card_id"))))
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
"version": 1,
|
|
173
|
+
"stats": {
|
|
174
|
+
"cards": len(cards),
|
|
175
|
+
"source_modules": report.get("stats", {}).get("modules"),
|
|
176
|
+
"source_hubs": report.get("stats", {}).get("hubs"),
|
|
177
|
+
"source_competing_pairs": report.get("stats", {}).get("competing_pairs"),
|
|
178
|
+
"source_effect_overlap_pairs": report.get("stats", {}).get("effect_overlap_pairs"),
|
|
179
|
+
},
|
|
180
|
+
"cards": cards,
|
|
181
|
+
"raw": {
|
|
182
|
+
"pathways": report,
|
|
183
|
+
},
|
|
184
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""Deterministic gate for classified module cards."""
|
|
4
|
+
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def gate_classified_module_cards(doc: dict[str, object]) -> dict[str, object]:
|
|
9
|
+
assignments = doc.get("assignments") if isinstance(doc, dict) else None
|
|
10
|
+
if not isinstance(assignments, list):
|
|
11
|
+
return {"ok": False, "violations": [{"kind": "invalid_format", "message": "missing assignments[]"}]}
|
|
12
|
+
|
|
13
|
+
violations: list[dict[str, object]] = []
|
|
14
|
+
|
|
15
|
+
# Validate required fields and group by (domain, card_id, lane)
|
|
16
|
+
by_key: dict[tuple[str, str, str], list[dict[str, str]]] = defaultdict(list)
|
|
17
|
+
seen_module_in_card: set[tuple[str, str]] = set() # (card_id, module_id)
|
|
18
|
+
|
|
19
|
+
for a in assignments:
|
|
20
|
+
if not isinstance(a, dict):
|
|
21
|
+
continue
|
|
22
|
+
card_id = str(a.get("card_id") or "").strip()
|
|
23
|
+
domain = str(a.get("domain") or "").strip()
|
|
24
|
+
module_id = str(a.get("module_id") or "").strip()
|
|
25
|
+
lane = str(a.get("lane") or "").strip()
|
|
26
|
+
status = str(a.get("status") or "").strip().lower()
|
|
27
|
+
|
|
28
|
+
if not card_id or not domain or not module_id or not lane:
|
|
29
|
+
violations.append({"kind": "missing_fields", "assignment": a})
|
|
30
|
+
continue
|
|
31
|
+
if status not in {"canonical", "heretical"}:
|
|
32
|
+
violations.append({"kind": "invalid_status", "assignment": a})
|
|
33
|
+
continue
|
|
34
|
+
|
|
35
|
+
if (card_id, module_id) in seen_module_in_card:
|
|
36
|
+
violations.append({"kind": "duplicate_module", "card_id": card_id, "module_id": module_id})
|
|
37
|
+
continue
|
|
38
|
+
seen_module_in_card.add((card_id, module_id))
|
|
39
|
+
|
|
40
|
+
by_key[(domain, card_id, lane)].append(
|
|
41
|
+
{"module_id": module_id, "status": status}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Convergence per lane.
|
|
45
|
+
for (domain, card_id, lane), items in sorted(by_key.items()):
|
|
46
|
+
canon = [i["module_id"] for i in items if i["status"] == "canonical"]
|
|
47
|
+
if len(canon) != 1:
|
|
48
|
+
violations.append(
|
|
49
|
+
{
|
|
50
|
+
"kind": "lane_not_converged",
|
|
51
|
+
"domain": domain,
|
|
52
|
+
"card_id": card_id,
|
|
53
|
+
"lane": lane,
|
|
54
|
+
"canonicals": canon,
|
|
55
|
+
"members": sorted(items, key=lambda r: (r["status"], r["module_id"])) ,
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return {"ok": not violations, "violations": violations}
|