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.
Files changed (393) hide show
  1. devflow_engine/__init__.py +3 -0
  2. devflow_engine/agentic_prompts.py +100 -0
  3. devflow_engine/agentic_runtime.py +398 -0
  4. devflow_engine/api_key_flow_harness.py +539 -0
  5. devflow_engine/api_keys.py +357 -0
  6. devflow_engine/bootstrap/__init__.py +2 -0
  7. devflow_engine/bootstrap/provision_from_template.py +84 -0
  8. devflow_engine/cli/__init__.py +0 -0
  9. devflow_engine/cli/app.py +7270 -0
  10. devflow_engine/core/__init__.py +0 -0
  11. devflow_engine/core/config.py +86 -0
  12. devflow_engine/core/logging.py +29 -0
  13. devflow_engine/core/paths.py +45 -0
  14. devflow_engine/core/toml_kv.py +33 -0
  15. devflow_engine/devflow_event_worker.py +1292 -0
  16. devflow_engine/devflow_state.py +201 -0
  17. devflow_engine/devin2/__init__.py +9 -0
  18. devflow_engine/devin2/agent_definition.py +120 -0
  19. devflow_engine/devin2/pi_runner.py +204 -0
  20. devflow_engine/devin_orchestration.py +69 -0
  21. devflow_engine/docs/prompts/anti-patterns.md +42 -0
  22. devflow_engine/docs/prompts/devin-agent-prompt.md +55 -0
  23. devflow_engine/docs/prompts/devin2-agent-prompt.md +81 -0
  24. devflow_engine/docs/prompts/examples/devin-vapi-clone-reference-exchange.json +85 -0
  25. devflow_engine/doctor/__init__.py +2 -0
  26. devflow_engine/doctor/triage.py +140 -0
  27. devflow_engine/error/__init__.py +0 -0
  28. devflow_engine/error/remediation.py +21 -0
  29. devflow_engine/errors/error_solver_dag.py +522 -0
  30. devflow_engine/errors/runtime_observability.py +67 -0
  31. devflow_engine/idea/__init__.py +4 -0
  32. devflow_engine/idea/actors.py +481 -0
  33. devflow_engine/idea/agentic.py +465 -0
  34. devflow_engine/idea/analyze.py +93 -0
  35. devflow_engine/idea/devin_chat_dag.py +1 -0
  36. devflow_engine/idea/diff.py +99 -0
  37. devflow_engine/idea/drafts.py +446 -0
  38. devflow_engine/idea/idea_creation_dag.py +643 -0
  39. devflow_engine/idea/ideation_enrichment.py +355 -0
  40. devflow_engine/idea/ideation_enrichment_worker.py +19 -0
  41. devflow_engine/idea/paths.py +28 -0
  42. devflow_engine/idea/promote.py +53 -0
  43. devflow_engine/idea/redaction.py +27 -0
  44. devflow_engine/idea/repo_tools.py +1277 -0
  45. devflow_engine/idea/response_mode.py +30 -0
  46. devflow_engine/idea/story_pipeline.py +1585 -0
  47. devflow_engine/idea/sufficiency.py +376 -0
  48. devflow_engine/idea/traditional_stories.py +1257 -0
  49. devflow_engine/implementation/__init__.py +0 -0
  50. devflow_engine/implementation/alembic_preflight.py +700 -0
  51. devflow_engine/implementation/dag.py +8450 -0
  52. devflow_engine/implementation/green_gate.py +93 -0
  53. devflow_engine/implementation/prompts.py +108 -0
  54. devflow_engine/implementation/test_runtime.py +623 -0
  55. devflow_engine/integration/__init__.py +19 -0
  56. devflow_engine/integration/agentic.py +66 -0
  57. devflow_engine/integration/dag.py +3539 -0
  58. devflow_engine/integration/prompts.py +114 -0
  59. devflow_engine/integration/supabase_schema.sql +31 -0
  60. devflow_engine/integration/supabase_sync.py +177 -0
  61. devflow_engine/llm/__init__.py +1 -0
  62. devflow_engine/llm/cli_one_shot.py +84 -0
  63. devflow_engine/llm/cli_stream.py +371 -0
  64. devflow_engine/llm/execution_context.py +26 -0
  65. devflow_engine/llm/invoke.py +1322 -0
  66. devflow_engine/llm/provider_api.py +304 -0
  67. devflow_engine/llm/repo_knowledge.py +588 -0
  68. devflow_engine/llm_primitives.py +315 -0
  69. devflow_engine/orchestration.py +62 -0
  70. devflow_engine/planning/__init__.py +0 -0
  71. devflow_engine/planning/analyze_repo.py +92 -0
  72. devflow_engine/planning/render_drafts.py +133 -0
  73. devflow_engine/playground/__init__.py +0 -0
  74. devflow_engine/playground/hooks.py +26 -0
  75. devflow_engine/playwright_workflow/__init__.py +5 -0
  76. devflow_engine/playwright_workflow/dag.py +1317 -0
  77. devflow_engine/process/__init__.py +5 -0
  78. devflow_engine/process/dag.py +59 -0
  79. devflow_engine/project_registration/__init__.py +3 -0
  80. devflow_engine/project_registration/dag.py +1581 -0
  81. devflow_engine/project_registry.py +109 -0
  82. devflow_engine/prompts/devin/generic/prompt.md +6 -0
  83. devflow_engine/prompts/devin/ideation/prompt.md +263 -0
  84. devflow_engine/prompts/devin/ideation/scenarios.md +5 -0
  85. devflow_engine/prompts/devin/ideation_loop/prompt.md +6 -0
  86. devflow_engine/prompts/devin/insight/prompt.md +11 -0
  87. devflow_engine/prompts/devin/insight/scenarios.md +5 -0
  88. devflow_engine/prompts/devin/intake/prompt.md +15 -0
  89. devflow_engine/prompts/devin/iterate/prompt.md +12 -0
  90. devflow_engine/prompts/devin/shared/eval_doctrine.md +9 -0
  91. devflow_engine/prompts/devin/shared/principles.md +246 -0
  92. devflow_engine/prompts/devin_eval/assessment/prompt.md +18 -0
  93. devflow_engine/prompts/idea/api_ideation_agent/prompt.md +8 -0
  94. devflow_engine/prompts/idea/api_insight_agent/prompt.md +8 -0
  95. devflow_engine/prompts/idea/response_doctrine/prompt.md +18 -0
  96. devflow_engine/prompts/implementation/dependency_assessment/prompt.md +12 -0
  97. devflow_engine/prompts/implementation/green/green/prompt.md +11 -0
  98. devflow_engine/prompts/implementation/green/node_config/prompt.md +3 -0
  99. devflow_engine/prompts/implementation/green_review/outcome_review/prompt.md +5 -0
  100. devflow_engine/prompts/implementation/green_review/prior_run_review/prompt.md +5 -0
  101. devflow_engine/prompts/implementation/red/prompt.md +27 -0
  102. devflow_engine/prompts/implementation/redreview/prompt.md +23 -0
  103. devflow_engine/prompts/implementation/redreview_repair/prompt.md +16 -0
  104. devflow_engine/prompts/implementation/setupdoc/prompt.md +10 -0
  105. devflow_engine/prompts/implementation/story_planning/prompt.md +13 -0
  106. devflow_engine/prompts/implementation/test_design/prompt.md +27 -0
  107. devflow_engine/prompts/integration/README.md +185 -0
  108. devflow_engine/prompts/integration/green/example.md +67 -0
  109. devflow_engine/prompts/integration/green/green/prompt.md +10 -0
  110. devflow_engine/prompts/integration/green/node_config/prompt.md +42 -0
  111. devflow_engine/prompts/integration/green/past_prompts/20260417T212300/green/prompt.md +15 -0
  112. devflow_engine/prompts/integration/green/past_prompts/20260417T212300/node_config/prompt.md +42 -0
  113. devflow_engine/prompts/integration/green_enrich/example.md +79 -0
  114. devflow_engine/prompts/integration/green_enrich/green_enrich/prompt.md +9 -0
  115. devflow_engine/prompts/integration/green_enrich/node_config/prompt.md +41 -0
  116. devflow_engine/prompts/integration/green_enrich/past_prompts/20260417T212300/green_enrich/prompt.md +14 -0
  117. devflow_engine/prompts/integration/green_enrich/past_prompts/20260417T212300/node_config/prompt.md +41 -0
  118. devflow_engine/prompts/integration/red/code_repair/prompt.md +12 -0
  119. devflow_engine/prompts/integration/red/example.md +152 -0
  120. devflow_engine/prompts/integration/red/node_config/prompt.md +86 -0
  121. devflow_engine/prompts/integration/red/past_prompts/20260417T212300/code_repair/prompt.md +19 -0
  122. devflow_engine/prompts/integration/red/past_prompts/20260417T212300/node_config/prompt.md +84 -0
  123. devflow_engine/prompts/integration/red/past_prompts/20260417T212300/red/prompt.md +16 -0
  124. devflow_engine/prompts/integration/red/past_prompts/20260417T212300/red_repair/prompt.md +15 -0
  125. devflow_engine/prompts/integration/red/past_prompts/20260417T215032/code_repair/prompt.md +10 -0
  126. devflow_engine/prompts/integration/red/past_prompts/20260417T215032/node_config/prompt.md +84 -0
  127. devflow_engine/prompts/integration/red/past_prompts/20260417T215032/red_repair/prompt.md +11 -0
  128. devflow_engine/prompts/integration/red/red/prompt.md +11 -0
  129. devflow_engine/prompts/integration/red/red_repair/prompt.md +12 -0
  130. devflow_engine/prompts/integration/red_review/example.md +71 -0
  131. devflow_engine/prompts/integration/red_review/node_config/prompt.md +41 -0
  132. devflow_engine/prompts/integration/red_review/past_prompts/20260417T212300/node_config/prompt.md +41 -0
  133. devflow_engine/prompts/integration/red_review/past_prompts/20260417T212300/red_review/prompt.md +15 -0
  134. devflow_engine/prompts/integration/red_review/red_review/prompt.md +9 -0
  135. devflow_engine/prompts/integration/resolve/example.md +111 -0
  136. devflow_engine/prompts/integration/resolve/node_config/prompt.md +64 -0
  137. devflow_engine/prompts/integration/resolve/past_prompts/20260417T212300/node_config/prompt.md +64 -0
  138. devflow_engine/prompts/integration/resolve/past_prompts/20260417T212300/resolve_implicated_users/prompt.md +15 -0
  139. devflow_engine/prompts/integration/resolve/past_prompts/20260417T212300/resolve_side_effects/prompt.md +15 -0
  140. devflow_engine/prompts/integration/resolve/resolve_implicated_users/prompt.md +10 -0
  141. devflow_engine/prompts/integration/resolve/resolve_side_effects/prompt.md +10 -0
  142. devflow_engine/prompts/integration/validate/build_idea_acceptance_coverage/prompt.md +12 -0
  143. devflow_engine/prompts/integration/validate/code_repair/prompt.md +13 -0
  144. devflow_engine/prompts/integration/validate/example.md +143 -0
  145. devflow_engine/prompts/integration/validate/node_config/prompt.md +87 -0
  146. devflow_engine/prompts/integration/validate/past_prompts/20260417T212300/code_repair/prompt.md +19 -0
  147. devflow_engine/prompts/integration/validate/past_prompts/20260417T212300/node_config/prompt.md +67 -0
  148. devflow_engine/prompts/integration/validate/past_prompts/20260417T212300/validate_enrich_gate/prompt.md +17 -0
  149. devflow_engine/prompts/integration/validate/past_prompts/20260417T212300/validate_repair/prompt.md +16 -0
  150. devflow_engine/prompts/integration/validate/past_prompts/20260417T215032/code_repair/prompt.md +10 -0
  151. devflow_engine/prompts/integration/validate/past_prompts/20260417T215032/node_config/prompt.md +67 -0
  152. devflow_engine/prompts/integration/validate/past_prompts/20260417T215032/validate_repair/prompt.md +9 -0
  153. devflow_engine/prompts/integration/validate/validate_enrich_gate/prompt.md +10 -0
  154. devflow_engine/prompts/integration/validate/validate_repair/prompt.md +20 -0
  155. devflow_engine/prompts/integration/write_workflows/example.md +100 -0
  156. devflow_engine/prompts/integration/write_workflows/node_config/prompt.md +44 -0
  157. devflow_engine/prompts/integration/write_workflows/past_prompts/20260417T212300/node_config/prompt.md +44 -0
  158. devflow_engine/prompts/integration/write_workflows/past_prompts/20260417T212300/write_workflows/prompt.md +17 -0
  159. devflow_engine/prompts/integration/write_workflows/write_workflows/prompt.md +11 -0
  160. devflow_engine/prompts/iterate/README.md +7 -0
  161. devflow_engine/prompts/iterate/coder/prompt.md +11 -0
  162. devflow_engine/prompts/iterate/framer/prompt.md +11 -0
  163. devflow_engine/prompts/iterate/iterator/prompt.md +13 -0
  164. devflow_engine/prompts/iterate/observer/prompt.md +11 -0
  165. devflow_engine/prompts/recovery/diagnosis/prompt.md +7 -0
  166. devflow_engine/prompts/recovery/execution/prompt.md +8 -0
  167. devflow_engine/prompts/recovery/execution_verification/prompt.md +7 -0
  168. devflow_engine/prompts/recovery/failure_investigation/prompt.md +10 -0
  169. devflow_engine/prompts/recovery/preflight_health_repo_repair/prompt.md +8 -0
  170. devflow_engine/prompts/recovery/remediation_execution/prompt.md +11 -0
  171. devflow_engine/prompts/recovery/root_cause_investigation/prompt.md +12 -0
  172. devflow_engine/prompts/scope_idea/doctrine/prompt.md +7 -0
  173. devflow_engine/prompts/source_doc_eval/document/prompt.md +6 -0
  174. devflow_engine/prompts/source_doc_eval/targeted_mutation/prompt.md +9 -0
  175. devflow_engine/prompts/source_doc_mutation/domain_entities/prompt.md +6 -0
  176. devflow_engine/prompts/source_doc_mutation/product_brief/prompt.md +6 -0
  177. devflow_engine/prompts/source_doc_mutation/project_doc_coherence/prompt.md +7 -0
  178. devflow_engine/prompts/source_doc_mutation/project_doc_render/prompt.md +9 -0
  179. devflow_engine/prompts/source_doc_mutation/source_doc_coherence/prompt.md +5 -0
  180. devflow_engine/prompts/source_doc_mutation/source_doc_enrichment_coherence/prompt.md +6 -0
  181. devflow_engine/prompts/source_doc_mutation/user_workflows/prompt.md +6 -0
  182. devflow_engine/prompts/source_scope/doctrine/prompt.md +10 -0
  183. devflow_engine/prompts/ui_grounding/doctrine/prompt.md +7 -0
  184. devflow_engine/recovery/__init__.py +3 -0
  185. devflow_engine/recovery/dag.py +2609 -0
  186. devflow_engine/recovery/models.py +220 -0
  187. devflow_engine/refactor.py +93 -0
  188. devflow_engine/registry/__init__.py +1 -0
  189. devflow_engine/registry/cards.py +238 -0
  190. devflow_engine/registry/domain_normalize.py +60 -0
  191. devflow_engine/registry/effects.py +65 -0
  192. devflow_engine/registry/enforce_report.py +150 -0
  193. devflow_engine/registry/module_cards_classify.py +164 -0
  194. devflow_engine/registry/module_cards_draft.py +184 -0
  195. devflow_engine/registry/module_cards_gate.py +59 -0
  196. devflow_engine/registry/packages.py +347 -0
  197. devflow_engine/registry/pathways.py +323 -0
  198. devflow_engine/review/__init__.py +11 -0
  199. devflow_engine/review/dag.py +588 -0
  200. devflow_engine/review/review_story.py +67 -0
  201. devflow_engine/scope_idea/__init__.py +3 -0
  202. devflow_engine/scope_idea/agentic.py +39 -0
  203. devflow_engine/scope_idea/dag.py +1069 -0
  204. devflow_engine/scope_idea/models.py +175 -0
  205. devflow_engine/skills/builtins/devflow/queue_failure_investigation/SKILL.md +112 -0
  206. devflow_engine/skills/builtins/devflow/queue_idea_to_story/SKILL.md +120 -0
  207. devflow_engine/skills/builtins/devflow/queue_integration/SKILL.md +105 -0
  208. devflow_engine/skills/builtins/devflow/queue_recovery/SKILL.md +108 -0
  209. devflow_engine/skills/builtins/devflow/queue_runtime_core/SKILL.md +155 -0
  210. devflow_engine/skills/builtins/devflow/queue_story_implementation/SKILL.md +122 -0
  211. devflow_engine/skills/builtins/devin/idea_to_story_handoff/SKILL.md +120 -0
  212. devflow_engine/skills/builtins/devin/ideation/SKILL.md +168 -0
  213. devflow_engine/skills/builtins/devin/ideation/state-and-phrasing-reference.md +18 -0
  214. devflow_engine/skills/builtins/devin/insight/SKILL.md +22 -0
  215. devflow_engine/skills/registry.example.yaml +42 -0
  216. devflow_engine/source_doc_assumptions.py +291 -0
  217. devflow_engine/source_doc_mutation_dag.py +1606 -0
  218. devflow_engine/source_doc_mutation_eval.py +417 -0
  219. devflow_engine/source_doc_mutation_worker.py +25 -0
  220. devflow_engine/source_docs_schema.py +207 -0
  221. devflow_engine/source_docs_updater.py +309 -0
  222. devflow_engine/source_scope/__init__.py +15 -0
  223. devflow_engine/source_scope/agentic.py +45 -0
  224. devflow_engine/source_scope/dag.py +1626 -0
  225. devflow_engine/source_scope/models.py +177 -0
  226. devflow_engine/stores/__init__.py +0 -0
  227. devflow_engine/stores/execution_store.py +3534 -0
  228. devflow_engine/story/__init__.py +0 -0
  229. devflow_engine/story/contracts.py +160 -0
  230. devflow_engine/story/discovery.py +47 -0
  231. devflow_engine/story/evidence.py +118 -0
  232. devflow_engine/story/hashing.py +27 -0
  233. devflow_engine/story/implemented_queue_purge.py +148 -0
  234. devflow_engine/story/indexer.py +105 -0
  235. devflow_engine/story/io.py +20 -0
  236. devflow_engine/story/markdown_contracts.py +298 -0
  237. devflow_engine/story/reconciliation.py +408 -0
  238. devflow_engine/story/validate_stories.py +149 -0
  239. devflow_engine/story/validate_tests_story.py +512 -0
  240. devflow_engine/story/validation.py +133 -0
  241. devflow_engine/ui_grounding/__init__.py +11 -0
  242. devflow_engine/ui_grounding/agentic.py +31 -0
  243. devflow_engine/ui_grounding/dag.py +874 -0
  244. devflow_engine/ui_grounding/models.py +224 -0
  245. devflow_engine/ui_grounding/pencil_bridge.py +247 -0
  246. devflow_engine/vendor/__init__.py +0 -0
  247. devflow_engine/vendor/datalumina_genai/__init__.py +11 -0
  248. devflow_engine/vendor/datalumina_genai/core/__init__.py +0 -0
  249. devflow_engine/vendor/datalumina_genai/core/exceptions.py +9 -0
  250. devflow_engine/vendor/datalumina_genai/core/nodes/__init__.py +0 -0
  251. devflow_engine/vendor/datalumina_genai/core/nodes/agent.py +48 -0
  252. devflow_engine/vendor/datalumina_genai/core/nodes/agent_streaming_node.py +26 -0
  253. devflow_engine/vendor/datalumina_genai/core/nodes/base.py +89 -0
  254. devflow_engine/vendor/datalumina_genai/core/nodes/concurrent.py +30 -0
  255. devflow_engine/vendor/datalumina_genai/core/nodes/router.py +69 -0
  256. devflow_engine/vendor/datalumina_genai/core/schema.py +72 -0
  257. devflow_engine/vendor/datalumina_genai/core/task.py +52 -0
  258. devflow_engine/vendor/datalumina_genai/core/validate.py +139 -0
  259. devflow_engine/vendor/datalumina_genai/core/workflow.py +200 -0
  260. devflow_engine/worker.py +1086 -0
  261. devflow_engine/worker_guard.py +233 -0
  262. devflow_engine-1.0.0.dist-info/METADATA +235 -0
  263. devflow_engine-1.0.0.dist-info/RECORD +393 -0
  264. devflow_engine-1.0.0.dist-info/WHEEL +4 -0
  265. devflow_engine-1.0.0.dist-info/entry_points.txt +3 -0
  266. devin/__init__.py +6 -0
  267. devin/dag.py +58 -0
  268. devin/dag_two_arm.py +138 -0
  269. devin/devin_chat_scenario_catalog.json +588 -0
  270. devin/devin_eval.py +677 -0
  271. devin/nodes/__init__.py +0 -0
  272. devin/nodes/ideation/__init__.py +0 -0
  273. devin/nodes/ideation/node.py +195 -0
  274. devin/nodes/ideation/playground.py +267 -0
  275. devin/nodes/ideation/prompt.md +65 -0
  276. devin/nodes/ideation/scenarios/continue_refinement.py +13 -0
  277. devin/nodes/ideation/scenarios/continue_refinement_evals.py +18 -0
  278. devin/nodes/ideation/scenarios/idea_fits_existing_patterns.py +17 -0
  279. devin/nodes/ideation/scenarios/idea_fits_existing_patterns_evals.py +16 -0
  280. devin/nodes/ideation/scenarios/large_idea_split.py +4 -0
  281. devin/nodes/ideation/scenarios/large_idea_split_evals.py +17 -0
  282. devin/nodes/ideation/scenarios/source_documentation_added.py +4 -0
  283. devin/nodes/ideation/scenarios/source_documentation_added_evals.py +16 -0
  284. devin/nodes/ideation/scenarios/user_says_create_it.py +30 -0
  285. devin/nodes/ideation/scenarios/user_says_create_it_evals.py +23 -0
  286. devin/nodes/ideation/scenarios/vague_idea.py +16 -0
  287. devin/nodes/ideation/scenarios/vague_idea_evals.py +47 -0
  288. devin/nodes/ideation/tools.json +312 -0
  289. devin/nodes/insight/__init__.py +0 -0
  290. devin/nodes/insight/node.py +49 -0
  291. devin/nodes/insight/playground.py +154 -0
  292. devin/nodes/insight/prompt.md +61 -0
  293. devin/nodes/insight/scenarios/architecture_pattern_query.py +15 -0
  294. devin/nodes/insight/scenarios/architecture_pattern_query_evals.py +25 -0
  295. devin/nodes/insight/scenarios/codebase_exploration.py +15 -0
  296. devin/nodes/insight/scenarios/codebase_exploration_evals.py +23 -0
  297. devin/nodes/insight/scenarios/devin_ideation_routing.py +19 -0
  298. devin/nodes/insight/scenarios/devin_ideation_routing_evals.py +39 -0
  299. devin/nodes/insight/scenarios/devin_insight_routing.py +20 -0
  300. devin/nodes/insight/scenarios/devin_insight_routing_evals.py +40 -0
  301. devin/nodes/insight/scenarios/operational_debugging.py +15 -0
  302. devin/nodes/insight/scenarios/operational_debugging_evals.py +23 -0
  303. devin/nodes/insight/scenarios/operational_question.py +9 -0
  304. devin/nodes/insight/scenarios/operational_question_evals.py +8 -0
  305. devin/nodes/insight/scenarios/queue_status.py +15 -0
  306. devin/nodes/insight/scenarios/queue_status_evals.py +23 -0
  307. devin/nodes/insight/scenarios/source_doc_explanation.py +14 -0
  308. devin/nodes/insight/scenarios/source_doc_explanation_evals.py +21 -0
  309. devin/nodes/insight/scenarios/worker_state_check.py +15 -0
  310. devin/nodes/insight/scenarios/worker_state_check_evals.py +22 -0
  311. devin/nodes/insight/tools.json +126 -0
  312. devin/nodes/intake/__init__.py +0 -0
  313. devin/nodes/intake/node.py +27 -0
  314. devin/nodes/intake/playground.py +47 -0
  315. devin/nodes/intake/prompt.md +12 -0
  316. devin/nodes/intake/scenarios/ideation_routing.py +4 -0
  317. devin/nodes/intake/scenarios/ideation_routing_evals.py +5 -0
  318. devin/nodes/intake/scenarios/insight_routing.py +4 -0
  319. devin/nodes/intake/scenarios/insight_routing_evals.py +5 -0
  320. devin/nodes/iterate/README.md +44 -0
  321. devin/nodes/iterate/__init__.py +1 -0
  322. devin/nodes/iterate/_archived_design_stages/01-objectives-requirements.md +112 -0
  323. devin/nodes/iterate/_archived_design_stages/02-evals.md +131 -0
  324. devin/nodes/iterate/_archived_design_stages/03-tools-and-boundaries.md +110 -0
  325. devin/nodes/iterate/_archived_design_stages/04-harness-and-playground.md +32 -0
  326. devin/nodes/iterate/_archived_design_stages/05-prompt-deferred.md +11 -0
  327. devin/nodes/iterate/_archived_design_stages/coder_agent_design/01-objectives-requirements.md +20 -0
  328. devin/nodes/iterate/_archived_design_stages/coder_agent_design/02-evals.md +8 -0
  329. devin/nodes/iterate/_archived_design_stages/coder_agent_design/03-tools-and-boundaries.md +14 -0
  330. devin/nodes/iterate/_archived_design_stages/coder_agent_design/04-harness-and-playground.md +12 -0
  331. devin/nodes/iterate/_archived_design_stages/framer_agent_design/01-objectives-requirements.md +20 -0
  332. devin/nodes/iterate/_archived_design_stages/framer_agent_design/02-evals.md +8 -0
  333. devin/nodes/iterate/_archived_design_stages/framer_agent_design/03-tools-and-boundaries.md +13 -0
  334. devin/nodes/iterate/_archived_design_stages/framer_agent_design/04-harness-and-playground.md +12 -0
  335. devin/nodes/iterate/_archived_design_stages/iterator_agent_design/01-objectives-requirements.md +25 -0
  336. devin/nodes/iterate/_archived_design_stages/iterator_agent_design/02-evals.md +9 -0
  337. devin/nodes/iterate/_archived_design_stages/iterator_agent_design/03-tools-and-boundaries.md +14 -0
  338. devin/nodes/iterate/_archived_design_stages/iterator_agent_design/04-harness-and-playground.md +12 -0
  339. devin/nodes/iterate/_archived_design_stages/observer_agent_design/01-objectives-requirements.md +20 -0
  340. devin/nodes/iterate/_archived_design_stages/observer_agent_design/02-evals.md +8 -0
  341. devin/nodes/iterate/_archived_design_stages/observer_agent_design/03-tools-and-boundaries.md +14 -0
  342. devin/nodes/iterate/_archived_design_stages/observer_agent_design/04-harness-and-playground.md +13 -0
  343. devin/nodes/iterate/agent-roles.md +89 -0
  344. devin/nodes/iterate/agents/README.md +10 -0
  345. devin/nodes/iterate/artifacts.md +504 -0
  346. devin/nodes/iterate/contract.md +100 -0
  347. devin/nodes/iterate/eval-plan.md +74 -0
  348. devin/nodes/iterate/node.py +100 -0
  349. devin/nodes/iterate/pipeline/README.md +13 -0
  350. devin/nodes/iterate/playground-contract.md +76 -0
  351. devin/nodes/iterate/prompt.md +11 -0
  352. devin/nodes/iterate/scenarios/README.md +38 -0
  353. devin/nodes/iterate/scenarios/artifact-and-loop-scenarios.md +101 -0
  354. devin/nodes/iterate/scenarios/coder_artifact_alignment.py +32 -0
  355. devin/nodes/iterate/scenarios/coder_artifact_alignment_evals.py +45 -0
  356. devin/nodes/iterate/scenarios/coder_bounded_fix.py +27 -0
  357. devin/nodes/iterate/scenarios/coder_bounded_fix_evals.py +45 -0
  358. devin/nodes/iterate/scenarios/devin_iterate_routing.py +21 -0
  359. devin/nodes/iterate/scenarios/devin_iterate_routing_evals.py +36 -0
  360. devin/nodes/iterate/scenarios/framer_scope_boundary.py +25 -0
  361. devin/nodes/iterate/scenarios/framer_scope_boundary_evals.py +57 -0
  362. devin/nodes/iterate/scenarios/framer_task_framing.py +25 -0
  363. devin/nodes/iterate/scenarios/framer_task_framing_evals.py +58 -0
  364. devin/nodes/iterate/scenarios/iterate_error_fix.py +21 -0
  365. devin/nodes/iterate/scenarios/iterate_error_fix_evals.py +39 -0
  366. devin/nodes/iterate/scenarios/iterate_quick_change.py +21 -0
  367. devin/nodes/iterate/scenarios/iterate_quick_change_evals.py +35 -0
  368. devin/nodes/iterate/scenarios/iterate_to_idea_promotion.py +23 -0
  369. devin/nodes/iterate/scenarios/iterate_to_idea_promotion_evals.py +53 -0
  370. devin/nodes/iterate/scenarios/iterate_to_insight_reroute.py +23 -0
  371. devin/nodes/iterate/scenarios/iterate_to_insight_reroute_evals.py +53 -0
  372. devin/nodes/iterate/scenarios/observer_evidence_seam.py +28 -0
  373. devin/nodes/iterate/scenarios/observer_evidence_seam_evals.py +55 -0
  374. devin/nodes/iterate/scenarios/observer_repro_creation.py +28 -0
  375. devin/nodes/iterate/scenarios/observer_repro_creation_evals.py +45 -0
  376. devin/nodes/iterate/scenarios/routing-matrix.md +45 -0
  377. devin/nodes/shared/__init__.py +0 -0
  378. devin/nodes/shared/filemaker_expert.md +80 -0
  379. devin/nodes/shared/filemaker_expert.py +354 -0
  380. devin/nodes/shared/filemaker_expert_eval/runner.py +176 -0
  381. devin/nodes/shared/filemaker_expert_eval/scenarios.json +65 -0
  382. devin/nodes/shared/goldilocks_advisor_eval/runner.py +214 -0
  383. devin/nodes/shared/goldilocks_advisor_eval/scenarios.json +58 -0
  384. devin/nodes/shared/helpers.py +156 -0
  385. devin/nodes/shared/idea_compliance_advisor_eval/runner.py +252 -0
  386. devin/nodes/shared/idea_compliance_advisor_eval/scenarios.json +75 -0
  387. devin/nodes/shared/models.py +44 -0
  388. devin/nodes/shared/post.py +40 -0
  389. devin/nodes/shared/router.py +107 -0
  390. devin/nodes/shared/tools.py +191 -0
  391. devin/shared/devin-chat-rubric.md +237 -0
  392. devin/shared/devin-chat-scenario-suite.md +90 -0
  393. 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"]