specfact-cli 0.20.0__tar.gz → 0.20.1__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 (166) hide show
  1. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/PKG-INFO +1 -1
  2. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/pyproject.toml +1 -1
  3. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/__init__.py +1 -1
  4. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/__init__.py +1 -1
  5. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/generate.py +63 -68
  6. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/init.py +95 -9
  7. specfact_cli-0.20.1/src/specfact_cli/commands/repro.py +459 -0
  8. specfact_cli-0.20.1/src/specfact_cli/utils/env_manager.py +443 -0
  9. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/validators/repro_checker.py +154 -52
  10. specfact_cli-0.20.0/src/specfact_cli/commands/repro.py +0 -226
  11. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/.gitignore +0 -0
  12. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/LICENSE.md +0 -0
  13. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/README.md +0 -0
  14. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/mappings/node-async.yaml +0 -0
  15. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/mappings/python-async.yaml +0 -0
  16. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/mappings/speckit-default.yaml +0 -0
  17. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/shared/cli-enforcement.md +0 -0
  18. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.01-import.md +0 -0
  19. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.02-plan.md +0 -0
  20. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.03-review.md +0 -0
  21. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.04-sdd.md +0 -0
  22. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.05-enforce.md +0 -0
  23. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.06-sync.md +0 -0
  24. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.07-contracts.md +0 -0
  25. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.compare.md +0 -0
  26. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.validate.md +0 -0
  27. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/schemas/deviation.schema.json +0 -0
  28. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/schemas/plan.schema.json +0 -0
  29. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/schemas/protocol.schema.json +0 -0
  30. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/templates/github-action.yml.j2 +0 -0
  31. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/templates/persona/architect.md.j2 +0 -0
  32. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/templates/persona/developer.md.j2 +0 -0
  33. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/templates/persona/product-owner.md.j2 +0 -0
  34. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/templates/plan.bundle.yaml.j2 +0 -0
  35. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/templates/pr-template.md.j2 +0 -0
  36. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/templates/protocol.yaml.j2 +0 -0
  37. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/templates/telemetry.yaml.example +0 -0
  38. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/agents/__init__.py +0 -0
  39. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/agents/analyze_agent.py +0 -0
  40. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/agents/base.py +0 -0
  41. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/agents/plan_agent.py +0 -0
  42. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/agents/registry.py +0 -0
  43. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/agents/sync_agent.py +0 -0
  44. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/__init__.py +0 -0
  45. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
  46. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
  47. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
  48. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
  49. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
  50. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
  51. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
  52. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
  53. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
  54. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/cli.py +0 -0
  55. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/__init__.py +0 -0
  56. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/analyze.py +0 -0
  57. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/bridge.py +0 -0
  58. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/contract_cmd.py +0 -0
  59. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/drift.py +0 -0
  60. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/enforce.py +0 -0
  61. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/implement.py +0 -0
  62. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/import_cmd.py +0 -0
  63. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/migrate.py +0 -0
  64. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/plan.py +0 -0
  65. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/project_cmd.py +0 -0
  66. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/sdd.py +0 -0
  67. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/spec.py +0 -0
  68. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/sync.py +0 -0
  69. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/common/__init__.py +0 -0
  70. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/common/logger_setup.py +0 -0
  71. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/common/logging_utils.py +0 -0
  72. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/common/text_utils.py +0 -0
  73. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/common/utils.py +0 -0
  74. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/comparators/__init__.py +0 -0
  75. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/comparators/plan_comparator.py +0 -0
  76. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
  77. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
  78. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/__init__.py +0 -0
  79. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/contract_generator.py +0 -0
  80. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/openapi_extractor.py +0 -0
  81. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/persona_exporter.py +0 -0
  82. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/plan_generator.py +0 -0
  83. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/protocol_generator.py +0 -0
  84. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/report_generator.py +0 -0
  85. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/task_generator.py +0 -0
  86. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/test_to_openapi.py +0 -0
  87. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/workflow_generator.py +0 -0
  88. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/importers/__init__.py +0 -0
  89. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/importers/speckit_converter.py +0 -0
  90. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/importers/speckit_scanner.py +0 -0
  91. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/integrations/__init__.py +0 -0
  92. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/integrations/specmatic.py +0 -0
  93. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/merge/__init__.py +0 -0
  94. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/merge/resolver.py +0 -0
  95. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/migrations/__init__.py +0 -0
  96. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/migrations/plan_migrator.py +0 -0
  97. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/__init__.py +0 -0
  98. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/bridge.py +0 -0
  99. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/contract.py +0 -0
  100. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/deviation.py +0 -0
  101. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/enforcement.py +0 -0
  102. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/persona_template.py +0 -0
  103. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/plan.py +0 -0
  104. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/project.py +0 -0
  105. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/protocol.py +0 -0
  106. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/quality.py +0 -0
  107. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/sdd.py +0 -0
  108. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/source_tracking.py +0 -0
  109. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/task.py +0 -0
  110. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/modes/__init__.py +0 -0
  111. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/modes/detector.py +0 -0
  112. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/modes/router.py +0 -0
  113. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/parsers/__init__.py +0 -0
  114. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/parsers/persona_importer.py +0 -0
  115. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/resources/semgrep/async.yml +0 -0
  116. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
  117. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
  118. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/runtime.py +0 -0
  119. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/__init__.py +0 -0
  120. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/bridge_probe.py +0 -0
  121. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/bridge_sync.py +0 -0
  122. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/bridge_watch.py +0 -0
  123. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/change_detector.py +0 -0
  124. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/code_to_spec.py +0 -0
  125. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/drift_detector.py +0 -0
  126. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/repository_sync.py +0 -0
  127. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/spec_to_code.py +0 -0
  128. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/spec_to_tests.py +0 -0
  129. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/speckit_sync.py +0 -0
  130. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/watcher.py +0 -0
  131. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
  132. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/telemetry.py +0 -0
  133. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/templates/__init__.py +0 -0
  134. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/templates/bridge_templates.py +0 -0
  135. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/templates/specification_templates.py +0 -0
  136. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/__init__.py +0 -0
  137. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
  138. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/bundle_loader.py +0 -0
  139. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/console.py +0 -0
  140. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/context_detection.py +0 -0
  141. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/enrichment_context.py +0 -0
  142. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/enrichment_parser.py +0 -0
  143. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/feature_keys.py +0 -0
  144. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/git.py +0 -0
  145. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/github_annotations.py +0 -0
  146. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/ide_setup.py +0 -0
  147. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/incremental_check.py +0 -0
  148. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/optional_deps.py +0 -0
  149. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/performance.py +0 -0
  150. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/progress.py +0 -0
  151. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
  152. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/prompts.py +0 -0
  153. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/sdd_discovery.py +0 -0
  154. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/source_scanner.py +0 -0
  155. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/structure.py +0 -0
  156. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/structured_io.py +0 -0
  157. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/suggestions.py +0 -0
  158. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/yaml_utils.py +0 -0
  159. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/validators/__init__.py +0 -0
  160. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/validators/agile_validation.py +0 -0
  161. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/validators/cli_first_validator.py +0 -0
  162. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/validators/contract_validator.py +0 -0
  163. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/validators/fsm.py +0 -0
  164. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/validators/schema.py +0 -0
  165. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/versioning/__init__.py +0 -0
  166. {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/versioning/analyzer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specfact-cli
3
- Version: 0.20.0
3
+ Version: 0.20.1
4
4
  Summary: Brownfield-first CLI: Reverse engineer legacy Python → specs → enforced contracts. Automate legacy code documentation and prevent modernization regressions.
5
5
  Project-URL: Homepage, https://github.com/nold-ai/specfact-cli
6
6
  Project-URL: Repository, https://github.com/nold-ai/specfact-cli.git
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "specfact-cli"
7
- version = "0.20.0"
7
+ version = "0.20.1"
8
8
  description = "Brownfield-first CLI: Reverse engineer legacy Python → specs → enforced contracts. Automate legacy code documentation and prevent modernization regressions."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -3,4 +3,4 @@ SpecFact CLI - Spec→Contract→Sentinel tool for contract-driven development.
3
3
  """
4
4
 
5
5
  # Define the package version (kept in sync with pyproject.toml and setup.py)
6
- __version__ = "0.20.0"
6
+ __version__ = "0.20.1"
@@ -9,6 +9,6 @@ This package provides command-line tools for:
9
9
  - Validating reproducibility
10
10
  """
11
11
 
12
- __version__ = "0.20.0"
12
+ __version__ = "0.20.1"
13
13
 
14
14
  __all__ = ["__version__"]
@@ -19,6 +19,12 @@ from specfact_cli.models.sdd import SDDManifest
19
19
  from specfact_cli.models.task import TaskList, TaskPhase
20
20
  from specfact_cli.telemetry import telemetry
21
21
  from specfact_cli.utils import print_error, print_info, print_success, print_warning
22
+ from specfact_cli.utils.env_manager import (
23
+ build_tool_command,
24
+ detect_env_manager,
25
+ detect_source_directories,
26
+ find_test_files_for_source,
27
+ )
22
28
  from specfact_cli.utils.optional_deps import check_cli_tool_available
23
29
  from specfact_cli.utils.structured_io import load_structured_file
24
30
 
@@ -931,12 +937,24 @@ def apply_enhanced_contracts(
931
937
  parts = enhanced_stem.split("-")
932
938
  if len(parts) >= 2:
933
939
  original_name = parts[1] # Get the original file name
934
- # Try common locations
935
- possible_paths = [
936
- repo_path / f"src/specfact_cli/{original_name}.py",
937
- repo_path / f"src/{original_name}.py",
938
- repo_path / f"{original_name}.py",
939
- ]
940
+ # Detect source directories dynamically
941
+ source_dirs = detect_source_directories(repo_path)
942
+ # Build possible paths based on detected source directories
943
+ possible_paths: list[Path] = []
944
+ # Add root-level file
945
+ possible_paths.append(repo_path / f"{original_name}.py")
946
+ # Add paths based on detected source directories
947
+ for src_dir in source_dirs:
948
+ # Remove trailing slash if present
949
+ src_dir_clean = src_dir.rstrip("/")
950
+ possible_paths.append(repo_path / src_dir_clean / f"{original_name}.py")
951
+ # Also try common patterns as fallback
952
+ possible_paths.extend(
953
+ [
954
+ repo_path / f"src/{original_name}.py",
955
+ repo_path / f"lib/{original_name}.py",
956
+ ]
957
+ )
940
958
  for path in possible_paths:
941
959
  if path.exists():
942
960
  original_file = path
@@ -980,11 +998,16 @@ def apply_enhanced_contracts(
980
998
  console.print("\n[bold cyan]Step 2/6: Validating enhanced code syntax...[/bold cyan]")
981
999
  syntax_errors: list[str] = []
982
1000
  try:
1001
+ # Detect environment manager and build appropriate command
1002
+ env_info = detect_env_manager(repo_path)
1003
+ python_command = ["python", "-m", "py_compile", str(enhanced_file)]
1004
+ compile_command = build_tool_command(env_info, python_command)
983
1005
  result = subprocess.run(
984
- ["python", "-m", "py_compile", str(enhanced_file)],
1006
+ compile_command,
985
1007
  capture_output=True,
986
1008
  text=True,
987
1009
  timeout=10,
1010
+ cwd=str(repo_path),
988
1011
  )
989
1012
  if result.returncode != 0:
990
1013
  error_output = result.stderr.strip()
@@ -1104,6 +1127,9 @@ def apply_enhanced_contracts(
1104
1127
  tools_checked = 0
1105
1128
  tools_passed = 0
1106
1129
 
1130
+ # Detect environment manager for building commands
1131
+ env_info = detect_env_manager(repo_path)
1132
+
1107
1133
  # List of common linting/formatting tools to check
1108
1134
  linting_tools = [
1109
1135
  ("ruff", ["ruff", "check", str(enhanced_file)], "Ruff linting"),
@@ -1122,8 +1148,10 @@ def apply_enhanced_contracts(
1122
1148
  console.print(f"[dim]Running {description}...[/dim]")
1123
1149
 
1124
1150
  try:
1151
+ # Build command with environment manager prefix if needed
1152
+ command_full = build_tool_command(env_info, command)
1125
1153
  result = subprocess.run(
1126
- command,
1154
+ command_full,
1127
1155
  capture_output=True,
1128
1156
  text=True,
1129
1157
  timeout=30, # 30 seconds per tool
@@ -1180,56 +1208,10 @@ def apply_enhanced_contracts(
1180
1208
  # Determine the source file we're testing (original or enhanced)
1181
1209
  source_file_rel = original_file_rel if original_file_rel else enhanced_file_rel
1182
1210
 
1183
- # Convert source file path to potential test file paths
1184
- # Pattern: src/specfact_cli/telemetry.py -> tests/unit/specfact_cli/test_telemetry.py
1185
- # or: src/common/logger.py -> tests/unit/common/test_logger.py
1186
- test_paths: list[Path] = []
1187
-
1188
- # Remove 'src/' prefix if present
1189
- test_rel_path = str(source_file_rel)
1190
- if test_rel_path.startswith("src/"):
1191
- test_rel_path = test_rel_path[4:] # Remove 'src/'
1192
- elif test_rel_path.startswith("tools/"):
1193
- test_rel_path = test_rel_path[6:] # Remove 'tools/'
1194
-
1195
- # Get directory and filename
1196
- test_file_dir = Path(test_rel_path).parent
1197
- test_file_name = Path(test_rel_path).stem # e.g., "telemetry" from "telemetry.py"
1198
-
1199
- # Try common test file patterns
1200
- test_file_patterns = [
1201
- f"test_{test_file_name}.py",
1202
- f"{test_file_name}_test.py",
1203
- ]
1204
-
1205
- # Try common test directory structures
1206
- test_dirs = [
1207
- repo_path / "tests" / "unit" / test_file_dir,
1208
- repo_path / "tests" / test_file_dir,
1209
- repo_path / "tests" / "unit",
1210
- repo_path / "tests",
1211
- ]
1212
-
1213
- # Build list of possible test file paths
1214
- for test_dir in test_dirs:
1215
- if test_dir.exists():
1216
- for pattern in test_file_patterns:
1217
- test_path = test_dir / pattern
1218
- if test_path.exists():
1219
- test_paths.append(test_path)
1220
-
1221
- # Also try E2E tests if unit tests not found
1222
- if not test_paths:
1223
- e2e_test_dirs = [
1224
- repo_path / "tests" / "e2e" / test_file_dir,
1225
- repo_path / "tests" / "e2e",
1226
- ]
1227
- for test_dir in e2e_test_dirs:
1228
- if test_dir.exists():
1229
- for pattern in test_file_patterns:
1230
- test_path = test_dir / pattern
1231
- if test_path.exists():
1232
- test_paths.append(test_path)
1211
+ # Use utility function to find test files dynamically
1212
+ test_paths = find_test_files_for_source(
1213
+ repo_path, source_file_rel if source_file_rel.is_absolute() else repo_path / source_file_rel
1214
+ )
1233
1215
 
1234
1216
  # If we found specific test files, run them
1235
1217
  if test_paths:
@@ -1238,8 +1220,13 @@ def apply_enhanced_contracts(
1238
1220
  console.print(f"[dim]Found test file: {test_path.relative_to(repo_path)}[/dim]")
1239
1221
  console.print("[dim]Running pytest on specific test file (fast, scoped validation)...[/dim]")
1240
1222
 
1223
+ # Detect environment manager and build appropriate command
1224
+ env_info = detect_env_manager(repo_path)
1225
+ pytest_command = ["pytest", str(test_path), "-v", "--tb=short"]
1226
+ pytest_command_full = build_tool_command(env_info, pytest_command)
1227
+
1241
1228
  result = subprocess.run(
1242
- ["pytest", str(test_path), "-v", "--tb=short"],
1229
+ pytest_command_full,
1243
1230
  capture_output=True,
1244
1231
  text=True,
1245
1232
  timeout=60, # 1 minute should be enough for a single test file
@@ -2030,10 +2017,21 @@ def generate_test_prompt(
2030
2017
  console.print("\n[bold cyan]Files that may need tests:[/bold cyan]\n")
2031
2018
 
2032
2019
  # Find Python files without corresponding test files
2020
+ # Use dynamic source directory detection
2021
+ source_dirs = detect_source_directories(repo_path)
2033
2022
  src_files: list[Path] = []
2034
- for src_dir in [repo_path / "src", repo_path]:
2035
- if src_dir.exists():
2036
- src_files.extend(src_dir.rglob("*.py"))
2023
+ # If no source dirs detected, check common patterns
2024
+ if not source_dirs:
2025
+ for src_dir in [repo_path / "src", repo_path / "lib", repo_path]:
2026
+ if src_dir.exists():
2027
+ src_files.extend(src_dir.rglob("*.py"))
2028
+ else:
2029
+ # Use detected source directories
2030
+ for src_dir_str in source_dirs:
2031
+ src_dir_clean = src_dir_str.rstrip("/")
2032
+ src_dir_path = repo_path / src_dir_clean
2033
+ if src_dir_path.exists():
2034
+ src_files.extend(src_dir_path.rglob("*.py"))
2037
2035
 
2038
2036
  files_without_tests: list[tuple[Path, str]] = []
2039
2037
  for src_file in src_files:
@@ -2042,12 +2040,9 @@ def generate_test_prompt(
2042
2040
  if src_file.name.startswith("__"):
2043
2041
  continue
2044
2042
 
2045
- # Check for corresponding test file
2046
- test_patterns = [
2047
- repo_path / "tests" / "unit" / f"test_{src_file.stem}.py",
2048
- repo_path / "tests" / f"test_{src_file.stem}.py",
2049
- ]
2050
- has_test = any(tp.exists() for tp in test_patterns)
2043
+ # Check for corresponding test file using dynamic detection
2044
+ test_files = find_test_files_for_source(repo_path, src_file)
2045
+ has_test = len(test_files) > 0
2051
2046
  if not has_test:
2052
2047
  rel_path = src_file.relative_to(repo_path) if src_file.is_relative_to(repo_path) else src_file
2053
2048
  files_without_tests.append((src_file, str(rel_path)))
@@ -18,6 +18,7 @@ from rich.console import Console
18
18
  from rich.panel import Panel
19
19
 
20
20
  from specfact_cli.telemetry import telemetry
21
+ from specfact_cli.utils.env_manager import EnvManager, build_tool_command, detect_env_manager
21
22
  from specfact_cli.utils.ide_setup import (
22
23
  IDE_CONFIG,
23
24
  copy_templates_to_ide,
@@ -60,7 +61,7 @@ def init(
60
61
  install_deps: bool = typer.Option(
61
62
  False,
62
63
  "--install-deps",
63
- help="Install required packages for contract enhancement (beartype, icontract, crosshair-tool, pytest) via pip",
64
+ help="Install required packages for contract enhancement (beartype, icontract, crosshair-tool, pytest) using detected environment manager",
64
65
  ),
65
66
  # Advanced/Configuration
66
67
  ide: str = typer.Option(
@@ -105,10 +106,41 @@ def init(
105
106
  console.print(f"[cyan]IDE:[/cyan] {ide_name} ({detected_ide})")
106
107
  console.print()
107
108
 
109
+ # Check for environment manager
110
+ env_info = detect_env_manager(repo_path)
111
+ if env_info.manager == EnvManager.UNKNOWN:
112
+ console.print()
113
+ console.print(
114
+ Panel(
115
+ "[bold yellow]⚠ No Compatible Environment Manager Detected[/bold yellow]",
116
+ border_style="yellow",
117
+ )
118
+ )
119
+ console.print(
120
+ "[yellow]SpecFact CLI works best with projects using standard Python project management tools.[/yellow]"
121
+ )
122
+ console.print()
123
+ console.print("[dim]Supported tools:[/dim]")
124
+ console.print(" - hatch (detected from [tool.hatch] in pyproject.toml)")
125
+ console.print(" - poetry (detected from [tool.poetry] in pyproject.toml or poetry.lock)")
126
+ console.print(" - uv (detected from [tool.uv] in pyproject.toml, uv.lock, or uv.toml)")
127
+ console.print(" - pip (detected from requirements.txt or setup.py)")
128
+ console.print()
129
+ console.print(
130
+ "[dim]Note: SpecFact CLI will still work, but commands like 'specfact repro' may use direct tool invocation.[/dim]"
131
+ )
132
+ console.print(
133
+ "[dim]Consider adding a pyproject.toml with [tool.hatch], [tool.poetry], or [tool.uv] for better integration.[/dim]"
134
+ )
135
+ console.print()
136
+
108
137
  # Install dependencies if requested
109
138
  if install_deps:
110
139
  console.print()
111
140
  console.print(Panel("[bold cyan]Installing Required Packages[/bold cyan]", border_style="cyan"))
141
+ if env_info.message:
142
+ console.print(f"[dim]{env_info.message}[/dim]")
143
+
112
144
  required_packages = [
113
145
  "beartype>=0.22.4",
114
146
  "icontract>=2.7.1",
@@ -119,19 +151,32 @@ def init(
119
151
  for package in required_packages:
120
152
  console.print(f" - {package}")
121
153
 
154
+ # Build install command using environment manager detection
155
+ install_cmd = ["pip", "install", "-U", *required_packages]
156
+ install_cmd = build_tool_command(env_info, install_cmd)
157
+
158
+ console.print(f"[dim]Using command: {' '.join(install_cmd)}[/dim]")
159
+
122
160
  try:
123
- # Use pip to install packages
124
161
  result = subprocess.run(
125
- [sys.executable, "-m", "pip", "install", "-U", *required_packages],
162
+ install_cmd,
126
163
  capture_output=True,
127
164
  text=True,
128
165
  check=False,
166
+ cwd=str(repo_path),
167
+ timeout=300, # 5 minute timeout
129
168
  )
130
169
 
131
170
  if result.returncode == 0:
132
171
  console.print()
133
172
  console.print("[green]✓[/green] All required packages installed successfully")
134
- record({"deps_installed": True, "packages_count": len(required_packages)})
173
+ record(
174
+ {
175
+ "deps_installed": True,
176
+ "packages_count": len(required_packages),
177
+ "env_manager": env_info.manager.value,
178
+ }
179
+ )
135
180
  else:
136
181
  console.print()
137
182
  console.print("[yellow]⚠[/yellow] Some packages failed to install")
@@ -142,19 +187,60 @@ def init(
142
187
  console.print(result.stderr)
143
188
  console.print()
144
189
  console.print("[yellow]You may need to install packages manually:[/yellow]")
190
+ # Provide environment-specific guidance
191
+ if env_info.manager == EnvManager.HATCH:
192
+ console.print(f" hatch run pip install {' '.join(required_packages)}")
193
+ elif env_info.manager == EnvManager.POETRY:
194
+ console.print(f" poetry add --dev {' '.join(required_packages)}")
195
+ elif env_info.manager == EnvManager.UV:
196
+ console.print(f" uv pip install {' '.join(required_packages)}")
197
+ else:
198
+ console.print(f" pip install {' '.join(required_packages)}")
199
+ record(
200
+ {
201
+ "deps_installed": False,
202
+ "error": result.stderr[:200] if result.stderr else "Unknown error",
203
+ "env_manager": env_info.manager.value,
204
+ }
205
+ )
206
+ except subprocess.TimeoutExpired:
207
+ console.print()
208
+ console.print("[red]Error:[/red] Installation timed out after 5 minutes")
209
+ console.print("[yellow]You may need to install packages manually:[/yellow]")
210
+ if env_info.manager == EnvManager.HATCH:
211
+ console.print(f" hatch run pip install {' '.join(required_packages)}")
212
+ elif env_info.manager == EnvManager.POETRY:
213
+ console.print(f" poetry add --dev {' '.join(required_packages)}")
214
+ elif env_info.manager == EnvManager.UV:
215
+ console.print(f" uv pip install {' '.join(required_packages)}")
216
+ else:
145
217
  console.print(f" pip install {' '.join(required_packages)}")
146
- record({"deps_installed": False, "error": result.stderr[:200]})
218
+ record({"deps_installed": False, "error": "timeout", "env_manager": env_info.manager.value})
147
219
  except FileNotFoundError:
148
220
  console.print()
149
221
  console.print("[red]Error:[/red] pip not found. Please install packages manually:")
150
- console.print(f" pip install {' '.join(required_packages)}")
151
- record({"deps_installed": False, "error": "pip not found"})
222
+ if env_info.manager == EnvManager.HATCH:
223
+ console.print(f" hatch run pip install {' '.join(required_packages)}")
224
+ elif env_info.manager == EnvManager.POETRY:
225
+ console.print(f" poetry add --dev {' '.join(required_packages)}")
226
+ elif env_info.manager == EnvManager.UV:
227
+ console.print(f" uv pip install {' '.join(required_packages)}")
228
+ else:
229
+ console.print(f" pip install {' '.join(required_packages)}")
230
+ record({"deps_installed": False, "error": "pip not found", "env_manager": env_info.manager.value})
152
231
  except Exception as e:
153
232
  console.print()
154
233
  console.print(f"[red]Error:[/red] Failed to install packages: {e}")
155
234
  console.print("[yellow]You may need to install packages manually:[/yellow]")
156
- console.print(f" pip install {' '.join(required_packages)}")
157
- record({"deps_installed": False, "error": str(e)})
235
+ if env_info.manager == EnvManager.HATCH:
236
+ console.print(f" hatch run pip install {' '.join(required_packages)}")
237
+ elif env_info.manager == EnvManager.POETRY:
238
+ console.print(f" poetry add --dev {' '.join(required_packages)}")
239
+ elif env_info.manager == EnvManager.UV:
240
+ console.print(f" uv pip install {' '.join(required_packages)}")
241
+ else:
242
+ console.print(f" pip install {' '.join(required_packages)}")
243
+ record({"deps_installed": False, "error": str(e), "env_manager": env_info.manager.value})
158
244
  console.print()
159
245
 
160
246
  # Find templates directory