specfact-cli 0.23.1__tar.gz → 0.24.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 (210) hide show
  1. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/PKG-INFO +15 -2
  2. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/README.md +14 -1
  3. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/pyproject.toml +1 -1
  4. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/run_sidecar.sh +124 -35
  5. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/__init__.py +1 -1
  6. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/__init__.py +1 -1
  7. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/cli.py +2 -0
  8. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/__init__.py +2 -0
  9. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/init.py +3 -0
  10. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/repro.py +45 -0
  11. specfact_cli-0.24.0/src/specfact_cli/commands/validate.py +158 -0
  12. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/__init__.py +11 -0
  13. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/contract_populator.py +145 -0
  14. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/crosshair_runner.py +98 -0
  15. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/crosshair_summary.py +164 -0
  16. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/framework_detector.py +184 -0
  17. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/frameworks/__init__.py +12 -0
  18. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/frameworks/base.py +84 -0
  19. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/frameworks/django.py +322 -0
  20. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/frameworks/drf.py +95 -0
  21. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +225 -0
  22. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/harness_generator.py +148 -0
  23. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/models.py +184 -0
  24. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/orchestrator.py +357 -0
  25. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/specmatic_runner.py +106 -0
  26. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/unannotated_detector.py +140 -0
  27. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/.gitignore +0 -0
  28. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/LICENSE.md +0 -0
  29. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/mappings/node-async.yaml +0 -0
  30. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/mappings/python-async.yaml +0 -0
  31. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/mappings/speckit-default.yaml +0 -0
  32. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/shared/cli-enforcement.md +0 -0
  33. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.01-import.md +0 -0
  34. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.02-plan.md +0 -0
  35. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.03-review.md +0 -0
  36. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.04-sdd.md +0 -0
  37. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.05-enforce.md +0 -0
  38. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.06-sync.md +0 -0
  39. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.07-contracts.md +0 -0
  40. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.compare.md +0 -0
  41. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.sync-backlog.md +0 -0
  42. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.validate.md +0 -0
  43. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/schemas/deviation.schema.json +0 -0
  44. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/schemas/plan.schema.json +0 -0
  45. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/schemas/protocol.schema.json +0 -0
  46. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/github-action.yml.j2 +0 -0
  47. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/persona/architect.md.j2 +0 -0
  48. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/persona/developer.md.j2 +0 -0
  49. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/persona/product-owner.md.j2 +0 -0
  50. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/plan.bundle.yaml.j2 +0 -0
  51. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/pr-template.md.j2 +0 -0
  52. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/protocol.yaml.j2 +0 -0
  53. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/STRUCTURE.md +0 -0
  54. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/__init__.py +0 -0
  55. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/README.md +0 -0
  56. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/adapters.py +0 -0
  57. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/bindings.yaml.example +0 -0
  58. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/crosshair_plugin.py +0 -0
  59. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/generate_harness.py +0 -0
  60. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/harness_contracts.py.example +0 -0
  61. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/populate_contracts.py +0 -0
  62. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/sidecar-init.sh +0 -0
  63. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/frameworks/django/__init__.py +0 -0
  64. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/frameworks/django/crosshair_django_wrapper.py +0 -0
  65. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/frameworks/django/django_form_extractor.py +0 -0
  66. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/frameworks/django/django_url_extractor.py +0 -0
  67. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/frameworks/drf/__init__.py +0 -0
  68. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/frameworks/drf/drf_serializer_extractor.py +0 -0
  69. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/frameworks/fastapi/__init__.py +0 -0
  70. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/frameworks/fastapi/fastapi_route_extractor.py +0 -0
  71. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/telemetry.yaml.example +0 -0
  72. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/adapters/__init__.py +0 -0
  73. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/adapters/base.py +0 -0
  74. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/adapters/github.py +0 -0
  75. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/adapters/openspec.py +0 -0
  76. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/adapters/openspec_parser.py +0 -0
  77. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/adapters/registry.py +0 -0
  78. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/adapters/speckit.py +0 -0
  79. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/agents/__init__.py +0 -0
  80. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/agents/analyze_agent.py +0 -0
  81. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/agents/base.py +0 -0
  82. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/agents/plan_agent.py +0 -0
  83. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/agents/registry.py +0 -0
  84. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/agents/sync_agent.py +0 -0
  85. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/__init__.py +0 -0
  86. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
  87. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
  88. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
  89. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
  90. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
  91. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
  92. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
  93. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
  94. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
  95. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/analyze.py +0 -0
  96. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/contract_cmd.py +0 -0
  97. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/drift.py +0 -0
  98. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/enforce.py +0 -0
  99. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/generate.py +0 -0
  100. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/import_cmd.py +0 -0
  101. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/migrate.py +0 -0
  102. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/plan.py +0 -0
  103. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/project_cmd.py +0 -0
  104. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/sdd.py +0 -0
  105. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/spec.py +0 -0
  106. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/sync.py +0 -0
  107. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/common/__init__.py +0 -0
  108. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/common/logger_setup.py +0 -0
  109. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/common/logging_utils.py +0 -0
  110. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/common/text_utils.py +0 -0
  111. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/common/utils.py +0 -0
  112. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/comparators/__init__.py +0 -0
  113. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/comparators/plan_comparator.py +0 -0
  114. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
  115. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
  116. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/__init__.py +0 -0
  117. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/contract_generator.py +0 -0
  118. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/openapi_extractor.py +0 -0
  119. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/persona_exporter.py +0 -0
  120. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/plan_generator.py +0 -0
  121. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/protocol_generator.py +0 -0
  122. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/report_generator.py +0 -0
  123. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/task_generator.py +0 -0
  124. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/test_to_openapi.py +0 -0
  125. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/workflow_generator.py +0 -0
  126. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/importers/__init__.py +0 -0
  127. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/importers/speckit_converter.py +0 -0
  128. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/importers/speckit_scanner.py +0 -0
  129. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/integrations/__init__.py +0 -0
  130. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/integrations/specmatic.py +0 -0
  131. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/merge/__init__.py +0 -0
  132. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/merge/resolver.py +0 -0
  133. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/migrations/__init__.py +0 -0
  134. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/migrations/plan_migrator.py +0 -0
  135. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/__init__.py +0 -0
  136. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/bridge.py +0 -0
  137. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/capabilities.py +0 -0
  138. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/change.py +0 -0
  139. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/contract.py +0 -0
  140. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/deviation.py +0 -0
  141. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/enforcement.py +0 -0
  142. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/persona_template.py +0 -0
  143. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/plan.py +0 -0
  144. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/project.py +0 -0
  145. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/protocol.py +0 -0
  146. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/quality.py +0 -0
  147. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/sdd.py +0 -0
  148. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/source_tracking.py +0 -0
  149. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/task.py +0 -0
  150. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/modes/__init__.py +0 -0
  151. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/modes/detector.py +0 -0
  152. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/modes/router.py +0 -0
  153. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/parsers/__init__.py +0 -0
  154. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/parsers/persona_importer.py +0 -0
  155. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/resources/semgrep/async.yml +0 -0
  156. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
  157. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
  158. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/runtime.py +0 -0
  159. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/__init__.py +0 -0
  160. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/bridge_probe.py +0 -0
  161. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/bridge_sync.py +0 -0
  162. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/bridge_watch.py +0 -0
  163. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/change_detector.py +0 -0
  164. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/code_to_spec.py +0 -0
  165. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/drift_detector.py +0 -0
  166. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/repository_sync.py +0 -0
  167. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/spec_to_code.py +0 -0
  168. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/spec_to_tests.py +0 -0
  169. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/watcher.py +0 -0
  170. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
  171. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/telemetry.py +0 -0
  172. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/templates/__init__.py +0 -0
  173. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/templates/bridge_templates.py +0 -0
  174. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/templates/specification_templates.py +0 -0
  175. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/__init__.py +0 -0
  176. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
  177. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/bundle_loader.py +0 -0
  178. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/code_change_detector.py +0 -0
  179. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/console.py +0 -0
  180. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/content_sanitizer.py +0 -0
  181. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/context_detection.py +0 -0
  182. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/enrichment_context.py +0 -0
  183. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/enrichment_parser.py +0 -0
  184. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/env_manager.py +0 -0
  185. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/feature_keys.py +0 -0
  186. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/git.py +0 -0
  187. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/github_annotations.py +0 -0
  188. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/ide_setup.py +0 -0
  189. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/incremental_check.py +0 -0
  190. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/optional_deps.py +0 -0
  191. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/performance.py +0 -0
  192. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/progress.py +0 -0
  193. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
  194. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/prompts.py +0 -0
  195. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/sdd_discovery.py +0 -0
  196. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/source_scanner.py +0 -0
  197. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/structure.py +0 -0
  198. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/structured_io.py +0 -0
  199. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/suggestions.py +0 -0
  200. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/terminal.py +0 -0
  201. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/yaml_utils.py +0 -0
  202. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/validators/__init__.py +0 -0
  203. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/validators/agile_validation.py +0 -0
  204. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/validators/cli_first_validator.py +0 -0
  205. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/validators/contract_validator.py +0 -0
  206. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/validators/fsm.py +0 -0
  207. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/validators/repro_checker.py +0 -0
  208. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/validators/schema.py +0 -0
  209. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/versioning/__init__.py +0 -0
  210. {specfact_cli-0.23.1 → specfact_cli-0.24.0}/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.23.1
