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
File without changes
@@ -0,0 +1,160 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from .markdown_contracts import (
6
+ ALLOWED_PLANES,
7
+ MANDATORY_CONTRACT_FORMATION_MODE_LINE,
8
+ ParsedStoryBlock,
9
+ StoryContract,
10
+ is_uuid4,
11
+ )
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class ContractIssue:
16
+ code: str
17
+ message: str
18
+ path: str
19
+
20
+
21
+ def validate_story_contract(contract: StoryContract, *, start_line: int = 1) -> list[ContractIssue]:
22
+ issues: list[ContractIssue] = []
23
+
24
+ if not contract.story_uuid:
25
+ issues.append(
26
+ ContractIssue(
27
+ "missing_story_uuid",
28
+ "Story contract must include story_uuid",
29
+ f"{contract.source_path}:{start_line}:story_uuid",
30
+ )
31
+ )
32
+ elif not is_uuid4(contract.story_uuid):
33
+ issues.append(
34
+ ContractIssue(
35
+ "invalid_story_uuid",
36
+ "story_uuid must be a uuid-v4 string",
37
+ f"{contract.source_path}:{start_line}:story_uuid",
38
+ )
39
+ )
40
+
41
+ if not contract.story_id:
42
+ issues.append(
43
+ ContractIssue(
44
+ "missing_story_id",
45
+ "Story contract must include story_id",
46
+ f"{contract.source_path}:{start_line}:story_id",
47
+ )
48
+ )
49
+
50
+ if not contract.title:
51
+ issues.append(
52
+ ContractIssue(
53
+ "missing_title",
54
+ "Story contract must include title",
55
+ f"{contract.source_path}:{start_line}:title",
56
+ )
57
+ )
58
+
59
+ if not contract.required_planes:
60
+ issues.append(
61
+ ContractIssue(
62
+ "missing_required_planes",
63
+ "Story contract must include required_planes",
64
+ f"{contract.source_path}:{start_line}:required_planes",
65
+ )
66
+ )
67
+ else:
68
+ invalid = [p for p in contract.required_planes if p not in ALLOWED_PLANES]
69
+ if invalid:
70
+ issues.append(
71
+ ContractIssue(
72
+ "invalid_required_planes",
73
+ f"required_planes contains invalid plane(s): {invalid}; allowed={ALLOWED_PLANES}",
74
+ f"{contract.source_path}:{start_line}:required_planes",
75
+ )
76
+ )
77
+
78
+ if not contract.contract_formation_mode_line_present:
79
+ issues.append(
80
+ ContractIssue(
81
+ "missing_contract_formation_mode",
82
+ f"Story must include mandatory line: {MANDATORY_CONTRACT_FORMATION_MODE_LINE}",
83
+ f"{contract.source_path}:{start_line}",
84
+ )
85
+ )
86
+
87
+ if not contract.plane_oracles:
88
+ issues.append(
89
+ ContractIssue(
90
+ "missing_plane_oracles",
91
+ "Story must include plane_oracles list block",
92
+ f"{contract.source_path}:{start_line}:plane_oracles",
93
+ )
94
+ )
95
+ return issues
96
+
97
+ # Validate one entry per required plane
98
+ seen: dict[str, int] = {}
99
+ for idx, po in enumerate(contract.plane_oracles):
100
+ if not po.plane:
101
+ issues.append(
102
+ ContractIssue(
103
+ "invalid_plane_oracle_plane",
104
+ "plane_oracles item missing plane",
105
+ f"{contract.source_path}:{start_line}:plane_oracles[{idx}].plane",
106
+ )
107
+ )
108
+ continue
109
+ seen[po.plane] = seen.get(po.plane, 0) + 1
110
+ if not po.oracle:
111
+ issues.append(
112
+ ContractIssue(
113
+ "invalid_plane_oracle_oracle",
114
+ "plane_oracles item missing oracle",
115
+ f"{contract.source_path}:{start_line}:plane_oracles[{idx}].oracle",
116
+ )
117
+ )
118
+ if not po.anchor:
119
+ issues.append(
120
+ ContractIssue(
121
+ "invalid_plane_oracle_anchor",
122
+ "plane_oracles item missing anchor",
123
+ f"{contract.source_path}:{start_line}:plane_oracles[{idx}].anchor",
124
+ )
125
+ )
126
+
127
+ missing = [p for p in contract.required_planes if seen.get(p, 0) == 0]
128
+ dupes = [p for p, n in seen.items() if n > 1]
129
+ extra = [p for p in seen if p not in contract.required_planes]
130
+
131
+ if missing:
132
+ issues.append(
133
+ ContractIssue(
134
+ "missing_plane_oracles_for_required_planes",
135
+ f"Missing plane_oracles entries for required_planes: {missing}",
136
+ f"{contract.source_path}:{start_line}:plane_oracles",
137
+ )
138
+ )
139
+ if dupes:
140
+ issues.append(
141
+ ContractIssue(
142
+ "duplicate_plane_oracles",
143
+ f"Duplicate plane_oracles entries for plane(s): {dupes}",
144
+ f"{contract.source_path}:{start_line}:plane_oracles",
145
+ )
146
+ )
147
+ if extra:
148
+ issues.append(
149
+ ContractIssue(
150
+ "extra_plane_oracles",
151
+ f"plane_oracles contains plane(s) not in required_planes: {extra}",
152
+ f"{contract.source_path}:{start_line}:plane_oracles",
153
+ )
154
+ )
155
+
156
+ return issues
157
+
158
+
159
+ def validate_parsed_story_block(block: ParsedStoryBlock) -> list[ContractIssue]:
160
+ return validate_story_contract(block.contract, start_line=block.start_line)
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ LEGACY_CANONICAL_STORY_PATH = Path("ai_docs/context/v2/project_docs/user_stories.md")
7
+ CANONICAL_STORY_SOURCES_DIR_PATH = Path("ai_docs/context/v2/project_docs/user_stories")
8
+ GENERATED_STORY_SOURCES_DIR_PATH = CANONICAL_STORY_SOURCES_DIR_PATH / "generated"
9
+ RECONCILED_STORY_DOC_PATH = GENERATED_STORY_SOURCES_DIR_PATH / "_reconciled_story_queue.md"
10
+
11
+ def _iter_story_markdown_paths(stories_dir: Path) -> list[Path]:
12
+ return sorted(
13
+ [p for p in stories_dir.rglob("*.md") if "/.git/" not in p.as_posix()],
14
+ key=lambda p: p.as_posix(),
15
+ )
16
+
17
+
18
+ def canonical_story_reconciliation_doc_path(repo_root: Path) -> Path:
19
+ repo_root = Path(repo_root)
20
+ legacy = repo_root / LEGACY_CANONICAL_STORY_PATH
21
+ if legacy.exists():
22
+ return legacy
23
+ return repo_root / RECONCILED_STORY_DOC_PATH
24
+
25
+
26
+ def canonical_story_generated_dir(repo_root: Path) -> Path:
27
+ return Path(repo_root) / GENERATED_STORY_SOURCES_DIR_PATH
28
+
29
+
30
+ def get_story_source_paths(repo_root: Path) -> list[Path]:
31
+ """Return deterministic story source paths from the current canonical tree.
32
+
33
+ Current truth is the markdown set under ``ai_docs/context/v2/project_docs/user_stories/``.
34
+ ``user_stories.md`` is retained as a legacy compatibility source when it exists.
35
+ """
36
+
37
+ repo_root = Path(repo_root)
38
+ legacy = repo_root / LEGACY_CANONICAL_STORY_PATH
39
+ stories_dir = repo_root / CANONICAL_STORY_SOURCES_DIR_PATH
40
+ paths = _iter_story_markdown_paths(stories_dir) if stories_dir.exists() else []
41
+
42
+ if legacy.exists():
43
+ return [legacy, *paths]
44
+ if paths:
45
+ return paths
46
+
47
+ raise FileNotFoundError(f"{stories_dir} (or legacy {legacy})")
@@ -0,0 +1,118 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from ..llm.execution_context import get_execution_context
9
+
10
+
11
+ def safe_story_id(story_id: str) -> str:
12
+ return re.sub(r"[^A-Za-z0-9_.-]+", "_", str(story_id or "").strip()) or "unknown_story"
13
+
14
+
15
+ def story_dir(*, repo_root: Path, story_id: str) -> Path:
16
+ return _story_state_root(repo_root=repo_root) / safe_story_id(story_id)
17
+
18
+
19
+ def _story_state_root(*, repo_root: Path) -> Path:
20
+ ctx = get_execution_context() or {}
21
+ override = str(ctx.get("devflow_story_state_root") or "").strip()
22
+ if override:
23
+ return Path(override)
24
+ return repo_root / ".devflow" / "stories"
25
+
26
+
27
+ def story_verification_path(*, repo_root: Path, story_id: str) -> Path:
28
+ return story_dir(repo_root=repo_root, story_id=story_id) / "verification.json"
29
+
30
+
31
+ def _load_json_if_exists(path: Path) -> dict[str, Any] | None:
32
+ if not path.exists() or not path.is_file():
33
+ return None
34
+ try:
35
+ payload = json.loads(path.read_text(encoding="utf-8"))
36
+ except Exception:
37
+ return None
38
+ return payload if isinstance(payload, dict) else None
39
+
40
+
41
+ def load_story_verification(*, repo_root: Path, story_id: str) -> dict[str, Any] | None:
42
+ return _load_json_if_exists(story_verification_path(repo_root=repo_root, story_id=story_id))
43
+
44
+
45
+ def persist_story_verification(*, repo_root: Path, story_id: str, payload: dict[str, Any]) -> Path:
46
+ path = story_verification_path(repo_root=repo_root, story_id=story_id)
47
+ path.parent.mkdir(parents=True, exist_ok=True)
48
+ path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
49
+ return path
50
+
51
+
52
+ def record_story_verification(
53
+ *,
54
+ repo_root: Path,
55
+ story_id: str,
56
+ story_uuid: str | None = None,
57
+ source: str,
58
+ test_paths: list[str] | None = None,
59
+ validator: dict[str, Any] | None = None,
60
+ tests: dict[str, Any] | None = None,
61
+ ) -> tuple[dict[str, Any], Path]:
62
+ current = load_story_verification(repo_root=repo_root, story_id=story_id) or {}
63
+ canonical_test_paths = [
64
+ str(path).strip() for path in (test_paths or current.get("canonical_test_paths") or []) if str(path).strip()
65
+ ]
66
+ latest_result = {
67
+ "source": str(source or "").strip() or "unknown",
68
+ "story_id": story_id,
69
+ "story_uuid": str(story_uuid or current.get("story_uuid") or "").strip() or None,
70
+ "canonical_test_paths": canonical_test_paths,
71
+ "validator": dict(validator or {}),
72
+ "tests": dict(tests or {}),
73
+ }
74
+ passed = bool(latest_result["validator"].get("ok")) and bool(latest_result["tests"].get("ok"))
75
+ latest_result["passed"] = passed
76
+
77
+ payload: dict[str, Any] = {
78
+ "schema_version": 1,
79
+ "story_id": story_id,
80
+ "story_uuid": latest_result["story_uuid"],
81
+ "canonical_test_paths": canonical_test_paths,
82
+ "latest_result": latest_result,
83
+ "latest_passing_verification": current.get("latest_passing_verification"),
84
+ }
85
+ if passed:
86
+ payload["latest_passing_verification"] = latest_result
87
+ path = persist_story_verification(repo_root=repo_root, story_id=story_id, payload=payload)
88
+ return payload, path
89
+
90
+
91
+ def resolve_story_verification_state(*, repo_root: Path, story_id: str) -> dict[str, Any]:
92
+ from ..implementation.test_runtime import load_story_test_runtime_contract, story_test_runtime_contract_path
93
+
94
+ runtime_contract = load_story_test_runtime_contract(repo_root=repo_root, story_id=story_id) or {}
95
+ verification = load_story_verification(repo_root=repo_root, story_id=story_id) or {}
96
+ canonical_test_paths = [
97
+ str(path).strip()
98
+ for path in (
99
+ runtime_contract.get("test_paths")
100
+ or verification.get("canonical_test_paths")
101
+ or ((verification.get("latest_result") or {}).get("canonical_test_paths"))
102
+ or []
103
+ )
104
+ if str(path).strip()
105
+ ]
106
+ latest_passing_verification = verification.get("latest_passing_verification")
107
+ return {
108
+ "story_id": story_id,
109
+ "runtime_contract_path": str(story_test_runtime_contract_path(repo_root=repo_root, story_id=story_id)),
110
+ "verification_path": str(story_verification_path(repo_root=repo_root, story_id=story_id)),
111
+ "has_runtime_contract": bool(runtime_contract),
112
+ "has_canonical_test_paths": bool(canonical_test_paths),
113
+ "canonical_test_paths": canonical_test_paths,
114
+ "has_verification_evidence": bool(verification),
115
+ "latest_result": dict(verification.get("latest_result") or {}) if isinstance(verification.get("latest_result"), dict) else None,
116
+ "has_latest_passing_verification": isinstance(latest_passing_verification, dict) and bool(latest_passing_verification),
117
+ "latest_passing_verification": dict(latest_passing_verification or {}) if isinstance(latest_passing_verification, dict) else None,
118
+ }
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+
5
+
6
+ def normalize_for_contract_hash(text: str) -> str:
7
+ """Normalize story markdown for stable contract hashing.
8
+
9
+ Policy (tests/spec):
10
+ - Remove UTF-8 BOM if present
11
+ - Normalize newlines to '\n'
12
+ - Trim trailing whitespace per line
13
+ - Ensure exactly one trailing newline
14
+ """
15
+
16
+ if text.startswith("\ufeff"):
17
+ text = text.lstrip("\ufeff")
18
+
19
+ text = text.replace("\r\n", "\n").replace("\r", "\n")
20
+
21
+ lines = [ln.rstrip() for ln in text.split("\n")]
22
+ return "\n".join(lines).rstrip("\n") + "\n"
23
+
24
+
25
+ def compute_contract_hash(text: str) -> str:
26
+ normalized = normalize_for_contract_hash(text)
27
+ return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
@@ -0,0 +1,148 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import time
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from ..devflow_state import fetch_story_statuses_from_supabase
9
+ from ..stores.execution_store import ExecutionStore
10
+
11
+ ACTIVE_STORY_QUEUE_STATUSES = ("queued", "claimed", "in_progress", "blocked")
12
+ PURGE_NOTE = "story queue completed locally because Supabase already marks the story implemented"
13
+
14
+
15
+ def _load_active_story_queue_rows(store: ExecutionStore, *, project_id: str | None) -> list[dict[str, Any]]:
16
+ query = (
17
+ "SELECT story_queue_id, project_id, enqueue_run_id, story_artifact_id, story_id, title, status, "
18
+ "claimed_by_worker_id, claimed_at, started_run_id, finished_run_id, failure_message, failure_context_json, "
19
+ "created_at, updated_at "
20
+ "FROM story_queue WHERE status IN ('queued','claimed','in_progress','blocked')"
21
+ )
22
+ params: tuple[Any, ...] = ()
23
+ if project_id is not None:
24
+ query += " AND project_id=?"
25
+ params = (project_id,)
26
+ query += " ORDER BY created_at ASC, story_queue_id ASC"
27
+
28
+ with store._connect() as conn:
29
+ rows = conn.execute(query, params).fetchall()
30
+
31
+ payloads: list[dict[str, Any]] = []
32
+ for row in rows:
33
+ payload = dict(row)
34
+ try:
35
+ payload["failure_context"] = (
36
+ json.loads(str(payload.get("failure_context_json") or ""))
37
+ if payload.get("failure_context_json")
38
+ else None
39
+ )
40
+ except Exception:
41
+ payload["failure_context"] = None
42
+ payloads.append(payload)
43
+ return payloads
44
+
45
+
46
+ def purge_implemented_story_queue(
47
+ *,
48
+ repo_root: Path,
49
+ project_id: str | None = None,
50
+ apply: bool = False,
51
+ ) -> dict[str, Any]:
52
+ repo_root = repo_root.expanduser().resolve()
53
+ store = ExecutionStore(repo_root / ".devflow" / "execution.sqlite")
54
+ queue_rows = _load_active_story_queue_rows(store, project_id=project_id)
55
+ story_ids = sorted(
56
+ {
57
+ str(row.get("story_id") or "").strip()
58
+ for row in queue_rows
59
+ if str(row.get("story_id") or "").strip()
60
+ }
61
+ )
62
+ supabase_status_by_story_id = fetch_story_statuses_from_supabase(story_ids=story_ids)
63
+
64
+ candidates: list[dict[str, Any]] = []
65
+ implemented_story_ids: set[str] = set()
66
+ for row in queue_rows:
67
+ story_id = str(row.get("story_id") or "").strip()
68
+ supabase_status = str(supabase_status_by_story_id.get(story_id) or "").strip().lower()
69
+ if supabase_status != "implemented":
70
+ continue
71
+ implemented_story_ids.add(story_id)
72
+ candidates.append(
73
+ {
74
+ "story_queue_id": str(row.get("story_queue_id") or ""),
75
+ "project_id": str(row.get("project_id") or ""),
76
+ "story_id": story_id,
77
+ "title": str(row.get("title") or ""),
78
+ "local_status_before": str(row.get("status") or ""),
79
+ "local_status_after": "completed",
80
+ "supabase_status": supabase_status,
81
+ "claimed_by_worker_id": row.get("claimed_by_worker_id"),
82
+ "claimed_at": row.get("claimed_at"),
83
+ "started_run_id": row.get("started_run_id"),
84
+ "finished_run_id": row.get("finished_run_id"),
85
+ }
86
+ )
87
+
88
+ applied_changes: list[dict[str, Any]] = []
89
+ if apply and candidates:
90
+ now = int(time.time())
91
+ touched_story_ids: set[str] = set()
92
+ with store._connect() as conn:
93
+ for row in queue_rows:
94
+ story_id = str(row.get("story_id") or "").strip()
95
+ if story_id not in implemented_story_ids:
96
+ continue
97
+ story_queue_id = str(row.get("story_queue_id") or "")
98
+ failure_context = row.get("failure_context") if isinstance(row.get("failure_context"), dict) else {}
99
+ failure_context.update(
100
+ {
101
+ "suppressed_because_supabase_status": "implemented",
102
+ "suppressed_from_active_execution_queue": True,
103
+ "suppressed_at": now,
104
+ }
105
+ )
106
+ cursor = conn.execute(
107
+ (
108
+ "UPDATE story_queue SET status='completed', claimed_by_worker_id=NULL, claimed_at=NULL, "
109
+ "started_run_id=NULL, finished_run_id=NULL, "
110
+ "failure_message=?, failure_context_json=?, updated_at=? "
111
+ "WHERE story_queue_id=? AND status IN ('queued','claimed','in_progress','blocked')"
112
+ ),
113
+ (PURGE_NOTE, json.dumps(failure_context, sort_keys=True), now, story_queue_id),
114
+ )
115
+ if int(getattr(cursor, "rowcount", 0) or 0) <= 0:
116
+ continue
117
+ touched_story_ids.add(story_id)
118
+ applied_changes.append(
119
+ {
120
+ "story_queue_id": story_queue_id,
121
+ "story_id": story_id,
122
+ "project_id": str(row.get("project_id") or ""),
123
+ "title": str(row.get("title") or ""),
124
+ }
125
+ )
126
+ for story_id in sorted(touched_story_ids):
127
+ store.update_story_status(story_id=story_id, status="implemented")
128
+
129
+ summary = {
130
+ "active_queue_rows_examined": len(queue_rows),
131
+ "active_story_ids_examined": len(story_ids),
132
+ "supabase_status_rows_found": len(supabase_status_by_story_id),
133
+ "implemented_story_ids_matched": len(implemented_story_ids),
134
+ "queue_rows_to_complete": len(candidates),
135
+ "queue_rows_completed": len(applied_changes),
136
+ }
137
+
138
+ return {
139
+ "repo_root": str(repo_root),
140
+ "db_path": str((repo_root / ".devflow" / "execution.sqlite").resolve()),
141
+ "project_id": project_id,
142
+ "apply": apply,
143
+ "summary": summary,
144
+ "candidates": candidates,
145
+ "applied_changes": applied_changes,
146
+ "supabase_status_by_story_id": supabase_status_by_story_id,
147
+ "note": PURGE_NOTE,
148
+ }
@@ -0,0 +1,105 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import sqlite3
5
+ from dataclasses import asdict, dataclass
6
+ from pathlib import Path
7
+
8
+ from .discovery import get_story_source_paths
9
+ from .hashing import compute_contract_hash
10
+ from .markdown_contracts import ParsedStoryBlock, parse_story_contracts_from_markdown
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class StoryIndexEntry:
15
+ story_uuid: str
16
+ story_id: str
17
+ title: str
18
+ source_path: str
19
+ contract_hash: str
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class StoryIndex:
24
+ entries: list[StoryIndexEntry]
25
+
26
+ def to_json_bytes(self) -> bytes:
27
+ payload = {"entries": [asdict(e) for e in self.entries]}
28
+ # Deterministic serialization: sort keys + stable separators.
29
+ return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode(
30
+ "utf-8"
31
+ )
32
+
33
+
34
+ def _iter_story_blocks(repo_root: Path) -> list[ParsedStoryBlock]:
35
+ blocks: list[ParsedStoryBlock] = []
36
+ for p in get_story_source_paths(repo_root):
37
+ text = p.read_text(encoding="utf-8")
38
+ blocks.extend(parse_story_contracts_from_markdown(text, source_path=str(p)))
39
+ return blocks
40
+
41
+
42
+ def build_story_index(repo_root: Path) -> StoryIndex:
43
+ """Build a deterministic story index from canonical + artifact sources.
44
+
45
+ Raises:
46
+ ValueError: on duplicate story_uuid across sources.
47
+ """
48
+
49
+ repo_root = Path(repo_root)
50
+ blocks = _iter_story_blocks(repo_root)
51
+
52
+ seen_uuid_to_path: dict[str, str] = {}
53
+ entries: list[StoryIndexEntry] = []
54
+
55
+ for b in blocks:
56
+ c = b.contract
57
+ if c.story_uuid in seen_uuid_to_path:
58
+ first = seen_uuid_to_path[c.story_uuid]
59
+ raise ValueError(
60
+ f"duplicate story_uuid {c.story_uuid} across sources: {first} and {c.source_path}"
61
+ )
62
+ if c.story_uuid:
63
+ seen_uuid_to_path[c.story_uuid] = c.source_path
64
+
65
+ entries.append(
66
+ StoryIndexEntry(
67
+ story_uuid=c.story_uuid,
68
+ story_id=c.story_id,
69
+ title=c.title,
70
+ source_path=c.source_path,
71
+ contract_hash=compute_contract_hash(c.raw_text),
72
+ )
73
+ )
74
+
75
+ # Deterministic ordering for downstream tools: sort by story_id then uuid.
76
+ entries_sorted = sorted(entries, key=lambda e: (e.story_id, e.story_uuid, e.source_path))
77
+ return StoryIndex(entries=entries_sorted)
78
+
79
+
80
+ def persist_story_index(db_path: Path, index: StoryIndex) -> None:
81
+ db_path = Path(db_path)
82
+ db_path.parent.mkdir(parents=True, exist_ok=True)
83
+
84
+ with sqlite3.connect(db_path) as conn:
85
+ conn.execute(
86
+ """
87
+ CREATE TABLE IF NOT EXISTS story_index (
88
+ story_uuid TEXT PRIMARY KEY,
89
+ story_id TEXT NOT NULL,
90
+ title TEXT NOT NULL,
91
+ source_path TEXT NOT NULL,
92
+ contract_hash TEXT NOT NULL
93
+ )
94
+ """
95
+ )
96
+ conn.execute("DELETE FROM story_index")
97
+
98
+ conn.executemany(
99
+ "INSERT INTO story_index (story_uuid, story_id, title, source_path, contract_hash) VALUES (?, ?, ?, ?, ?)",
100
+ [
101
+ (e.story_uuid, e.story_id, e.title, e.source_path, e.contract_hash)
102
+ for e in index.entries
103
+ ],
104
+ )
105
+ conn.commit()
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ import yaml
7
+
8
+
9
+ def load_yaml(path: Path) -> dict[str, Any]:
10
+ data = yaml.safe_load(path.read_text(encoding="utf-8"))
11
+ if data is None:
12
+ return {}
13
+ if not isinstance(data, dict):
14
+ raise ValueError(f"YAML at {path} must be a mapping")
15
+ return data
16
+
17
+
18
+ def dump_yaml(path: Path, obj: dict[str, Any]) -> None:
19
+ path.parent.mkdir(parents=True, exist_ok=True)
20
+ path.write_text(yaml.safe_dump(obj, sort_keys=False, allow_unicode=True), encoding="utf-8")