specfact-cli 0.24.0__tar.gz → 0.25.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 (217) hide show
  1. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/.gitignore +2 -1
  2. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/PKG-INFO +1 -1
  3. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/pyproject.toml +1 -1
  4. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/__init__.py +1 -1
  5. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/__init__.py +1 -1
  6. specfact_cli-0.25.0/src/specfact_cli/adapters/backlog_base.py +272 -0
  7. specfact_cli-0.25.0/src/specfact_cli/adapters/github.py +2100 -0
  8. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/import_cmd.py +17 -6
  9. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/sync.py +17 -7
  10. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/validate.py +117 -2
  11. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/integrations/specmatic.py +6 -0
  12. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/bridge_sync.py +288 -122
  13. specfact_cli-0.25.0/src/specfact_cli/validators/change_proposal_integration.py +412 -0
  14. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/contract_populator.py +37 -31
  15. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/crosshair_runner.py +15 -4
  16. specfact_cli-0.25.0/src/specfact_cli/validators/sidecar/crosshair_summary.py +268 -0
  17. specfact_cli-0.25.0/src/specfact_cli/validators/sidecar/dependency_installer.py +234 -0
  18. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/framework_detector.py +2 -2
  19. specfact_cli-0.25.0/src/specfact_cli/validators/sidecar/frameworks/flask.py +298 -0
  20. specfact_cli-0.25.0/src/specfact_cli/validators/sidecar/harness_generator.py +698 -0
  21. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/models.py +15 -3
  22. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/orchestrator.py +95 -5
  23. specfact_cli-0.24.0/src/specfact_cli/adapters/github.py +0 -792
  24. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -164
  25. specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/harness_generator.py +0 -148
  26. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/LICENSE.md +0 -0
  27. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/README.md +0 -0
  28. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/mappings/node-async.yaml +0 -0
  29. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/mappings/python-async.yaml +0 -0
  30. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/mappings/speckit-default.yaml +0 -0
  31. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/shared/cli-enforcement.md +0 -0
  32. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.01-import.md +0 -0
  33. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.02-plan.md +0 -0
  34. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.03-review.md +0 -0
  35. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.04-sdd.md +0 -0
  36. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.05-enforce.md +0 -0
  37. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.06-sync.md +0 -0
  38. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.07-contracts.md +0 -0
  39. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.compare.md +0 -0
  40. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.sync-backlog.md +0 -0
  41. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.validate.md +0 -0
  42. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/schemas/deviation.schema.json +0 -0
  43. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/schemas/plan.schema.json +0 -0
  44. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/schemas/protocol.schema.json +0 -0
  45. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/github-action.yml.j2 +0 -0
  46. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/persona/architect.md.j2 +0 -0
  47. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/persona/developer.md.j2 +0 -0
  48. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/persona/product-owner.md.j2 +0 -0
  49. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/plan.bundle.yaml.j2 +0 -0
  50. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/pr-template.md.j2 +0 -0
  51. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/protocol.yaml.j2 +0 -0
  52. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/STRUCTURE.md +0 -0
  53. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/__init__.py +0 -0
  54. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/README.md +0 -0
  55. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/adapters.py +0 -0
  56. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/bindings.yaml.example +0 -0
  57. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/crosshair_plugin.py +0 -0
  58. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/generate_harness.py +0 -0
  59. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/harness_contracts.py.example +0 -0
  60. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/populate_contracts.py +0 -0
  61. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/run_sidecar.sh +0 -0
  62. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/sidecar-init.sh +0 -0
  63. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/frameworks/django/__init__.py +0 -0
  64. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/frameworks/django/crosshair_django_wrapper.py +0 -0
  65. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/frameworks/django/django_form_extractor.py +0 -0
  66. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/frameworks/django/django_url_extractor.py +0 -0
  67. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/frameworks/drf/__init__.py +0 -0
  68. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/frameworks/drf/drf_serializer_extractor.py +0 -0
  69. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/frameworks/fastapi/__init__.py +0 -0
  70. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/frameworks/fastapi/fastapi_route_extractor.py +0 -0
  71. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/telemetry.yaml.example +0 -0
  72. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/adapters/__init__.py +0 -0
  73. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/adapters/base.py +0 -0
  74. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/adapters/openspec.py +0 -0
  75. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/adapters/openspec_parser.py +0 -0
  76. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/adapters/registry.py +0 -0
  77. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/adapters/speckit.py +0 -0
  78. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/agents/__init__.py +0 -0
  79. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/agents/analyze_agent.py +0 -0
  80. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/agents/base.py +0 -0
  81. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/agents/plan_agent.py +0 -0
  82. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/agents/registry.py +0 -0
  83. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/agents/sync_agent.py +0 -0
  84. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/__init__.py +0 -0
  85. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
  86. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
  87. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
  88. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
  89. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
  90. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
  91. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
  92. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
  93. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
  94. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/cli.py +0 -0
  95. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/__init__.py +0 -0
  96. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/analyze.py +0 -0
  97. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/contract_cmd.py +0 -0
  98. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/drift.py +0 -0
  99. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/enforce.py +0 -0
  100. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/generate.py +0 -0
  101. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/init.py +0 -0
  102. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/migrate.py +0 -0
  103. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/plan.py +0 -0
  104. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/project_cmd.py +0 -0
  105. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/repro.py +0 -0
  106. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/sdd.py +0 -0
  107. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/spec.py +0 -0
  108. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/common/__init__.py +0 -0
  109. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/common/logger_setup.py +0 -0
  110. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/common/logging_utils.py +0 -0
  111. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/common/text_utils.py +0 -0
  112. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/common/utils.py +0 -0
  113. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/comparators/__init__.py +0 -0
  114. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/comparators/plan_comparator.py +0 -0
  115. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
  116. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
  117. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/__init__.py +0 -0
  118. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/contract_generator.py +0 -0
  119. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/openapi_extractor.py +0 -0
  120. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/persona_exporter.py +0 -0
  121. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/plan_generator.py +0 -0
  122. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/protocol_generator.py +0 -0
  123. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/report_generator.py +0 -0
  124. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/task_generator.py +0 -0
  125. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/test_to_openapi.py +0 -0
  126. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/workflow_generator.py +0 -0
  127. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/importers/__init__.py +0 -0
  128. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/importers/speckit_converter.py +0 -0
  129. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/importers/speckit_scanner.py +0 -0
  130. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/integrations/__init__.py +0 -0
  131. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/merge/__init__.py +0 -0
  132. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/merge/resolver.py +0 -0
  133. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/migrations/__init__.py +0 -0
  134. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/migrations/plan_migrator.py +0 -0
  135. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/__init__.py +0 -0
  136. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/bridge.py +0 -0
  137. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/capabilities.py +0 -0
  138. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/change.py +0 -0
  139. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/contract.py +0 -0
  140. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/deviation.py +0 -0
  141. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/enforcement.py +0 -0
  142. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/persona_template.py +0 -0
  143. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/plan.py +0 -0
  144. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/project.py +0 -0
  145. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/protocol.py +0 -0
  146. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/quality.py +0 -0
  147. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/sdd.py +0 -0
  148. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/source_tracking.py +0 -0
  149. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/task.py +0 -0
  150. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/modes/__init__.py +0 -0
  151. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/modes/detector.py +0 -0
  152. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/modes/router.py +0 -0
  153. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/parsers/__init__.py +0 -0
  154. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/parsers/persona_importer.py +0 -0
  155. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/resources/semgrep/async.yml +0 -0
  156. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
  157. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
  158. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/runtime.py +0 -0
  159. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/__init__.py +0 -0
  160. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/bridge_probe.py +0 -0
  161. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/bridge_watch.py +0 -0
  162. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/change_detector.py +0 -0
  163. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/code_to_spec.py +0 -0
  164. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/drift_detector.py +0 -0
  165. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/repository_sync.py +0 -0
  166. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/spec_to_code.py +0 -0
  167. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/spec_to_tests.py +0 -0
  168. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/watcher.py +0 -0
  169. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
  170. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/telemetry.py +0 -0
  171. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/templates/__init__.py +0 -0
  172. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/templates/bridge_templates.py +0 -0
  173. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/templates/specification_templates.py +0 -0
  174. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/__init__.py +0 -0
  175. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
  176. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/bundle_loader.py +0 -0
  177. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/code_change_detector.py +0 -0
  178. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/console.py +0 -0
  179. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/content_sanitizer.py +0 -0
  180. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/context_detection.py +0 -0
  181. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/enrichment_context.py +0 -0
  182. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/enrichment_parser.py +0 -0
  183. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/env_manager.py +0 -0
  184. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/feature_keys.py +0 -0
  185. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/git.py +0 -0
  186. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/github_annotations.py +0 -0
  187. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/ide_setup.py +0 -0
  188. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/incremental_check.py +0 -0
  189. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/optional_deps.py +0 -0
  190. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/performance.py +0 -0
  191. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/progress.py +0 -0
  192. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
  193. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/prompts.py +0 -0
  194. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/sdd_discovery.py +0 -0
  195. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/source_scanner.py +0 -0
  196. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/structure.py +0 -0
  197. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/structured_io.py +0 -0
  198. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/suggestions.py +0 -0
  199. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/terminal.py +0 -0
  200. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/yaml_utils.py +0 -0
  201. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/__init__.py +0 -0
  202. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/agile_validation.py +0 -0
  203. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/cli_first_validator.py +0 -0
  204. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/contract_validator.py +0 -0
  205. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/fsm.py +0 -0
  206. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/repro_checker.py +0 -0
  207. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/schema.py +0 -0
  208. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
  209. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
  210. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
  211. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
  212. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
  213. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
  214. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
  215. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
  216. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/versioning/__init__.py +0 -0
  217. {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/versioning/analyzer.py +0 -0
@@ -123,4 +123,5 @@ _site/
123
123
  findings.json
124
124
  answers.json
125
125
  questions.json
126
- docs/project-plans/
126
+ docs/project-plans/
127
+ harness_contracts.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specfact-cli
3
- Version: 0.24.0
3
+ Version: 0.25.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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "specfact-cli"
7
- version = "0.24.0"
7
+ version = "0.25.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"
@@ -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.24.0"
6
+ __version__ = "0.25.0"
@@ -9,6 +9,6 @@ This package provides command-line tools for:
9
9
  - Validating reproducibility
10
10
  """
11
11
 
12
- __version__ = "0.24.0"
12
+ __version__ = "0.25.0"
13
13
 
14
14
  __all__ = ["__version__"]
@@ -0,0 +1,272 @@
1
+ """
2
+ Base classes and utilities for backlog adapters.
3
+
4
+ This module provides reusable patterns and abstractions for implementing backlog
5
+ adapters (GitHub, Azure DevOps, Jira, Linear, etc.) that support bidirectional
6
+ sync between backlog management tools and OpenSpec change proposals.
7
+
8
+ All backlog adapters should inherit from BacklogAdapterMixin to get common
9
+ functionality for status mapping, metadata extraction, and conflict resolution.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from abc import ABC, abstractmethod
15
+ from datetime import UTC, datetime
16
+ from typing import Any
17
+
18
+ from beartype import beartype
19
+ from icontract import ensure, require
20
+
21
+ from specfact_cli.models.change import ChangeProposal
22
+ from specfact_cli.models.source_tracking import SourceTracking
23
+
24
+
25
+ class BacklogAdapterMixin(ABC):
26
+ """
27
+ Mixin class providing common functionality for backlog adapters.
28
+
29
+ This mixin provides tool-agnostic patterns for:
30
+ - Status mapping (backlog status ↔ OpenSpec status)
31
+ - Metadata extraction (backlog item → change proposal)
32
+ - Conflict resolution (when status differs)
33
+
34
+ Future backlog adapters (ADO, Jira, Linear) should inherit from this mixin
35
+ and implement the abstract methods to provide tool-specific implementations.
36
+ """
37
+
38
+ @abstractmethod
39
+ @beartype
40
+ @require(lambda status: isinstance(status, str) and len(status) > 0, "Status must be non-empty string")
41
+ @ensure(lambda result: isinstance(result, str) and len(result) > 0, "Must return non-empty status string")
42
+ def map_backlog_status_to_openspec(self, status: str) -> str:
43
+ """
44
+ Map backlog tool status to OpenSpec change status.
45
+
46
+ Args:
47
+ status: Backlog tool status (e.g., GitHub label, ADO state, Jira status, Linear state)
48
+
49
+ Returns:
50
+ OpenSpec change status (proposed, in-progress, applied, deprecated, discarded)
51
+
52
+ Note:
53
+ This method must be implemented by each backlog adapter to provide
54
+ tool-specific status mapping logic.
55
+ """
56
+
57
+ @abstractmethod
58
+ @beartype
59
+ @require(lambda status: isinstance(status, str) and len(status) > 0, "Status must be non-empty string")
60
+ @ensure(lambda result: isinstance(result, (str, list)), "Must return status string or list of status strings")
61
+ def map_openspec_status_to_backlog(self, status: str) -> str | list[str]:
62
+ """
63
+ Map OpenSpec change status to backlog tool status.
64
+
65
+ Args:
66
+ status: OpenSpec change status (proposed, in-progress, applied, deprecated, discarded)
67
+
68
+ Returns:
69
+ Backlog tool status (e.g., GitHub label, ADO state, Jira status, Linear state)
70
+ or list of status strings for tools that support multiple status indicators
71
+
72
+ Note:
73
+ This method must be implemented by each backlog adapter to provide
74
+ tool-specific status mapping logic.
75
+ """
76
+
77
+ @abstractmethod
78
+ @beartype
79
+ @require(lambda item_data: isinstance(item_data, dict), "Item data must be dict")
80
+ @ensure(lambda result: isinstance(result, dict), "Must return dict with extracted fields")
81
+ def extract_change_proposal_data(self, item_data: dict[str, Any]) -> dict[str, Any]:
82
+ """
83
+ Extract change proposal data from backlog item.
84
+
85
+ Args:
86
+ item_data: Backlog item data (e.g., GitHub issue dict, ADO work item dict, Jira issue dict, Linear issue dict)
87
+
88
+ Returns:
89
+ Dict with change proposal fields:
90
+ - title: str
91
+ - description: str (What Changes section)
92
+ - rationale: str (Why section)
93
+ - status: str (mapped to OpenSpec status)
94
+ - Other optional fields (timeline, owner, stakeholders, dependencies)
95
+
96
+ Raises:
97
+ ValueError: If required fields are missing or data is malformed
98
+
99
+ Note:
100
+ This method must be implemented by each backlog adapter to parse
101
+ tool-specific data formats (GitHub issue body, ADO work item fields, etc.).
102
+ """
103
+
104
+ @beartype
105
+ @require(lambda item_data: isinstance(item_data, dict), "Item data must be dict")
106
+ @require(lambda tool_name: isinstance(tool_name, str) and len(tool_name) > 0, "Tool name must be non-empty")
107
+ @ensure(lambda result: isinstance(result, SourceTracking), "Must return SourceTracking")
108
+ def create_source_tracking(
109
+ self, item_data: dict[str, Any], tool_name: str, bridge_config: Any = None
110
+ ) -> SourceTracking:
111
+ """
112
+ Create SourceTracking from backlog item metadata.
113
+
114
+ This is a reusable utility method that all backlog adapters can use
115
+ to store tool-specific metadata in source_tracking.
116
+
117
+ Args:
118
+ item_data: Backlog item data with metadata (ID, URL, status, assignees, etc.)
119
+ tool_name: Tool identifier (e.g., "github", "ado", "jira", "linear")
120
+ bridge_config: Optional bridge configuration (for cross-repo support)
121
+
122
+ Returns:
123
+ SourceTracking instance with tool-specific metadata stored in source_metadata
124
+
125
+ Note:
126
+ This method provides a common pattern for storing backlog item metadata.
127
+ Each adapter should call this method and add tool-specific fields to source_metadata.
128
+ """
129
+ source_metadata: dict[str, Any] = {}
130
+
131
+ # Extract common fields (ID, URL) if present
132
+ if "id" in item_data or "number" in item_data:
133
+ source_metadata["source_id"] = item_data.get("id") or item_data.get("number")
134
+ # Prefer html_url (user-friendly) over url (API URL)
135
+ if "html_url" in item_data:
136
+ source_metadata["source_url"] = item_data.get("html_url")
137
+ elif "url" in item_data:
138
+ source_metadata["source_url"] = item_data.get("url")
139
+ if "state" in item_data:
140
+ source_metadata["source_state"] = item_data.get("state")
141
+ if "assignees" in item_data or "assignee" in item_data:
142
+ assignees = item_data.get("assignees", [])
143
+ if not assignees and "assignee" in item_data:
144
+ assignees = [item_data["assignee"]] if item_data["assignee"] else []
145
+ source_metadata["assignees"] = assignees
146
+
147
+ # Add cross-repo support if bridge_config has external_base_path
148
+ if bridge_config and hasattr(bridge_config, "external_base_path") and bridge_config.external_base_path:
149
+ source_metadata["external_base_path"] = str(bridge_config.external_base_path)
150
+
151
+ return SourceTracking(tool=tool_name, source_metadata=source_metadata)
152
+
153
+ @beartype
154
+ @require(
155
+ lambda openspec_status: isinstance(openspec_status, str) and len(openspec_status) > 0,
156
+ "Status must be non-empty",
157
+ )
158
+ @require(
159
+ lambda backlog_status: isinstance(backlog_status, str) and len(backlog_status) > 0, "Status must be non-empty"
160
+ )
161
+ @ensure(lambda result: isinstance(result, str), "Must return conflict resolution strategy name")
162
+ def resolve_status_conflict(
163
+ self, openspec_status: str, backlog_status: str, strategy: str = "prefer_openspec"
164
+ ) -> str:
165
+ """
166
+ Resolve status conflict when OpenSpec and backlog status differ.
167
+
168
+ Args:
169
+ openspec_status: OpenSpec change status
170
+ backlog_status: Backlog tool status (mapped to OpenSpec format)
171
+ strategy: Conflict resolution strategy:
172
+ - "prefer_openspec": Use OpenSpec status (default)
173
+ - "prefer_backlog": Use backlog status
174
+ - "merge": Use most advanced status (in-progress > proposed, applied > in-progress)
175
+
176
+ Returns:
177
+ Resolved status (OpenSpec format)
178
+
179
+ Note:
180
+ This provides a reusable conflict resolution pattern that all backlog
181
+ adapters can use. The default strategy prefers OpenSpec as the source of truth.
182
+ """
183
+ if openspec_status == backlog_status:
184
+ return openspec_status
185
+
186
+ if strategy == "prefer_openspec":
187
+ return openspec_status
188
+ if strategy == "prefer_backlog":
189
+ return backlog_status
190
+ if strategy == "merge":
191
+ # Status priority: applied > in-progress > proposed > deprecated > discarded
192
+ status_priority = {
193
+ "applied": 5,
194
+ "in-progress": 4,
195
+ "proposed": 3,
196
+ "deprecated": 2,
197
+ "discarded": 1,
198
+ }
199
+ openspec_priority = status_priority.get(openspec_status, 0)
200
+ backlog_priority = status_priority.get(backlog_status, 0)
201
+ return openspec_status if openspec_priority >= backlog_priority else backlog_status
202
+
203
+ # Default: prefer OpenSpec
204
+ return openspec_status
205
+
206
+ @beartype
207
+ @require(lambda item_data: isinstance(item_data, dict), "Item data must be dict")
208
+ @require(lambda tool_name: isinstance(tool_name, str) and len(tool_name) > 0, "Tool name must be non-empty")
209
+ @ensure(lambda result: isinstance(result, ChangeProposal) or result is None, "Must return ChangeProposal or None")
210
+ def import_backlog_item_as_proposal(
211
+ self, item_data: dict[str, Any], tool_name: str, bridge_config: Any = None
212
+ ) -> ChangeProposal | None:
213
+ """
214
+ Import backlog item as OpenSpec change proposal (reusable pattern).
215
+
216
+ This method provides a common workflow that all backlog adapters can use:
217
+ 1. Extract change proposal data from backlog item
218
+ 2. Map backlog status to OpenSpec status
219
+ 3. Create SourceTracking with tool-specific metadata
220
+ 4. Create ChangeProposal instance
221
+
222
+ Args:
223
+ item_data: Backlog item data (tool-specific format)
224
+ tool_name: Tool identifier (e.g., "github", "ado", "jira", "linear")
225
+ bridge_config: Optional bridge configuration (for cross-repo support)
226
+
227
+ Returns:
228
+ ChangeProposal instance if successful, None if data is invalid
229
+
230
+ Raises:
231
+ ValueError: If required fields are missing or data is malformed
232
+
233
+ Note:
234
+ This method implements the common import pattern. Each backlog adapter
235
+ should call this method after implementing extract_change_proposal_data()
236
+ and map_backlog_status_to_openspec().
237
+ """
238
+ try:
239
+ # Extract change proposal data (tool-specific parsing)
240
+ proposal_data = self.extract_change_proposal_data(item_data)
241
+
242
+ # Get status from extracted data or map from backlog item
243
+ if "status" in proposal_data:
244
+ openspec_status = proposal_data["status"]
245
+ else:
246
+ # Map backlog status to OpenSpec status
247
+ backlog_status = item_data.get("state") or item_data.get("status") or "open"
248
+ openspec_status = self.map_backlog_status_to_openspec(backlog_status)
249
+
250
+ # Create source tracking
251
+ source_tracking = self.create_source_tracking(item_data, tool_name, bridge_config)
252
+
253
+ # Create change proposal
254
+ change_id = proposal_data.get("change_id") or item_data.get("id") or item_data.get("number") or "unknown"
255
+ return ChangeProposal(
256
+ name=change_id,
257
+ title=proposal_data.get("title", "Untitled Change Proposal"),
258
+ description=proposal_data.get("description", ""),
259
+ rationale=proposal_data.get("rationale", ""),
260
+ timeline=proposal_data.get("timeline"),
261
+ owner=proposal_data.get("owner"),
262
+ stakeholders=proposal_data.get("stakeholders", []),
263
+ dependencies=proposal_data.get("dependencies", []),
264
+ status=openspec_status,
265
+ created_at=proposal_data.get("created_at") or datetime.now(UTC).isoformat(),
266
+ applied_at=proposal_data.get("applied_at"),
267
+ archived_at=proposal_data.get("archived_at"),
268
+ source_tracking=source_tracking,
269
+ )
270
+ except (KeyError, ValueError, TypeError) as e:
271
+ msg = f"Failed to import backlog item as change proposal: {e}"
272
+ raise ValueError(msg) from e