3
+ Version: 0.24.0
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
@@ -368,6 +368,10 @@ specfact init --ide cursor --install-deps
368
368
  ```bash
369
369
  # Analyze legacy codebase (most common use case)
370
370
  specfact import from-code my-project --repo .
371
+
372
+ # Or validate external codebase without modifying source (sidecar validation)
373
+ specfact validate sidecar init my-project /path/to/repo
374
+ specfact validate sidecar run my-project /path/to/repo
371
375
  ```
372
376
 
373
377
  **⏱️ Timing:** Analysis typically takes **10-15 minutes** for typical repositories (e.g., `specfact-cli` itself with several hundred features & contracts). Smaller codebases may complete in 2-5 minutes. Large codebases (3000+ features) may take 15-30 minutes, but progress reporting shows real-time status. The analysis performs AST parsing, Semgrep pattern detection, and Specmatic integration.
@@ -432,8 +436,10 @@ specfact import from-code my-project --repo .
432
436
  - **Prevent regressions** with runtime contract validation
433
437
  - **CI/CD integration** - Block bad code from merging
434
438
  - **Works offline** - No cloud required
439
+ - **Sidecar validation** - Validate external codebases without modifying source code
435
440
 
436
- 👉 **[Command Reference](docs/reference/commands.md)** - All enforcement commands
441
+ 👉 **[Command Reference](docs/reference/commands.md)** - All enforcement commands
442
+ 👉 **[Sidecar Validation Guide](docs/guides/sidecar-validation.md)** - Validate external codebases
437
443
 
