codd-dev 1.35.0__tar.gz → 1.37.0__tar.gz

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 (223) hide show
  1. {codd_dev-1.35.0 → codd_dev-1.37.0}/PKG-INFO +1 -1
  2. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/cli.py +118 -12
  3. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/finding.py +37 -3
  4. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/extract_ai.py +57 -3
  5. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/extractor.py +65 -25
  6. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/parsing.py +390 -0
  7. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/git_patcher.py +15 -12
  8. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/llm_repair_engine.py +105 -27
  9. codd_dev-1.37.0/codd/repair/templates/repair_strategy_meta.md +44 -0
  10. {codd_dev-1.35.0 → codd_dev-1.37.0}/pyproject.toml +1 -1
  11. {codd_dev-1.35.0 → codd_dev-1.37.0}/.gitignore +0 -0
  12. {codd_dev-1.35.0 → codd_dev-1.37.0}/LICENSE +0 -0
  13. {codd_dev-1.35.0 → codd_dev-1.37.0}/README.md +0 -0
  14. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/__init__.py +0 -0
  15. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/__main__.py +0 -0
  16. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/_git_helper.py +0 -0
  17. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/ask_user_question_adapter.py +0 -0
  18. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/assembler.py +0 -0
  19. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/bridge.py +0 -0
  20. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/clustering.py +0 -0
  21. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/coherence_adapters.py +0 -0
  22. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/coherence_engine.py +0 -0
  23. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/config.py +0 -0
  24. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/contracts.py +0 -0
  25. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/coverage_auditor.py +0 -0
  26. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/coverage_metrics.py +0 -0
  27. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/__init__.py +0 -0
  28. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/builder.py +0 -0
  29. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/__init__.py +0 -0
  30. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/depends_on_consistency.py +0 -0
  31. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/deployment_completeness.py +0 -0
  32. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/edge_validity.py +0 -0
  33. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/environment_coverage.py +0 -0
  34. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/implementation_coverage.py +0 -0
  35. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/node_completeness.py +0 -0
  36. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/task_completion.py +0 -0
  37. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/transitive_closure.py +0 -0
  38. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/checks/user_journey_coherence.py +0 -0
  39. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/coverage_axes.py +0 -0
  40. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/cli.yaml +0 -0
  41. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/cpp_embedded.yaml +0 -0
  42. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/csharp.yaml +0 -0
  43. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/elixir.yaml +0 -0
  44. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/generic.yaml +0 -0
  45. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/iot.yaml +0 -0
  46. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/java.yaml +0 -0
  47. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/kotlin.yaml +0 -0
  48. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/mobile.yaml +0 -0
  49. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/ruby.yaml +0 -0
  50. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/rust.yaml +0 -0
  51. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/scala.yaml +0 -0
  52. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/swift.yaml +0 -0
  53. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/test_frameworks.yaml +0 -0
  54. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/defaults/web.yaml +0 -0
  55. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/extractor.py +0 -0
  56. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/dag/runner.py +0 -0
  57. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/defaults.yaml +0 -0
  58. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deploy_targets/__init__.py +0 -0
  59. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deploy_targets/app_service.py +0 -0
  60. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deploy_targets/base.py +0 -0
  61. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deploy_targets/docker_compose.py +0 -0
  62. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployer.py +0 -0
  63. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/__init__.py +0 -0
  64. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/checks/__init__.py +0 -0
  65. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/defaults/deploy_targets.yaml +0 -0
  66. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/defaults/runtime_capability_inference.yaml +0 -0
  67. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/defaults/schema_providers.yaml +0 -0
  68. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/defaults/verification_templates.yaml +0 -0
  69. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/extractor.py +0 -0
  70. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/__init__.py +0 -0
  71. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/ai_command.py +0 -0
  72. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/llm_consideration.py +0 -0
  73. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/schema/__init__.py +0 -0
  74. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/schema/prisma.py +0 -0
  75. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/target/__init__.py +0 -0
  76. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/target/docker_compose.py +0 -0
  77. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/__init__.py +0 -0
  78. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/assertion_handlers.py +0 -0
  79. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/cdp_browser.py +0 -0
  80. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/cdp_engines.py +0 -0
  81. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/cdp_launchers.py +0 -0
  82. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/cdp_wire.py +0 -0
  83. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/curl.py +0 -0
  84. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/form_strategies.py +0 -0
  85. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/means_catalog.py +0 -0
  86. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/deployment/providers/verification/playwright.py +0 -0
  87. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/design_md.py +0 -0
  88. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/diff/__init__.py +0 -0
  89. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/diff/apply.py +0 -0
  90. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/diff/engine.py +0 -0
  91. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/diff/persistence.py +0 -0
  92. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/diff/templates/diff_prompt.md +0 -0
  93. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/drift.py +0 -0
  94. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/e2e_extractor.py +0 -0
  95. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/e2e_generator.py +0 -0
  96. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/e2e_runner.py +0 -0
  97. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/__init__.py +0 -0
  98. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/apply.py +0 -0
  99. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/engine.py +0 -0
  100. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/formatters/__init__.py +0 -0
  101. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/formatters/base.py +0 -0
  102. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/formatters/interactive.py +0 -0
  103. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/formatters/json_fmt.py +0 -0
  104. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/formatters/md.py +0 -0
  105. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/lexicon_loader.py +0 -0
  106. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/persistence.py +0 -0
  107. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/elicit/templates/elicit_prompt_L0.md +0 -0
  108. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/env_refs.py +0 -0
  109. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/fixer.py +0 -0
  110. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/fixup_drift.py +0 -0
  111. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/fixup_drift_strategies/__init__.py +0 -0
  112. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/fixup_drift_strategies/design_token_drift.py +0 -0
  113. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/fixup_drift_strategies/lexicon_violation.py +0 -0
  114. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/fixup_drift_strategies/url_drift.py +0 -0
  115. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/generator.py +0 -0
  116. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/graph.py +0 -0
  117. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/hitl_session.py +0 -0
  118. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/hooks/__init__.py +0 -0
  119. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/hooks/pre-commit +0 -0
  120. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/hooks/recipes/claude_settings_example.json +0 -0
  121. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/hooks/recipes/codex_hook.sh +0 -0
  122. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/hooks/recipes/git_post_commit.sh +0 -0
  123. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/hooks/recipes/git_pre_commit.sh +0 -0
  124. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/implementer/__init__.py +0 -0
  125. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/implementer/chunked_runner.py +0 -0
  126. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/implementer/typecheck_loop.py +0 -0
  127. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/implementer.py +0 -0
  128. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/inheritance.py +0 -0
  129. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/knowledge_fetcher.py +0 -0
  130. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/lexicon.py +0 -0
  131. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/__init__.py +0 -0
  132. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/approval.py +0 -0
  133. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/best_practice_augmenter.py +0 -0
  134. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/criteria_expander.py +0 -0
  135. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/design_doc_extractor.py +0 -0
  136. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/impl_step_deriver.py +0 -0
  137. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/means_catalog_loader.py +0 -0
  138. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/parser.py +0 -0
  139. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/plan_deriver.py +0 -0
  140. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/prompt_builder.py +0 -0
  141. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/strategy_validator.py +0 -0
  142. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/templates/best_practice_augment_meta.md +0 -0
  143. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/templates/criteria_expand_meta.md +0 -0
  144. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/templates/design_doc_extract_meta.md +0 -0
  145. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/templates/impl_step_derive_meta.md +0 -0
  146. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/templates/implementation_step_catalog.yaml +0 -0
  147. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/templates/meta_instruction.md +0 -0
  148. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/templates/plan_derive_meta.md +0 -0
  149. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/llm/templates/verification_means_catalog.yaml +0 -0
  150. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/mcp_server.py +0 -0
  151. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/measure.py +0 -0
  152. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/planner.py +0 -0
  153. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/policy.py +0 -0
  154. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/preflight/__init__.py +0 -0
  155. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/preflight/defaults/cli.yaml +0 -0
  156. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/preflight/defaults/iot.yaml +0 -0
  157. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/preflight/defaults/mobile.yaml +0 -0
  158. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/preflight/defaults/web.yaml +0 -0
  159. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/propagate.py +0 -0
  160. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/propagator.py +0 -0
  161. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/registry.py +0 -0
  162. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/__init__.py +0 -0
  163. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/approval_repair.py +0 -0
  164. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/engine.py +0 -0
  165. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/history.py +0 -0
  166. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/loop.py +0 -0
  167. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/primary_picker.py +0 -0
  168. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/proof_breaks.py +0 -0
  169. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/repair_result.py +0 -0
  170. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/repairability_classifier.py +0 -0
  171. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/schema.py +0 -0
  172. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/templates/analyze_meta.md +0 -0
  173. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/templates/propose_meta.md +0 -0
  174. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/templates/repairability_meta.md +0 -0
  175. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair/verify_runner.py +0 -0
  176. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/repair_slice.py +0 -0
  177. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/require.py +0 -0
  178. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/require_plugins.py +0 -0
  179. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/require_propagate.py +0 -0
  180. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/required_artifacts/defaults/cli.yaml +0 -0
  181. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/required_artifacts/defaults/iot.yaml +0 -0
  182. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/required_artifacts/defaults/mobile.yaml +0 -0
  183. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/required_artifacts/defaults/web.yaml +0 -0
  184. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/required_artifacts_deriver.py +0 -0
  185. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/requirement_completeness/defaults/cli.yaml +0 -0
  186. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/requirement_completeness/defaults/iot.yaml +0 -0
  187. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/requirement_completeness/defaults/mobile.yaml +0 -0
  188. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/requirement_completeness/defaults/web.yaml +0 -0
  189. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/requirement_completeness_auditor.py +0 -0
  190. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/restore.py +0 -0
  191. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/routes_extractor.py +0 -0
  192. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/scanner.py +0 -0
  193. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/schema_refs.py +0 -0
  194. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/screen_flow_validator.py +0 -0
  195. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/screen_transition_extractor.py +0 -0
  196. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/screen_transitions/defaults.yaml +0 -0
  197. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/synth.py +0 -0
  198. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/codd.yaml.tmpl +0 -0
  199. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/conventions.yaml.tmpl +0 -0
  200. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/data_dependencies.yaml.tmpl +0 -0
  201. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/doc_links.yaml.tmpl +0 -0
  202. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/extract_ai_prompt_baseline.md +0 -0
  203. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/extracted/api-contract.md.j2 +0 -0
  204. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/extracted/architecture-overview.md.j2 +0 -0
  205. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/extracted/module-detail.md.j2 +0 -0
  206. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/extracted/schema-design.md.j2 +0 -0
  207. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/extracted/system-context.md.j2 +0 -0
  208. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/gitignore.tmpl +0 -0
  209. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/lexicon_questions.md +0 -0
  210. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/lexicon_schema.yaml +0 -0
  211. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/templates/overrides.yaml.tmpl +0 -0
  212. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/traceability.py +0 -0
  213. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/validator.py +0 -0
  214. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/watch/__init__.py +0 -0
  215. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/watch/events.py +0 -0
  216. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/watch/propagation_log.py +0 -0
  217. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/watch/propagation_pipeline.py +0 -0
  218. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/watch/test_runner.py +0 -0
  219. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/watch/watcher.py +0 -0
  220. {codd_dev-1.35.0 → codd_dev-1.37.0}/codd/wiring.py +0 -0
  221. {codd_dev-1.35.0 → codd_dev-1.37.0}/docs/cookbook/cdp_browser/README.md +0 -0
  222. {codd_dev-1.35.0 → codd_dev-1.37.0}/docs/requirements/README.md +0 -0
  223. {codd_dev-1.35.0 → codd_dev-1.37.0}/tests/integration/standalone_repair_skeleton/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codd-dev