438
444
  ### 👥 Team Collaboration
439
445
 
@@ -463,6 +469,12 @@ specfact import from-code my-project --repo .
463
469
 
464
470
  👉 **[Brownfield Modernization Guide](docs/guides/brownfield-engineer.md)** - Complete walkthrough
465
471
 
472
+ ### 1.5. Validating External Codebases (Sidecar Validation) 🆕
473
+
474
+ **Problem:** Need to validate third-party libraries or legacy codebases without modifying source code
475
+
476
+ 👉 **[Sidecar Validation Guide](docs/guides/sidecar-validation.md)** - Validate external codebases with contract testing
477
+
466
478
  ### 2. Working with a Team
467
479
 
468
480
  **Problem:** Need team collaboration with role-based workflows
@@ -502,6 +514,7 @@ specfact import from-code my-project --repo .
502
514
  - **[AI IDE Workflow](docs/guides/ai-ide-workflow.md)** ⭐ **NEW** - AI-assisted development
503
515
  - **[Agile/Scrum Workflows](docs/guides/agile-scrum-workflows.md)** ⭐ - Team collaboration
504
516
  - **[Integrations Overview](docs/guides/integrations-overview.md)** ⭐ **NEW** - All integrations
517
+ - **[Sidecar Validation](docs/guides/sidecar-validation.md)** 🆕 - Validate external codebases without modifying source
505
518
  - **[Use Cases](docs/guides/use-cases.md)** - Common scenarios
506
519
 
507
520
  ### Integration Guides
@@ -93,6 +93,10 @@ specfact init --ide cursor --install-deps
93
93
  ```bash
94
94
  # Analyze legacy codebase (most common use case)
95
95
  specfact import from-code my-project --repo .
96
+
97
+ # Or validate external codebase without modifying source (sidecar validation)
98
+ specfact validate sidecar init my-project /path/to/repo
99
+ specfact validate sidecar run my-project /path/to/repo
96
100
  ```
97
101
 
98
102
  **⏱️ Timing:** Analysis typically takes **10-15 minutes** for typical repositories (e.g., `specfact-cli` itself with several hundred features & contracts). Smaller codebases may complete in 2-5 minutes. Large codebases (3000+ features) may take 15-30 minutes, but progress reporting shows real-time status. The analysis performs AST parsing, Semgrep pattern detection, and Specmatic integration.
@@ -157,8 +161,10 @@ specfact import from-code my-project --repo .
157
161
  - **Prevent regressions** with runtime contract validation
158
162
  - **CI/CD integration** - Block bad code from merging
159
163
  - **Works offline** - No cloud required
164
+ - **Sidecar validation** - Validate external codebases without modifying source code
160
165
 
161
- 👉 **[Command Reference](docs/reference/commands.md)** - All enforcement commands
166
+ 👉 **[Command Reference](docs/reference/commands.md)** - All enforcement commands
167
+ 👉 **[Sidecar Validation Guide](docs/guides/sidecar-validation.md)** - Validate external codebases
162
168
 
163
169
  ### 👥 Team Collaboration
164
170
 
@@ -188,6 +194,12 @@ specfact import from-code my-project --repo .
188
194
 
189
195
  👉 **[Brownfield Modernization Guide](docs/guides/brownfield-engineer.md)** - Complete walkthrough
190
196
 
197
+ ### 1.5. Validating External Codebases (Sidecar Validation) 🆕
198
+
199
+ **Problem:** Need to validate third-party libraries or legacy codebases without modifying source code
200
+
201
+ 👉 **[Sidecar Validation Guide](docs/guides/sidecar-validation.md)** - Validate external codebases with contract testing
202
+
191
203
  ### 2. Working with a Team
192
204
 
193
205
  **Problem:** Need team collaboration with role-based workflows
@@ -227,6 +239,7 @@ specfact import from-code my-project --repo .
227
239
  - **[AI IDE Workflow](docs/guides/ai-ide-workflow.md)** ⭐ **NEW** - AI-assisted development
228
240
  - **[Agile/Scrum Workflows](docs/guides/agile-scrum-workflows.md)** ⭐ - Team collaboration
229
241
  - **[Integrations Overview](docs/guides/integrations-overview.md)** ⭐ **NEW** - All integrations
242
+ - **[Sidecar Validation](docs/guides/sidecar-validation.md)** 🆕 - Validate external codebases without modifying source
230
243
  - **[Use Cases](docs/guides/use-cases.md)** - Common scenarios
231
244
 
232
245
  ### Integration Guides
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "specfact-cli"
7
- version = "0.23.1"
7
+ version = "0.24.0"
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"
@@ -148,6 +148,54 @@ _filter_crosshair_dirs() {
148
148
  echo "${filtered[@]}"
149
149
  }
150
150
 
151
+ # Convert source directory paths to Python module names for CrossHair
152
+ # CrossHair expects module names (e.g., "sqlalchemy") not paths (e.g., "lib/sqlalchemy")
153
+ # This function extracts the module name and ensures PYTHONPATH includes the parent directory
154
+ _path_to_module() {
155
+ local source_path="$1"
156
+ local repo_path="${2:-${REPO_PATH}}"
157
+
158
+ # Remove trailing slash
159
+ source_path="${source_path%/}"
160
+
161
+ # If it's an absolute path, make it relative to repo
162
+ if [[ "$source_path" == /* ]]; then
163
+ source_path="${source_path#${repo_path}/}"
164
+ fi
165
+
166
+ # Handle common patterns: lib/pkg, src/pkg, backend/app, pkg
167
+ # Extract the module name (last component that's a valid Python package)
168
+ local module_name=""
169
+ local parent_dir=""
170
+
171
+ if [[ "$source_path" == *"/"* ]]; then
172
+ # Path has directory structure (e.g., lib/sqlalchemy, src/mypackage)
173
+ parent_dir="${source_path%/*}" # Everything before last /
174
+ module_name="${source_path##*/}" # Last component
175
+
176
+ # Check if the module directory has __init__.py (is a package)
177
+ local full_module_path="${repo_path}/${source_path}"
178
+ if [[ -f "${full_module_path}/__init__.py" ]]; then
179
+ # It's a package - return module name and parent dir
180
+ echo "${module_name}|${repo_path}/${parent_dir}"
181
+ return 0
182
+ fi
183
+
184
+ # Check subdirectories for packages (e.g., lib/sqlalchemy where sqlalchemy is the package)
185
+ for subdir in "${full_module_path}"/*; do
186
+ if [[ -d "$subdir" ]] && [[ -f "${subdir}/__init__.py" ]]; then
187
+ # Found a package subdirectory
188
+ echo "$(basename "$subdir")|${full_module_path}"
189
+ return 0
190
+ fi
191
+ done
192
+ fi
193
+
194
+ # No directory structure or no package found - use the path as-is
195
+ # This handles cases like "mypackage" where PYTHONPATH is already set correctly
196
+ echo "${source_path}|"
197
+ }
198
+
151
199
  run_with_timeout() {
152
200
  local timeout_secs="$1"
153
201
  shift
@@ -427,36 +475,68 @@ if [[ "${RUN_CROSSHAIR}" == "1" ]] && command -v crosshair >/dev/null 2>&1; then
427
475
  if [[ -z "${CROSSHAIR_FILTERED_DIRS}" ]]; then
428
476
  echo "[sidecar] warning: all source directories filtered out (contain tests), skipping source code analysis"
429
477
  else
430
- if [[ "${FRAMEWORK_TYPE}" == "django" ]]; then
431
- # Use Django-aware wrapper for source code analysis
432
- CROSSHAIR_WRAPPER="${SIDECAR_DIR}/../frameworks/django/crosshair_django_wrapper.py"
433
- if [[ -f "${CROSSHAIR_WRAPPER}" ]]; then
434
- echo "[sidecar] using Django-aware CrossHair wrapper for source analysis"
435
- # Export environment variables for Django initialization
436
- CROSSHAIR_ENV=""
437
- if [[ -n "${DJANGO_SETTINGS_MODULE:-}" ]]; then
438
- CROSSHAIR_ENV="DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE} "
478
+ # Convert source paths to module names for CrossHair
479
+ # CrossHair expects module names (e.g., "sqlalchemy") not paths (e.g., "lib/sqlalchemy")
480
+ CROSSHAIR_MODULES=""
481
+ CROSSHAIR_EXTRA_PYTHONPATH=""
482
+ for src_dir in ${CROSSHAIR_FILTERED_DIRS}; do
483
+ MODULE_INFO=$(_path_to_module "$src_dir" "${REPO_PATH}")
484
+ MODULE_NAME="${MODULE_INFO%%|*}"
485
+ MODULE_PARENT="${MODULE_INFO##*|}"
486
+
487
+ if [[ -n "$MODULE_NAME" ]]; then
488
+ CROSSHAIR_MODULES="${CROSSHAIR_MODULES} ${MODULE_NAME}"
489
+ if [[ -n "$MODULE_PARENT" ]] && [[ ":${CROSSHAIR_EXTRA_PYTHONPATH}:" != *":${MODULE_PARENT}:"* ]]; then
490
+ CROSSHAIR_EXTRA_PYTHONPATH="${CROSSHAIR_EXTRA_PYTHONPATH}:${MODULE_PARENT}"
491
+ fi
439
492
  fi
440
- if [[ -n "${REPO_PATH:-}" ]]; then
441
- CROSSHAIR_ENV="${CROSSHAIR_ENV}REPO_PATH=${REPO_PATH} "
493
+ done
494
+ CROSSHAIR_MODULES="${CROSSHAIR_MODULES# }" # Trim leading space
495
+ CROSSHAIR_EXTRA_PYTHONPATH="${CROSSHAIR_EXTRA_PYTHONPATH#:}" # Trim leading colon
496
+
497
+ if [[ -z "${CROSSHAIR_MODULES}" ]]; then
498
+ echo "[sidecar] warning: could not convert source directories to modules, skipping source code analysis"
499
+ else
500
+ echo "[sidecar] analyzing modules: ${CROSSHAIR_MODULES}"
501
+ if [[ -n "${CROSSHAIR_EXTRA_PYTHONPATH}" ]]; then
502
+ echo "[sidecar] extra PYTHONPATH: ${CROSSHAIR_EXTRA_PYTHONPATH}"
442
503
  fi
443
- if [[ -n "${PYTHONPATH:-}" ]]; then
444
- CROSSHAIR_ENV="${CROSSHAIR_ENV}PYTHONPATH=${PYTHONPATH} "
504
+
505
+ # Build PYTHONPATH for CrossHair (include extra paths for module resolution)
506
+ CROSSHAIR_PYTHONPATH="${PYTHONPATH:-}"
507
+ if [[ -n "${CROSSHAIR_EXTRA_PYTHONPATH}" ]]; then
508
+ CROSSHAIR_PYTHONPATH="${CROSSHAIR_EXTRA_PYTHONPATH}:${CROSSHAIR_PYTHONPATH}"
509
+ fi
510
+
511
+ if [[ "${FRAMEWORK_TYPE}" == "django" ]]; then
512
+ # Use Django-aware wrapper for source code analysis
513
+ CROSSHAIR_WRAPPER="${SIDECAR_DIR}/../frameworks/django/crosshair_django_wrapper.py"
514
+ if [[ -f "${CROSSHAIR_WRAPPER}" ]]; then
515
+ echo "[sidecar] using Django-aware CrossHair wrapper for source analysis"
516
+ # Export environment variables for Django initialization
517
+ CROSSHAIR_ENV=""
518
+ if [[ -n "${DJANGO_SETTINGS_MODULE:-}" ]]; then
519
+ CROSSHAIR_ENV="DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE} "
520
+ fi
521
+ if [[ -n "${REPO_PATH:-}" ]]; then
522
+ CROSSHAIR_ENV="${CROSSHAIR_ENV}REPO_PATH=${REPO_PATH} "
523
+ fi
524
+ CROSSHAIR_ENV="${CROSSHAIR_ENV}PYTHONPATH=${CROSSHAIR_PYTHONPATH} "
525
+ run_and_log "${TIMEOUT_CROSSHAIR}" \
526
+ "${SIDECAR_REPORTS_DIR}/${TIMESTAMP}-crosshair-source.log" \
527
+ env ${CROSSHAIR_ENV}"${PYTHON_CMD}" "${CROSSHAIR_WRAPPER}" check "${CROSSHAIR_ARGS[@]}" ${CROSSHAIR_MODULES}
528
+ else
529
+ echo "[sidecar] warning: Django wrapper not found, using standard CrossHair (may fail)"
530
+ run_and_log "${TIMEOUT_CROSSHAIR}" \
531
+ "${SIDECAR_REPORTS_DIR}/${TIMESTAMP}-crosshair-source.log" \
532
+ env PYTHONPATH="${CROSSHAIR_PYTHONPATH}" "${PYTHON_CMD}" -m crosshair check "${CROSSHAIR_ARGS[@]}" ${CROSSHAIR_MODULES}
533
+ fi
534
+ else
535
+ # Standard CrossHair for non-Django projects
536
+ run_and_log "${TIMEOUT_CROSSHAIR}" \
537
+ "${SIDECAR_REPORTS_DIR}/${TIMESTAMP}-crosshair-source.log" \
538
+ env PYTHONPATH="${CROSSHAIR_PYTHONPATH}" "${PYTHON_CMD}" -m crosshair check "${CROSSHAIR_ARGS[@]}" ${CROSSHAIR_MODULES}
445
539
  fi
446
- run_and_log "${TIMEOUT_CROSSHAIR}" \
447
- "${SIDECAR_REPORTS_DIR}/${TIMESTAMP}-crosshair-source.log" \
448
- env ${CROSSHAIR_ENV}"${PYTHON_CMD}" "${CROSSHAIR_WRAPPER}" check "${CROSSHAIR_ARGS[@]}" ${CROSSHAIR_FILTERED_DIRS}
449
- else
450
- echo "[sidecar] warning: Django wrapper not found, using standard CrossHair (may fail)"
451
- run_and_log "${TIMEOUT_CROSSHAIR}" \
452
- "${SIDECAR_REPORTS_DIR}/${TIMESTAMP}-crosshair-source.log" \
453
- "${PYTHON_CMD}" -m crosshair check "${CROSSHAIR_ARGS[@]}" ${CROSSHAIR_FILTERED_DIRS}
454
- fi
455
- else
456
- # Standard CrossHair for non-Django projects
457
- run_and_log "${TIMEOUT_CROSSHAIR}" \
458
- "${SIDECAR_REPORTS_DIR}/${TIMESTAMP}-crosshair-source.log" \
459
- "${PYTHON_CMD}" -m crosshair check "${CROSSHAIR_ARGS[@]}" ${CROSSHAIR_FILTERED_DIRS}
460
540
  fi
461
541
  fi
462
542
  fi
@@ -466,21 +546,30 @@ if [[ "${RUN_CROSSHAIR}" == "1" ]] && command -v crosshair >/dev/null 2>&1; then
466
546
  # This is the primary analysis method for frameworks without decorators (Django, etc.)
467
547
  if [[ -f "${HARNESS_PATH}" ]]; then
468
548
  echo "[sidecar] crosshair (harness - external contracts)..."
549
+
550
+ # Build PYTHONPATH for harness analysis:
551
+ # 1. Sidecar directory (for harness imports like 'common.adapters')
552
+ # 2. Original PYTHONPATH (for repo modules)
553
+ HARNESS_DIR="$(cd "$(dirname "${HARNESS_PATH}")" && pwd)"
554
+ HARNESS_FILE="$(basename "${HARNESS_PATH}")"
555
+ HARNESS_MODULE="${HARNESS_FILE%.py}" # Remove .py extension
556
+
557
+ # Build PYTHONPATH: sidecar dir + original PYTHONPATH
558
+ HARNESS_PYTHONPATH="${HARNESS_DIR}"
559
+ if [[ -n "${PYTHONPATH:-}" ]]; then
560
+ HARNESS_PYTHONPATH="${HARNESS_PYTHONPATH}:${PYTHONPATH}"
561
+ fi
562
+
469
563
  # Export environment variables for CrossHair subprocess
470
- CROSSHAIR_ENV=""
564
+ CROSSHAIR_ENV="PYTHONPATH=${HARNESS_PYTHONPATH} "
471
565
  if [[ -n "${DJANGO_SETTINGS_MODULE:-}" ]]; then
472
- CROSSHAIR_ENV="DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE} "
566
+ CROSSHAIR_ENV="${CROSSHAIR_ENV}DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE} "
473
567
  fi
474
568
  if [[ -n "${REPO_PATH:-}" ]]; then
475
569
  CROSSHAIR_ENV="${CROSSHAIR_ENV}REPO_PATH=${REPO_PATH} "
476
570
  fi
477
- if [[ -n "${PYTHONPATH:-}" ]]; then
478
- CROSSHAIR_ENV="${CROSSHAIR_ENV}PYTHONPATH=${PYTHONPATH} "
479
- fi
571
+
480
572
  # Change to harness directory to ensure valid module name (avoids hyphenated directory names in module path)
481
- HARNESS_DIR="$(dirname "${HARNESS_PATH}")"
482
- HARNESS_FILE="$(basename "${HARNESS_PATH}")"
483
- HARNESS_MODULE="${HARNESS_FILE%.py}" # Remove .py extension
484
573
  run_and_log "${TIMEOUT_CROSSHAIR}" \
485
574
  "${SIDECAR_REPORTS_DIR}/${TIMESTAMP}-crosshair-harness.log" \
486
575
  bash -c "cd '${HARNESS_DIR}' && env ${CROSSHAIR_ENV}${PYTHON_CMD} -m crosshair check ${CROSSHAIR_ARGS[*]} ${HARNESS_MODULE}"
@@ -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.23.1"
6
+ __version__ = "0.24.0"
@@ -9,6 +9,6 @@ This package provides command-line tools for:
9
9
  - Validating reproducibility
10
10
  """
11
11
 
12
- __version__ = "0.23.1"
12
+ __version__ = "0.24.0"
13
13
 
14
14
  __all__ = ["__version__"]
@@ -68,6 +68,7 @@ from specfact_cli.commands import (
68
68
  sdd,
69
69
  spec,
70
70
  sync,
71
+ validate,
71
72
  )
72
73
  from specfact_cli.modes import OperationalMode, detect_mode
73
74
  from specfact_cli.runtime import get_configured_console
@@ -350,6 +351,7 @@ app.add_typer(drift.app, name="drift", help="Detect drift between code and speci
350
351
 
351
352
  # 11.6. Analysis
352
353
  app.add_typer(analyze.app, name="analyze", help="Analyze codebase for contract coverage and quality")
354
+ app.add_typer(validate.app, name="validate", help="Validation commands including sidecar validation")
353
355
 
354
356
 
355
357
  def cli_main() -> None:
@@ -19,6 +19,7 @@ from specfact_cli.commands import (
19
19
  sdd,
20
20
  spec,
21
21
  sync,
22
+ validate,
22
23
  )
23
24
 
24
25
 
@@ -38,4 +39,5 @@ __all__ = [
38
39
  "sdd",
39
40
  "spec",
40
41
  "sync",
42
+ "validate",
41
43
  ]
@@ -146,6 +146,9 @@ def init(
146
146
  "icontract>=2.7.1",
147
147
  "crosshair-tool>=0.0.97",
148
148
  "pytest>=8.4.2",
149
+ # Sidecar validation tools
150
+ # Note: specmatic may need separate installation (Java-based tool)
151
+ # Users may need to install specmatic separately: https://specmatic.in/documentation/getting_started.html
149
152
  ]
150
153
  console.print("[dim]Installing packages for contract enhancement:[/dim]")
151
154
  for package in required_packages:
@@ -167,6 +167,16 @@ def main(
167
167
  help="Time budget in seconds (must be > 0)",
168
168
  hidden=True, # Hidden by default, shown with --help-advanced
169
169
  ),
170
+ sidecar: bool = typer.Option(
171
+ False,
172
+ "--sidecar",
173
+ help="Run sidecar validation for unannotated code (no-edit path)",
174
+ ),
175
+ sidecar_bundle: str | None = typer.Option(
176
+ None,
177
+ "--sidecar-bundle",
178
+ help="Bundle name for sidecar validation (required if --sidecar is used)",
179
+ ),
170
180
  ) -> None:
171
181
  """
172
182
  Run full validation suite for reproducibility.
@@ -182,6 +192,7 @@ def main(
182
192
  - Contract exploration (CrossHair) - optional
183
193
  - Property tests (pytest tests/contracts/) - optional, only if directory exists
184
194
  - Smoke tests (pytest tests/smoke/) - optional, only if directory exists
195
+ - Sidecar validation (--sidecar) - optional, for unannotated code validation
185
196
 
186
197
  Works on external repositories without requiring SpecFact CLI adoption.
187
198
 
@@ -189,6 +200,7 @@ def main(
189
200
  specfact repro --verbose --budget 120
190
201
  specfact repro --repo /path/to/external/repo --verbose
191
202
  specfact repro --fix --budget 120
203
+ specfact repro --sidecar --sidecar-bundle legacy-api --repo /path/to/repo
192
204
  """
193
205
  # If a subcommand was invoked, don't run the main validation
194
206
  if ctx.invoked_subcommand is not None:
@@ -201,6 +213,8 @@ def main(
201
213
  raise typer.BadParameter("Budget must be positive")
202
214
  if not _is_valid_output_path(out):
203
215
  raise typer.BadParameter("Output path must exist if provided")
216
+ if sidecar and not sidecar_bundle:
217
+ raise typer.BadParameter("--sidecar-bundle is required when --sidecar is used")
204
218
 
205
219
  from specfact_cli.utils.yaml_utils import dump_yaml
206
220
 
@@ -319,6 +333,37 @@ def main(
319
333
  dump_yaml(report.to_dict(), out)
320
334
  console.print(f"\n[dim]Report written to: {out}[/dim]")
321
335
 
336
+ # Run sidecar validation if requested (after main checks)
337
+ if sidecar and sidecar_bundle:
338
+ from specfact_cli.validators.sidecar.models import SidecarConfig
339
+ from specfact_cli.validators.sidecar.orchestrator import run_sidecar_validation
340
+ from specfact_cli.validators.sidecar.unannotated_detector import detect_unannotated_in_repo
341
+
342
+ console.print("\n[bold cyan]Running sidecar validation for unannotated code...[/bold cyan]")
343
+
344
+ # Detect unannotated code
345
+ unannotated = detect_unannotated_in_repo(repo)
346
+ if unannotated:
347
+ console.print(f"[dim]Found {len(unannotated)} unannotated functions[/dim]")
348
+ # Store unannotated functions info for harness generation
349
+ sidecar_config = SidecarConfig.create(sidecar_bundle, repo)
350
+ # Pass unannotated info to orchestrator (via results dict)
351
+ else:
352
+ console.print("[dim]No unannotated functions detected (all functions have contracts)[/dim]")
353
+ sidecar_config = SidecarConfig.create(sidecar_bundle, repo)
354
+
355
+ # Run sidecar validation (harness will be generated for unannotated code)
356
+ sidecar_results = run_sidecar_validation(sidecar_config, console=console)
357
+
358
+ # Display sidecar results
359
+ if sidecar_results.get("crosshair_summary"):
360
+ summary = sidecar_results["crosshair_summary"]
361
+ console.print(
362
+ f"[dim]Sidecar CrossHair: {summary.get('confirmed', 0)} confirmed, "
363
+ f"{summary.get('not_confirmed', 0)} not confirmed, "
364
+ f"{summary.get('violations', 0)} violations[/dim]"
365
+ )
366
+
322
367
  # Exit with appropriate code
323
368
  exit_code = report.get_exit_code()
324
369
  if exit_code == 0:
@@ -0,0 +1,158 @@
1
+ """
2
+ Validate command group for SpecFact CLI.
3
+
4
+ This module provides validation commands including sidecar validation.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+
11
+ import typer
12
+ from beartype import beartype
13
+ from icontract import require
14
+
15
+ from specfact_cli.runtime import get_configured_console
16
+ from specfact_cli.validators.sidecar.crosshair_summary import format_summary_line
17
+ from specfact_cli.validators.sidecar.models import SidecarConfig
18
+ from specfact_cli.validators.sidecar.orchestrator import initialize_sidecar_workspace, run_sidecar_validation
19
+
20
+
21
+ app = typer.Typer(name="validate", help="Validation commands", suggest_commands=False)
22
+ console = get_configured_console()
23
+
24
+ # Create sidecar subcommand group
25
+ sidecar_app = typer.Typer(name="sidecar", help="Sidecar validation commands", suggest_commands=False)
26
+ app.add_typer(sidecar_app)
27
+
28
+
29
+ @sidecar_app.command()
30
+ @beartype
31
+ @require(lambda bundle_name: bundle_name and len(bundle_name.strip()) > 0, "Bundle name must be non-empty")
32
+ @require(lambda repo_path: repo_path.exists(), "Repository path must exist")
33
+ def init(
34
+ bundle_name: str = typer.Argument(..., help="Project bundle name (e.g., 'legacy-api')"),
35
+ repo_path: Path = typer.Argument(..., help="Path to repository root directory"),
36
+ ) -> None:
37
+ """
38
+ Initialize sidecar workspace for validation.
39
+
40
+ Creates sidecar workspace directory structure and configuration for contract-based
41
+ validation of external codebases without modifying source code.
42
+
43
+ **What it does:**
44
+ - Detects framework type (Django, FastAPI, DRF, pure-python)
45
+ - Creates sidecar workspace directory structure
46
+ - Generates configuration files
47
+ - Detects Python environment (venv, poetry, uv, pip)
48
+ - Sets up framework-specific configuration (e.g., DJANGO_SETTINGS_MODULE)
49
+
50
+ **Example:**
51
+ ```bash
52
+ specfact validate sidecar init legacy-api /path/to/repo
53
+ ```
54
+
55
+ **Next steps:**
56
+ After initialization, run `specfact validate sidecar run` to execute validation.
57
+ """
58
+ config = SidecarConfig.create(bundle_name, repo_path)
59
+
60
+ console.print(f"[bold]Initializing sidecar workspace for bundle: {bundle_name}[/bold]")
61
+
62
+ if initialize_sidecar_workspace(config):
63
+ console.print("[green]✓[/green] Sidecar workspace initialized successfully")
64
+ console.print(f" Framework detected: {config.framework_type}")
65
+ if config.django_settings_module:
66
+ console.print(f" Django settings: {config.django_settings_module}")
67
+ else:
68
+ console.print("[red]✗[/red] Failed to initialize sidecar workspace")
69
+ raise typer.Exit(1)
70
+
71
+
72
+ @sidecar_app.command()
73
+ @beartype
74
+ @require(lambda bundle_name: bundle_name and len(bundle_name.strip()) > 0, "Bundle name must be non-empty")
75
+ @require(lambda repo_path: repo_path.exists(), "Repository path must exist")
76
+ def run(
77
+ bundle_name: str = typer.Argument(..., help="Project bundle name (e.g., 'legacy-api')"),
78
+ repo_path: Path = typer.Argument(..., help="Path to repository root directory"),
79
+ run_crosshair: bool = typer.Option(
80
+ True, "--run-crosshair/--no-run-crosshair", help="Run CrossHair symbolic execution analysis"
81
+ ),
82
+ run_specmatic: bool = typer.Option(
83
+ True, "--run-specmatic/--no-run-specmatic", help="Run Specmatic contract testing validation"
84
+ ),
85
+ ) -> None:
86
+ """
87
+ Run sidecar validation workflow.
88
+
89
+ Executes complete sidecar validation workflow including framework detection,
90
+ route extraction, contract population, harness generation, and validation tools.
91
+
92
+ **Workflow steps:**
93
+ 1. **Framework Detection**: Automatically detects Django, FastAPI, DRF, or pure-python
94
+ 2. **Route Extraction**: Extracts routes and schemas from framework-specific patterns
95
+ 3. **Contract Population**: Populates OpenAPI contracts with extracted routes/schemas
96
+ 4. **Harness Generation**: Generates CrossHair harness from populated contracts
97
+ 5. **CrossHair Analysis**: Runs symbolic execution on source code and harness (if enabled)
98
+ 6. **Specmatic Validation**: Runs contract testing against API endpoints (if enabled)
99
+
100
+ **Example:**
101
+ ```bash
102
+ # Run full validation (CrossHair + Specmatic)
103
+ specfact validate sidecar run legacy-api /path/to/repo
104
+
105
+ # Run only CrossHair analysis
106
+ specfact validate sidecar run legacy-api /path/to/repo --no-run-specmatic
107
+
108
+ # Run only Specmatic validation
109
+ specfact validate sidecar run legacy-api /path/to/repo --no-run-crosshair
110
+ ```
111
+
112
+ **Output:**
113
+ - Validation results displayed in console
114
+ - Reports saved to `.specfact/projects/<bundle>/reports/sidecar/`
115
+ - Progress indicators for long-running operations
116
+ """
117
+ config = SidecarConfig.create(bundle_name, repo_path)
118
+ config.tools.run_crosshair = run_crosshair
119
+ config.tools.run_specmatic = run_specmatic
120
+
121
+ console.print(f"[bold]Running sidecar validation for bundle: {bundle_name}[/bold]")
122
+
123
+ results = run_sidecar_validation(config, console=console)
124
+
125
+ # Display results
126
+ console.print("\n[bold]Validation Results:[/bold]")
127
+ console.print(f" Framework: {results.get('framework_detected', 'unknown')}")
128
+ console.print(f" Routes extracted: {results.get('routes_extracted', 0)}")
129
+ console.print(f" Contracts populated: {results.get('contracts_populated', 0)}")
130
+ console.print(f" Harness generated: {results.get('harness_generated', False)}")
131
+
132
+ if results.get("crosshair_results"):
133
+ console.print("\n[bold]CrossHair Results:[/bold]")
134
+ for key, value in results["crosshair_results"].items():
135
+ success = value.get("success", False)
136
+ status = "[green]✓[/green]" if success else "[red]✗[/red]"
137
+ console.print(f" {status} {key}")
138
+
139
+ # Display summary if available
140
+ if results.get("crosshair_summary"):
141
+ summary = results["crosshair_summary"]
142
+ summary_line = format_summary_line(summary)
143
+ console.print(f" {summary_line}")
144
+
145
+ # Show summary file location if generated
146
+ if results.get("crosshair_summary_file"):
147
+ console.print(f" Summary file: {results['crosshair_summary_file']}")
148
+
149
+ if results.get("specmatic_skipped"):
150
+ console.print(
151
+ f"\n[yellow]⚠ Specmatic skipped: {results.get('specmatic_skip_reason', 'Unknown reason')}[/yellow]"
152
+ )
153
+ elif results.get("specmatic_results"):
154
+ console.print("\n[bold]Specmatic Results:[/bold]")
155
+ for key, value in results["specmatic_results"].items():
156
+ success = value.get("success", False)
157
+ status = "[green]✓[/green]" if success else "[red]✗[/red]"
158
+ console.print(f" {status} {key}")
@@ -0,0 +1,11 @@
1
+ """
2
+ Sidecar validation package.
3
+
4
+ This package provides native CLI integration for sidecar validation workflow,
5
+ enabling contract-based validation of external codebases without modifying source code.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+
11
+ __all__ = []