3
- Version: 1.35.0
3
+ Version: 1.37.0
4
4
  Summary: CoDD: Coherence-Driven Development — cross-artifact change impact analysis
5
5
  Project-URL: Homepage, https://github.com/yohey-w/codd-dev
6
6
  Project-URL: Repository, https://github.com/yohey-w/codd-dev
@@ -288,13 +288,9 @@ def _resolve_bootstrap_codd_dir(project_root: Path) -> Path:
288
288
  return existing
289
289
 
290
290
  hidden_dir = project_root / ".codd"
291
- default_dir = project_root / "codd"
292
291
  if hidden_dir.exists():
293
292
  return hidden_dir
294
- if default_dir.exists():
295
- # Avoid writing config into projects whose source package is already named codd/.
296
- return hidden_dir
297
- return default_dir
293
+ return hidden_dir
298
294
 
299
295
 
300
296
  def _format_yaml_list(items: list[str], *, indent: int = 4) -> str:
@@ -1028,6 +1024,75 @@ def elicit_apply_cmd(input_file: Path, format_name: str, project_path: str) -> N
1028
1024
  click.echo(f"Updated: {file_path}")
1029
1025
 
1030
1026
 
1027
+ @main.command("brownfield")
1028
+ @click.argument("target_path", type=click.Path(file_okay=False, path_type=Path))
1029
+ @click.option(
1030
+ "--requirements",
1031
+ "requirements_path",
1032
+ type=click.Path(dir_okay=False, path_type=Path),
1033
+ default=None,
1034
+ help="Requirements file (default: <target>/.codd/requirements.md; skipped if absent).",
1035
+ )
1036
+ @click.option(
1037
+ "--lexicon",
1038
+ "lexicon_path",
1039
+ type=click.Path(path_type=Path),
1040
+ default=None,
1041
+ help="Lexicon directory or manifest (default: <target>/.codd/lexicon; discovery mode if absent).",
1042
+ )
1043
+ @click.option(
1044
+ "--format",
1045
+ "format_name",
1046
+ type=click.Choice(["json", "md"]),
1047
+ default="md",
1048
+ show_default=True,
1049
+ help="Integrated report format.",
1050
+ )
1051
+ @click.option("--output", type=click.Path(dir_okay=False, path_type=Path), default=None, help="Output report file.")
1052
+ @click.option(
1053
+ "--ai-cmd",
1054
+ default=None,
1055
+ help="Override AI CLI command for diff and elicit engines.",
1056
+ )
1057
+ def brownfield_cmd(
1058
+ target_path: Path,
1059
+ requirements_path: Path | None,
1060
+ lexicon_path: Path | None,
1061
+ format_name: str,
1062
+ output: Path | None,
1063
+ ai_cmd: str | None,
1064
+ ) -> None:
1065
+ """Run brownfield extract, diff, elicit, and merged reporting."""
1066
+ from codd.brownfield.pipeline import BrownfieldPipeline, format_brownfield_result
1067
+
1068
+ project_root = target_path.expanduser().resolve()
1069
+ try:
1070
+ result = BrownfieldPipeline(ai_command=ai_cmd).run(
1071
+ project_root,
1072
+ requirements_path=requirements_path,
1073
+ lexicon_path=lexicon_path,
1074
+ )
1075
+ formatted = format_brownfield_result(result, format_name)
1076
+ output_path = _project_file(
1077
+ project_root,
1078
+ output,
1079
+ f".codd/brownfield_report.{format_name}",
1080
+ )
1081
+ output_path.parent.mkdir(parents=True, exist_ok=True)
1082
+ output_path.write_text(formatted, encoding="utf-8")
1083
+ except (OSError, TypeError, ValueError, json.JSONDecodeError, yaml.YAMLError) as exc:
1084
+ click.echo(f"Error: {exc}")
1085
+ raise SystemExit(1)
1086
+
1087
+ click.echo(
1088
+ "Brownfield pipeline complete: "
1089
+ f"diff_findings={len(result.diff_findings)}, "
1090
+ f"elicit_findings={len(result.elicit_findings)}, "
1091
+ f"merged_findings={len(result.merged_findings)}"
1092
+ )
1093
+ click.echo(f"Output: {_display_path(output_path, project_root)}")
1094
+
1095
+
1031
1096
  @main.group("diff", invoke_without_command=True)
1032
1097
  @click.option("--extract-input", type=click.Path(dir_okay=False, path_type=Path), default=None)
1033
1098
  @click.option("--requirements", "requirements_path", type=click.Path(dir_okay=False, path_type=Path), default=None)
@@ -1068,7 +1133,7 @@ def diff_cmd(
1068
1133
  from codd.elicit.formatters.md import MdFormatter
1069
1134
 
1070
1135
  project_root = Path(project_path).resolve()
1071
- extract_path = _project_file(project_root, extract_input, "codd/extracted.md")
1136
+ extract_path = _resolve_diff_extract_input(project_root, extract_input)
1072
1137
  req_path = _project_file(project_root, requirements_path, "docs/requirements/requirements.md")
1073
1138
  output_path = _project_file(project_root, output, "drift_findings.md") if output is not None or format_name == "md" else None
1074
1139
 
@@ -1161,6 +1226,37 @@ def _project_file(project_root: Path, value: Path | None, default: str) -> Path:
1161
1226
  return project_root / path
1162
1227
 
1163
1228
 
1229
+ def _resolve_diff_extract_input(project_root: Path, value: Path | None) -> Path:
1230
+ """Locate the extract-input file, preferring isolated `.codd/extract/` output.
1231
+
1232
+ Order of resolution:
1233
+ 1. Explicit ``--extract-input`` value (absolute or project-relative).
1234
+ 2. ``.codd/extract/extracted.md`` (default Issue #17 isolation target).
1235
+ 3. Top-level ``extracted.md`` aggregated output if present.
1236
+ 4. First ``.codd/extract/modules/*.md`` module file (deterministic by name).
1237
+ 5. Legacy fallback ``codd/extracted.md`` (preserved for older projects).
1238
+ """
1239
+ if value is not None:
1240
+ candidate = value.expanduser()
1241
+ return candidate if candidate.is_absolute() else project_root / candidate
1242
+
1243
+ candidates: list[Path] = [
1244
+ project_root / ".codd" / "extract" / "extracted.md",
1245
+ project_root / "extracted.md",
1246
+ ]
1247
+ modules_dir = project_root / ".codd" / "extract" / "modules"
1248
+ if modules_dir.is_dir():
1249
+ module_files = sorted(modules_dir.glob("*.md"))
1250
+ if module_files:
1251
+ candidates.append(module_files[0])
1252
+ candidates.append(project_root / "codd" / "extracted.md")
1253
+
1254
+ for candidate in candidates:
1255
+ if candidate.is_file():
1256
+ return candidate
1257
+ return candidates[0]
1258
+
1259
+
1164
1260
  def _build_diff_engine(engine_cls: Any, ai_cmd: str | None, project_root: Path) -> Any:
1165
1261
  attempts = (
1166
1262
  lambda: engine_cls(llm_client=ai_cmd, project_root=project_root),
@@ -2203,7 +2299,8 @@ def e2e_generate_legacy(path: str, base_url: str, output: str | None, framework:
2203
2299
  @click.option("--path", default=".", help="Project root directory")
2204
2300
  @click.option("--language", default=None, help="Override language detection (python/typescript/javascript/go — full support; java — symbols only)")
2205
2301
  @click.option("--source-dirs", default=None, help="Comma-separated source directories (default: auto-detect)")
2206
- @click.option("--output", default=None, help="Output directory (default: <config-dir>/extracted/)")
2302
+ @click.option("--output", default=None, help="Output directory (default: <project-root>/.codd/extract/)")
2303
+ @click.option("--init", "initialize", is_flag=True, help="Add brownfield init metadata to generated YAML/Markdown")
2207
2304
  @click.option("--ai", is_flag=True, default=False, help="Use AI-powered extraction (6-layer MECE design docs)")
2208
2305
  @click.option(
2209
2306
  "--ai-cmd",
@@ -2237,6 +2334,7 @@ def extract(
2237
2334
  language: str | None,
2238
2335
  source_dirs: str | None,
2239
2336
  output: str | None,
2337
+ initialize: bool,
2240
2338
  ai: bool,
2241
2339
  ai_cmd: str | None,
2242
2340
  prompt_file: str | None,
@@ -2249,9 +2347,8 @@ def extract(
2249
2347
  Default mode: static analysis (no AI, pure structural facts).
2250
2348
  With --ai: AI-powered 6-layer MECE extraction using claude --print.
2251
2349
 
2252
- Output goes to the discovered CoDD config dir as draft documents
2253
- (`codd/extracted/` or `.codd/extracted/`). Review and promote
2254
- confirmed docs when ready.
2350
+ Output goes to `.codd/extract/` by default. Review and promote confirmed
2351
+ docs when ready.
2255
2352
  """
2256
2353
  if ctx.invoked_subcommand is not None:
2257
2354
  return
@@ -2259,7 +2356,13 @@ def extract(
2259
2356
  project_root = Path(path).resolve()
2260
2357
  bootstrap_codd_dir = _resolve_bootstrap_codd_dir(project_root)
2261
2358
  dirs = [d.strip() for d in source_dirs.split(",") if d.strip()] if source_dirs else None
2262
- output_path = Path(output) if output else bootstrap_codd_dir / "extracted"
2359
+ output_path = Path(output) if output else project_root / ".codd" / "extract"
2360
+ if output and not output_path.is_absolute():
2361
+ output_path = project_root / output_path
2362
+ init_metadata = None
2363
+ if initialize:
2364
+ from codd.extractor import build_extract_init_metadata
2365
+ init_metadata = build_extract_init_metadata(project_root)
2263
2366
 
2264
2367
  if layer == "routes":
2265
2368
  from codd.config import load_project_config
@@ -2314,6 +2417,9 @@ def extract(
2314
2417
  except Exception as exc:
2315
2418
  click.echo(f"Error: {exc}")
2316
2419
  raise SystemExit(1)
2420
+ if init_metadata is not None:
2421
+ from codd.extractor import add_extract_init_frontmatter
2422
+ add_extract_init_frontmatter(result.generated_files, init_metadata)
2317
2423
 
2318
2424
  facts = extract_facts(project_root, language, dirs)
2319
2425
  config_path, generated_config = _ensure_bootstrap_codd_yaml(
@@ -2340,7 +2446,7 @@ def extract(
2340
2446
  from codd.extractor import run_extract
2341
2447
 
2342
2448
  try:
2343
- result = run_extract(project_root, language, dirs, str(output_path))
2449
+ result = run_extract(project_root, language, dirs, str(output_path), init_metadata=init_metadata)
2344
2450
  except Exception as exc:
2345
2451
  click.echo(f"Error: {exc}")
2346
2452
  raise SystemExit(1)
@@ -41,13 +41,12 @@ class Finding:
41
41
 
42
42
  finding_id = str(payload.get("id", "")).strip()
43
43
  kind = str(payload.get("kind", "")).strip()
44
- severity = str(payload.get("severity", "")).strip()
44
+ raw_severity = str(payload.get("severity", "")).strip()
45
45
  if not finding_id:
46
46
  raise ValueError("Finding id is required")
47
47
  if not kind:
48
48
  raise ValueError("Finding kind is required")
49
- if severity not in _SEVERITIES:
50
- raise ValueError(f"Finding severity must be one of {sorted(_SEVERITIES)}")
49
+ severity = _coerce_severity(raw_severity)
51
50
 
52
51
  source = str(payload.get("source", "greenfield")).strip() or "greenfield"
53
52
  if source not in _SOURCES:
@@ -85,6 +84,41 @@ def _optional_text(value: Any) -> str | None:
85
84
  return text if text else None
86
85
 
87
86
 
87
+ _SEVERITY_ALIASES = {
88
+ "blocker": "critical",
89
+ "fatal": "critical",
90
+ "severe": "critical",
91
+ "urgent": "critical",
92
+ "error": "high",
93
+ "major": "high",
94
+ "important": "high",
95
+ "warn": "medium",
96
+ "warning": "medium",
97
+ "moderate": "medium",
98
+ "minor": "info",
99
+ "low": "info",
100
+ "informational": "info",
101
+ "note": "info",
102
+ "trivial": "info",
103
+ }
104
+
105
+
106
+ def _coerce_severity(raw: str) -> str:
107
+ """Map a raw severity string into the canonical Literal set, with a safe fallback.
108
+
109
+ Strict membership is preserved for canonical values; common synonyms are mapped
110
+ deterministically; anything unknown defaults to ``info`` so downstream tools stay
111
+ operational rather than aborting on LLM drift. Generic mapping only — no stack /
112
+ framework / domain literals.
113
+ """
114
+ cleaned = raw.lower().strip()
115
+ if cleaned in _SEVERITIES:
116
+ return cleaned
117
+ if cleaned in _SEVERITY_ALIASES:
118
+ return _SEVERITY_ALIASES[cleaned]
119
+ return "info"
120
+
121
+
88
122
  @dataclass
89
123
  class ElicitResult:
90
124
  findings: list[Finding] = field(default_factory=list)
@@ -49,6 +49,12 @@ SCHEMA_PATTERNS = [
49
49
  "prisma/schema.prisma", "db/schema.rb", "alembic/versions",
50
50
  ]
51
51
 
52
+ # Generic source extensions included in the AI context scan. The deterministic
53
+ # parser decides language-specific structure later; this layer keeps raw text.
54
+ SOURCE_EXTENSIONS = {
55
+ ".go", ".java", ".js", ".jsx", ".php", ".py", ".rb", ".ts", ".tsx", ".vue",
56
+ }
57
+
52
58
  # Max file size to include in prompt (bytes)
53
59
  MAX_FILE_SIZE = 50_000
54
60
 
@@ -171,9 +177,45 @@ def _find_source_files(root: Path) -> list[Path]:
171
177
  # Filter out files in skip dirs
172
178
  found = [f for f in found if not any(s in f.parts for s in SKIP_DIRS)]
173
179
  files.extend(found)
180
+ files.extend(_find_source_files_by_extension(root))
174
181
  return sorted(set(files))
175
182
 
176
183
 
184
+ def _find_source_files_by_extension(root: Path) -> list[Path]:
185
+ """Find source files generically by extension, preserving raw text for AI."""
186
+ files: list[Path] = []
187
+ for current, dirs, filenames in os.walk(root):
188
+ dirs[:] = [
189
+ d for d in dirs
190
+ if d not in SKIP_DIRS and not d.startswith(".")
191
+ ]
192
+ current_path = Path(current)
193
+ for filename in filenames:
194
+ path = current_path / filename
195
+ if path.suffix not in SOURCE_EXTENSIONS:
196
+ continue
197
+ try:
198
+ rel_parts = path.relative_to(root).parts
199
+ except ValueError:
200
+ continue
201
+ if _is_test_path(rel_parts) or any(part in SKIP_DIRS for part in rel_parts):
202
+ continue
203
+ files.append(path)
204
+ return files
205
+
206
+
207
+ def _is_test_path(parts: tuple[str, ...]) -> bool:
208
+ if any(part in {"test", "tests", "spec", "__tests__"} for part in parts[:-1]):
209
+ return True
210
+ filename = parts[-1].lower() if parts else ""
211
+ return (
212
+ filename.startswith("test_")
213
+ or filename.endswith(("_test.py", "_spec.rb"))
214
+ or ".test." in filename
215
+ or ".spec." in filename
216
+ )
217
+
218
+
177
219
  def _find_iac_files(root: Path) -> list[Path]:
178
220
  """Find IaC and DevOps files."""
179
221
  iac: list[Path] = []
@@ -198,6 +240,8 @@ def _find_iac_files(root: Path) -> list[Path]:
198
240
  def _find_test_files(root: Path) -> list[Path]:
199
241
  """Find test files (paths only, not contents)."""
200
242
  patterns = [
243
+ "tests/test_*.py", "tests/**/test_*.py",
244
+ "test/test_*.py", "test/**/test_*.py",
201
245
  "tests/**/*.test.*", "tests/**/*.spec.*",
202
246
  "test/**/*.test.*", "test/**/*.spec.*",
203
247
  "src/**/*.test.*", "src/**/*.spec.*",
@@ -206,11 +250,21 @@ def _find_test_files(root: Path) -> list[Path]:
206
250
  tests: list[Path] = []
207
251
  for pattern in patterns:
208
252
  found = list(root.glob(pattern))
209
- found = [f for f in found if not any(s in f.parts for s in SKIP_DIRS)]
253
+ found = [
254
+ f for f in found
255
+ if not any(s in _relative_parts(root, f) for s in SKIP_DIRS)
256
+ ]
210
257
  tests.extend(found)
211
258
  return sorted(set(tests))
212
259
 
213
260
 
261
+ def _relative_parts(root: Path, path: Path) -> tuple[str, ...]:
262
+ try:
263
+ return path.relative_to(root).parts
264
+ except ValueError:
265
+ return path.parts
266
+
267
+
214
268
  def pre_scan(project_root: Path) -> PreScanResult:
215
269
  """Phase 1: Deterministic pre-scan of the project."""
216
270
  result = PreScanResult(
@@ -456,11 +510,11 @@ def run_extract_ai(
456
510
  Args:
457
511
  project_root: Path to the project to extract from.
458
512
  ai_command: AI CLI command (e.g. 'claude --print --model claude-opus-4-6').
459
- output_dir: Output directory (default: {project_root}/codd/extracted/).
513
+ output_dir: Output directory (default: {project_root}/.codd/extract/).
460
514
  prompt_file: Path to a custom prompt file. Overrides the built-in baseline preset.
461
515
  """
462
516
  project_root = project_root.resolve()
463
- out = Path(output_dir) if output_dir else project_root / "codd" / "extracted"
517
+ out = Path(output_dir) if output_dir else project_root / ".codd" / "extract"
464
518
  out.mkdir(parents=True, exist_ok=True)
465
519
 
466
520
  # Phase 1: Pre-scan
@@ -12,6 +12,7 @@ Two-phase architecture:
12
12
  import os
13
13
  import re
14
14
  from dataclasses import dataclass, field
15
+ from datetime import datetime
15
16
  from pathlib import Path
16
17
  from typing import Any
17
18
 
@@ -125,6 +126,59 @@ class ExtractResult:
125
126
  source_dirs: list[str]
126
127
 
127
128
 
129
+ def build_extract_init_metadata(project_root: Path, extracted_at: str | None = None) -> dict[str, str]:
130
+ """Build generic brownfield extraction metadata for generated YAML/Markdown."""
131
+ timestamp = extracted_at or datetime.now().astimezone().replace(microsecond=0).isoformat()
132
+ return {
133
+ "version": "1.0",
134
+ "extracted_at": timestamp,
135
+ "source": project_root.resolve().as_posix(),
136
+ }
137
+
138
+
139
+ def add_extract_init_frontmatter(paths: list[Path], metadata: dict[str, str]) -> None:
140
+ """Add codd init metadata to generated Markdown frontmatter or YAML payloads."""
141
+ for path in paths:
142
+ suffix = path.suffix.lower()
143
+ if suffix in {".md", ".markdown"}:
144
+ _upsert_markdown_codd_metadata(path, metadata)
145
+ elif suffix in {".yaml", ".yml"}:
146
+ _upsert_yaml_codd_metadata(path, metadata)
147
+
148
+
149
+ def _merge_codd_metadata(payload: Any, metadata: dict[str, str]) -> dict[str, Any]:
150
+ if not isinstance(payload, dict):
151
+ payload = {}
152
+ codd = payload.get("codd")
153
+ if not isinstance(codd, dict):
154
+ codd = {}
155
+ codd.update(metadata)
156
+ payload["codd"] = codd
157
+ return payload
158
+
159
+
160
+ def _upsert_markdown_codd_metadata(path: Path, metadata: dict[str, str]) -> None:
161
+ text = path.read_text(encoding="utf-8")
162
+ match = re.match(r"\A---\s*\n(.*?)\n---\s*\n?", text, re.DOTALL)
163
+ if match:
164
+ frontmatter = yaml.safe_load(match.group(1)) or {}
165
+ body = text[match.end():]
166
+ else:
167
+ frontmatter = {}
168
+ body = text
169
+
170
+ payload = _merge_codd_metadata(frontmatter, metadata)
171
+ rendered = yaml.safe_dump(payload, sort_keys=False, allow_unicode=True)
172
+ separator = "" if body.startswith("\n") else "\n"
173
+ path.write_text(f"---\n{rendered}---\n{separator}{body}", encoding="utf-8")
174
+
175
+
176
+ def _upsert_yaml_codd_metadata(path: Path, metadata: dict[str, str]) -> None:
177
+ payload = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
178
+ merged = _merge_codd_metadata(payload, metadata)
179
+ path.write_text(yaml.safe_dump(merged, sort_keys=False, allow_unicode=True), encoding="utf-8")
180
+
181
+
128
182
  # ═══════════════════════════════════════════════════════════
129
183
  # Phase 1: Extract Facts (deterministic, no AI)
130
184
  # ═══════════════════════════════════════════════════════════
@@ -695,28 +749,13 @@ def _detect_patterns(facts: ProjectFacts, project_root: Path):
695
749
 
696
750
 
697
751
  def _detect_python_patterns(facts: ProjectFacts, content: str):
698
- frameworks = {
699
- "fastapi": "FastAPI", "flask": "Flask", "django": "Django",
700
- "starlette": "Starlette", "tornado": "Tornado", "aiohttp": "aiohttp",
701
- }
702
- orms = {
703
- "sqlalchemy": "SQLAlchemy", "django": "Django ORM",
704
- "tortoise-orm": "Tortoise ORM", "peewee": "Peewee",
705
- "sqlmodel": "SQLModel", "prisma": "Prisma",
706
- }
707
- test_fw = {
708
- "pytest": "pytest", "unittest": "unittest", "nose": "nose2",
709
- }
752
+ """No-op: framework/ORM/test detection is delegated to LLM (extract --ai).
710
753
 
711
- for key, name in frameworks.items():
712
- if key in content.lower():
713
- facts.detected_frameworks.append(name)
714
- for key, name in orms.items():
715
- if key in content.lower() and not facts.detected_orm:
716
- facts.detected_orm = name
717
- for key, name in test_fw.items():
718
- if key in content.lower() and not facts.detected_test_framework:
719
- facts.detected_test_framework = name
754
+ Removed in v1.36.0 to honor Generality Gate. Hard-coded framework/ORM/test
755
+ dictionaries violated the constraint that CoDD core must remain stack-agnostic.
756
+ The downstream AI extraction path (codd/extract_ai.py) infers these dynamically.
757
+ """
758
+ return
720
759
 
721
760
 
722
761
  def _detect_js_patterns(facts: ProjectFacts, content: str):
@@ -973,7 +1012,8 @@ def synth_architecture(facts: ProjectFacts, output_dir: Path) -> Path:
973
1012
 
974
1013
  def run_extract(project_root: Path, language: str | None = None,
975
1014
  source_dirs: list[str] | None = None,
976
- output: str | None = None) -> ExtractResult:
1015
+ output: str | None = None,
1016
+ init_metadata: dict[str, str] | None = None) -> ExtractResult:
977
1017
  """Run full extract pipeline: facts → docs."""
978
1018
 
979
1019
  # Try to load config if it exists
@@ -994,11 +1034,11 @@ def run_extract(project_root: Path, language: str | None = None,
994
1034
  # Phase 2: Generate docs
995
1035
  if output:
996
1036
  output_dir = Path(output)
997
- elif codd_dir is not None:
998
- output_dir = codd_dir / "extracted"
999
1037
  else:
1000
- output_dir = project_root / "codd" / "extracted"
1038
+ output_dir = project_root / ".codd" / "extract"
1001
1039
  generated = synth_docs(facts, output_dir)
1040
+ if init_metadata is not None:
1041
+ add_extract_init_frontmatter(generated, init_metadata)
1002
1042
 
1003
1043
  return ExtractResult(
1004
1044
  output_dir=output_dir,