specfact-cli 0.25.1__tar.gz → 0.25.3__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 (219) hide show
  1. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/PKG-INFO +3 -1
  2. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/pyproject.toml +3 -1
  3. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/__init__.py +1 -1
  4. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/__init__.py +1 -1
  5. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/adapters/ado.py +62 -16
  6. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/adapters/github.py +61 -12
  7. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/agents/analyze_agent.py +1 -0
  8. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/cli.py +5 -1
  9. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/commands/__init__.py +2 -0
  10. specfact_cli-0.25.3/src/specfact_cli/commands/auth.py +341 -0
  11. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/commands/import_cmd.py +4 -4
  12. specfact_cli-0.25.3/src/specfact_cli/contracts/__init__.py +3 -0
  13. specfact_cli-0.25.3/src/specfact_cli/contracts/crosshair_props.py +25 -0
  14. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/sync/bridge_sync.py +1582 -0
  15. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/telemetry.py +1 -0
  16. specfact_cli-0.25.3/src/specfact_cli/utils/auth_tokens.py +182 -0
  17. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/repro_checker.py +76 -15
  18. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/orchestrator.py +2 -2
  19. specfact_cli-0.25.1/resources/templates/sidecar/STRUCTURE.md +0 -70
  20. specfact_cli-0.25.1/resources/templates/sidecar/__init__.py +0 -0
  21. specfact_cli-0.25.1/resources/templates/sidecar/common/README.md +0 -208
  22. specfact_cli-0.25.1/resources/templates/sidecar/common/adapters.py +0 -926
  23. specfact_cli-0.25.1/resources/templates/sidecar/common/bindings.yaml.example +0 -42
  24. specfact_cli-0.25.1/resources/templates/sidecar/common/crosshair_plugin.py +0 -34
  25. specfact_cli-0.25.1/resources/templates/sidecar/common/generate_harness.py +0 -561
  26. specfact_cli-0.25.1/resources/templates/sidecar/common/harness_contracts.py.example +0 -26
  27. specfact_cli-0.25.1/resources/templates/sidecar/common/populate_contracts.py +0 -1013
  28. specfact_cli-0.25.1/resources/templates/sidecar/common/run_sidecar.sh +0 -579
  29. specfact_cli-0.25.1/resources/templates/sidecar/common/sidecar-init.sh +0 -108
  30. specfact_cli-0.25.1/resources/templates/sidecar/frameworks/django/__init__.py +0 -0
  31. specfact_cli-0.25.1/resources/templates/sidecar/frameworks/django/crosshair_django_wrapper.py +0 -140
  32. specfact_cli-0.25.1/resources/templates/sidecar/frameworks/django/django_form_extractor.py +0 -440
  33. specfact_cli-0.25.1/resources/templates/sidecar/frameworks/django/django_url_extractor.py +0 -338
  34. specfact_cli-0.25.1/resources/templates/sidecar/frameworks/drf/__init__.py +0 -0
  35. specfact_cli-0.25.1/resources/templates/sidecar/frameworks/drf/drf_serializer_extractor.py +0 -372
  36. specfact_cli-0.25.1/resources/templates/sidecar/frameworks/fastapi/__init__.py +0 -0
  37. specfact_cli-0.25.1/resources/templates/sidecar/frameworks/fastapi/fastapi_route_extractor.py +0 -643
  38. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/.gitignore +0 -0
  39. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/LICENSE.md +0 -0
  40. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/README.md +0 -0
  41. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/mappings/node-async.yaml +0 -0
  42. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/mappings/python-async.yaml +0 -0
  43. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/mappings/speckit-default.yaml +0 -0
  44. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/prompts/shared/cli-enforcement.md +0 -0
  45. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/prompts/specfact.01-import.md +0 -0
  46. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/prompts/specfact.02-plan.md +0 -0
  47. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/prompts/specfact.03-review.md +0 -0
  48. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/prompts/specfact.04-sdd.md +0 -0
  49. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/prompts/specfact.05-enforce.md +0 -0
  50. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/prompts/specfact.06-sync.md +0 -0
  51. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/prompts/specfact.07-contracts.md +0 -0
  52. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/prompts/specfact.compare.md +0 -0
  53. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/prompts/specfact.sync-backlog.md +0 -0
  54. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/prompts/specfact.validate.md +0 -0
  55. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/schemas/deviation.schema.json +0 -0
  56. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/schemas/plan.schema.json +0 -0
  57. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/schemas/protocol.schema.json +0 -0
  58. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/templates/github-action.yml.j2 +0 -0
  59. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/templates/persona/architect.md.j2 +0 -0
  60. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/templates/persona/developer.md.j2 +0 -0
  61. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/templates/persona/product-owner.md.j2 +0 -0
  62. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/templates/plan.bundle.yaml.j2 +0 -0
  63. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/templates/pr-template.md.j2 +0 -0
  64. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/templates/protocol.yaml.j2 +0 -0
  65. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/resources/templates/telemetry.yaml.example +0 -0
  66. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/adapters/__init__.py +0 -0
  67. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/adapters/backlog_base.py +0 -0
  68. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/adapters/base.py +0 -0
  69. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/adapters/openspec.py +0 -0
  70. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/adapters/openspec_parser.py +0 -0
  71. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/adapters/registry.py +0 -0
  72. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/adapters/speckit.py +0 -0
  73. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/agents/__init__.py +0 -0
  74. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/agents/base.py +0 -0
  75. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/agents/plan_agent.py +0 -0
  76. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/agents/registry.py +0 -0
  77. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/agents/sync_agent.py +0 -0
  78. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/analyzers/__init__.py +0 -0
  79. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
  80. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
  81. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
  82. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
  83. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
  84. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
  85. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
  86. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
  87. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
  88. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/commands/analyze.py +0 -0
  89. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/commands/contract_cmd.py +0 -0
  90. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/commands/drift.py +0 -0
  91. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/commands/enforce.py +0 -0
  92. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/commands/generate.py +0 -0
  93. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/commands/init.py +0 -0
  94. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/commands/migrate.py +0 -0
  95. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/commands/plan.py +0 -0
  96. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/commands/project_cmd.py +0 -0
  97. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/commands/repro.py +0 -0
  98. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/commands/sdd.py +0 -0
  99. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/commands/spec.py +0 -0
  100. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/commands/sync.py +0 -0
  101. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/commands/validate.py +0 -0
  102. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/common/__init__.py +0 -0
  103. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/common/logger_setup.py +0 -0
  104. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/common/logging_utils.py +0 -0
  105. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/common/text_utils.py +0 -0
  106. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/common/utils.py +0 -0
  107. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/comparators/__init__.py +0 -0
  108. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/comparators/plan_comparator.py +0 -0
  109. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
  110. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
  111. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/generators/__init__.py +0 -0
  112. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/generators/contract_generator.py +0 -0
  113. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/generators/openapi_extractor.py +0 -0
  114. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/generators/persona_exporter.py +0 -0
  115. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/generators/plan_generator.py +0 -0
  116. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/generators/protocol_generator.py +0 -0
  117. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/generators/report_generator.py +0 -0
  118. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/generators/task_generator.py +0 -0
  119. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/generators/test_to_openapi.py +0 -0
  120. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/generators/workflow_generator.py +0 -0
  121. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/importers/__init__.py +0 -0
  122. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/importers/speckit_converter.py +0 -0
  123. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/importers/speckit_scanner.py +0 -0
  124. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/integrations/__init__.py +0 -0
  125. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/integrations/specmatic.py +0 -0
  126. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/merge/__init__.py +0 -0
  127. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/merge/resolver.py +0 -0
  128. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/migrations/__init__.py +0 -0
  129. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/migrations/plan_migrator.py +0 -0
  130. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/models/__init__.py +0 -0
  131. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/models/bridge.py +0 -0
  132. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/models/capabilities.py +0 -0
  133. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/models/change.py +0 -0
  134. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/models/contract.py +0 -0
  135. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/models/deviation.py +0 -0
  136. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/models/enforcement.py +0 -0
  137. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/models/persona_template.py +0 -0
  138. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/models/plan.py +0 -0
  139. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/models/project.py +0 -0
  140. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/models/protocol.py +0 -0
  141. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/models/quality.py +0 -0
  142. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/models/sdd.py +0 -0
  143. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/models/source_tracking.py +0 -0
  144. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/models/task.py +0 -0
  145. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/modes/__init__.py +0 -0
  146. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/modes/detector.py +0 -0
  147. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/modes/router.py +0 -0
  148. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/parsers/__init__.py +0 -0
  149. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/parsers/persona_importer.py +0 -0
  150. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/resources/semgrep/async.yml +0 -0
  151. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
  152. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
  153. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/runtime.py +0 -0
  154. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/sync/__init__.py +0 -0
  155. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/sync/bridge_probe.py +0 -0
  156. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/sync/bridge_watch.py +0 -0
  157. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/sync/change_detector.py +0 -0
  158. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/sync/code_to_spec.py +0 -0
  159. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/sync/drift_detector.py +0 -0
  160. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/sync/repository_sync.py +0 -0
  161. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/sync/spec_to_code.py +0 -0
  162. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/sync/spec_to_tests.py +0 -0
  163. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/sync/watcher.py +0 -0
  164. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
  165. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/templates/__init__.py +0 -0
  166. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/templates/bridge_templates.py +0 -0
  167. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/templates/specification_templates.py +0 -0
  168. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/__init__.py +0 -0
  169. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
  170. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/bundle_loader.py +0 -0
  171. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/code_change_detector.py +0 -0
  172. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/console.py +0 -0
  173. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/content_sanitizer.py +0 -0
  174. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/context_detection.py +0 -0
  175. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/enrichment_context.py +0 -0
  176. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/enrichment_parser.py +0 -0
  177. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/env_manager.py +0 -0
  178. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/feature_keys.py +0 -0
  179. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/git.py +0 -0
  180. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/github_annotations.py +0 -0
  181. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/ide_setup.py +0 -0
  182. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/incremental_check.py +0 -0
  183. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/optional_deps.py +0 -0
  184. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/performance.py +0 -0
  185. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/progress.py +0 -0
  186. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
  187. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/prompts.py +0 -0
  188. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/sdd_discovery.py +0 -0
  189. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/source_scanner.py +0 -0
  190. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/structure.py +0 -0
  191. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/structured_io.py +0 -0
  192. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/suggestions.py +0 -0
  193. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/terminal.py +0 -0
  194. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/utils/yaml_utils.py +0 -0
  195. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/__init__.py +0 -0
  196. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/agile_validation.py +0 -0
  197. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
  198. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/cli_first_validator.py +0 -0
  199. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/contract_validator.py +0 -0
  200. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/fsm.py +0 -0
  201. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/schema.py +0 -0
  202. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
  203. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
  204. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
  205. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
  206. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
  207. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
  208. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
  209. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
  210. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
  211. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
  212. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
  213. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
  214. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
  215. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/models.py +0 -0
  216. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
  217. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
  218. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/src/specfact_cli/versioning/__init__.py +0 -0
  219. {specfact_cli-0.25.1 → specfact_cli-0.25.3}/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.25.1
3
+ Version: 0.25.3
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
@@ -223,6 +223,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
223
223
  Classifier: Topic :: Software Development :: Quality Assurance
224
224
  Classifier: Topic :: Software Development :: Testing
225
225
  Requires-Python: >=3.11
226
+ Requires-Dist: azure-identity>=1.17.1
226
227
  Requires-Dist: beartype>=0.22.4
227
228
  Requires-Dist: crosshair-tool>=0.0.97
228
229
  Requires-Dist: gitpython>=3.1.45
@@ -237,6 +238,7 @@ Requires-Dist: opentelemetry-sdk>=1.27.0
237
238
  Requires-Dist: pydantic>=2.12.3
238
239
  Requires-Dist: python-dotenv>=1.2.1
239
240
  Requires-Dist: pyyaml>=6.0.3
241
+ Requires-Dist: requests>=2.32.3
240
242
  Requires-Dist: rich<13.6.0,>=13.5.2
241
243
  Requires-Dist: ruamel-yaml>=0.18.16
242
244
  Requires-Dist: ruff>=0.14.2
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "specfact-cli"
7
- version = "0.25.1"
7
+ version = "0.25.3"
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"
@@ -31,6 +31,8 @@ dependencies = [
31
31
  "python-dotenv>=1.2.1",
32
32
  "typing-extensions>=4.15.0",
33
33
  "PyYAML>=6.0.3",
34
+ "requests>=2.32.3",
35
+ "azure-identity>=1.17.1",
34
36
 
35
37
  # CLI framework
36
38
  "typer>=0.20.0",
@@ -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.25.1"
6
+ __version__ = "0.25.3"
@@ -9,6 +9,6 @@ This package provides command-line tools for:
9
9
  - Validating reproducibility
10
10
  """
11
11
 
12
- __version__ = "0.25.1"
12
+ __version__ = "0.25.3"
13
13
 
14
14
  __all__ = ["__version__"]
@@ -26,6 +26,7 @@ from specfact_cli.adapters.base import BridgeAdapter
26
26
  from specfact_cli.models.bridge import BridgeConfig
27
27
  from specfact_cli.models.capabilities import ToolCapabilities
28
28
  from specfact_cli.models.change import ChangeProposal, ChangeTracking
29
+ from specfact_cli.utils.auth_tokens import get_token
29
30
 
30
31
 
31
32
  console = Console()
@@ -57,19 +58,27 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
57
58
  org: Azure DevOps organization name (optional, can be provided via env/CLI)
58
59
  project: Azure DevOps project name (optional, can be provided via env/CLI)
59
60
  base_url: Azure DevOps base URL (optional, defaults to https://dev.azure.com)
60
- api_token: Azure DevOps PAT (optional, uses AZURE_DEVOPS_TOKEN env var)
61
+ api_token: Azure DevOps PAT (optional, uses AZURE_DEVOPS_TOKEN env var or stored auth token)
61
62
  work_item_type: Work item type (optional, derived from process template if not provided)
62
63
  """
63
64
  self.org = org
64
65
  self.project = project
66
+ self.auth_scheme: str | None = None
65
67
 
66
- # Token resolution: explicit token > env var
68
+ # Token resolution: explicit token > env var > stored token
67
69
  if api_token:
68
70
  self.api_token = api_token
71
+ self.auth_scheme = "basic"
69
72
  elif os.environ.get("AZURE_DEVOPS_TOKEN"):
70
73
  self.api_token = os.environ.get("AZURE_DEVOPS_TOKEN")
74
+ self.auth_scheme = "basic"
75
+ elif stored_token := get_token("azure-devops"):
76
+ self.api_token = stored_token.get("access_token")
77
+ token_type = (stored_token.get("token_type") or "bearer").lower()
78
+ self.auth_scheme = "bearer" if token_type == "bearer" else "basic"
71
79
  else:
72
80
  self.api_token = None
81
+ self.auth_scheme = None
73
82
 
74
83
  # Base URL defaults to Azure DevOps Services (cloud)
75
84
  self.base_url = base_url or "https://dev.azure.com"
@@ -210,12 +219,13 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
210
219
 
211
220
  description = ""
212
221
  rationale = ""
222
+ impact = ""
213
223
 
214
224
  # Parse markdown sections (Why, What Changes)
215
225
  if description_raw:
216
226
  # Extract "Why" section (stop at What Changes or OpenSpec footer)
217
227
  why_match = re.search(
218
- r"##\s+Why\s*\n(.*?)(?=\n##\s+What\s+Changes\s|\n---\s*\n\*OpenSpec Change Proposal:|\Z)",
228
+ r"##\s+Why\s*\n(.*?)(?=\n##\s+What\s+Changes\s|\n##\s+Impact\s|\n---\s*\n\*OpenSpec Change Proposal:|\Z)",
219
229
  description_raw,
220
230
  re.DOTALL | re.IGNORECASE,
221
231
  )
@@ -224,7 +234,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
224
234
 
225
235
  # Extract "What Changes" section (stop at OpenSpec footer)
226
236
  what_match = re.search(
227
- r"##\s+What\s+Changes\s*\n(.*?)(?=\n---\s*\n\*OpenSpec Change Proposal:|\Z)",
237
+ r"##\s+What\s+Changes\s*\n(.*?)(?=\n##\s+Impact\s|\n---\s*\n\*OpenSpec Change Proposal:|\Z)",
228
238
  description_raw,
229
239
  re.DOTALL | re.IGNORECASE,
230
240
  )
@@ -235,6 +245,14 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
235
245
  body_clean = re.sub(r"\n---\s*\n\*OpenSpec Change Proposal:.*", "", description_raw, flags=re.DOTALL)
236
246
  description = body_clean.strip()
237
247
 
248
+ impact_match = re.search(
249
+ r"##\s+Impact\s*\n(.*?)(?=\n---\s*\n\*OpenSpec Change Proposal:|\Z)",
250
+ description_raw,
251
+ re.DOTALL | re.IGNORECASE,
252
+ )
253
+ if impact_match:
254
+ impact = impact_match.group(1).strip()
255
+
238
256
  # Extract change ID from OpenSpec metadata footer or work item ID
239
257
  change_id = None
240
258
  if description_raw:
@@ -306,6 +324,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
306
324
  "title": title,
307
325
  "description": description,
308
326
  "rationale": rationale,
327
+ "impact": impact,
309
328
  "status": status,
310
329
  "created_at": created_at,
311
330
  "timeline": timeline,
@@ -505,7 +524,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
505
524
  msg = (
506
525
  "Azure DevOps API token required. Options:\n"
507
526
  " 1. Set AZURE_DEVOPS_TOKEN environment variable\n"
508
- " 2. Provide via --ado-token option"
527
+ " 2. Provide via --ado-token option\n"
528
+ " 3. Run `specfact auth azure-devops` for device code authentication"
509
529
  )
510
530
  raise ValueError(msg)
511
531
 
@@ -946,8 +966,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
946
966
  # Get process template from project
947
967
  url = f"{self.base_url}/{org}/_apis/projects/{project}?api-version=7.1"
948
968
  headers = {
949
- "Authorization": f"Basic {self._encode_pat(self.api_token)}",
950
969
  "Content-Type": "application/json",
970
+ **self._auth_headers(),
951
971
  }
952
972
  response = requests.get(url, headers=headers, timeout=30)
953
973
  response.raise_for_status()
@@ -1054,6 +1074,14 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
1054
1074
 
1055
1075
  return base64.b64encode(f":{token}".encode()).decode()
1056
1076
 
1077
+ def _auth_headers(self) -> dict[str, str]:
1078
+ """Return authorization headers based on token type."""
1079
+ if not self.api_token:
1080
+ return {}
1081
+ if self.auth_scheme == "bearer":
1082
+ return {"Authorization": f"Bearer {self.api_token}"}
1083
+ return {"Authorization": f"Basic {self._encode_pat(self.api_token)}"}
1084
+
1057
1085
  def _work_item_exists(self, work_item_id: int | str, org: str, project: str) -> bool:
1058
1086
  """
1059
1087
  Check if a work item exists in Azure DevOps.
@@ -1078,8 +1106,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
1078
1106
 
1079
1107
  url = f"{self.base_url}/{org}/{project}/_apis/wit/workitems/{work_item_id}?api-version=7.1"
1080
1108
  headers = {
1081
- "Authorization": f"Basic {self._encode_pat(self.api_token)}",
1082
1109
  "Accept": "application/json",
1110
+ **self._auth_headers(),
1083
1111
  }
1084
1112
 
1085
1113
  try:
@@ -1121,8 +1149,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
1121
1149
 
1122
1150
  url = f"{self.base_url}/{org}/{project}/_apis/wit/workitems/{work_item_id}?api-version=7.1"
1123
1151
  headers = {
1124
- "Authorization": f"Basic {self._encode_pat(self.api_token)}",
1125
1152
  "Accept": "application/json",
1153
+ **self._auth_headers(),
1126
1154
  }
1127
1155
 
1128
1156
  try:
@@ -1165,8 +1193,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
1165
1193
  }
1166
1194
  url = f"{self.base_url}/{org}/{project}/_apis/wit/wiql?api-version=7.1"
1167
1195
  headers = {
1168
- "Authorization": f"Basic {self._encode_pat(self.api_token)}",
1169
1196
  "Content-Type": "application/json",
1197
+ **self._auth_headers(),
1170
1198
  }
1171
1199
 
1172
1200
  try:
@@ -1208,6 +1236,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
1208
1236
  title = proposal_data.get("title", "Untitled Change Proposal")
1209
1237
  description = proposal_data.get("description", "")
1210
1238
  rationale = proposal_data.get("rationale", "")
1239
+ impact = proposal_data.get("impact", "")
1211
1240
  status = proposal_data.get("status", "proposed")
1212
1241
  change_id = proposal_data.get("change_id", "unknown")
1213
1242
  raw_title, raw_body = self._extract_raw_fields(proposal_data)
@@ -1243,8 +1272,16 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
1243
1272
  body_parts.append(line)
1244
1273
  body_parts.append("") # Blank line
1245
1274
 
1275
+ if impact:
1276
+ body_parts.append("## Impact")
1277
+ body_parts.append("")
1278
+ impact_lines = impact.strip().split("\n")
1279
+ for line in impact_lines:
1280
+ body_parts.append(line)
1281
+ body_parts.append("")
1282
+
1246
1283
  # If no content, add placeholder
1247
- if not body_parts or (not rationale and not description):
1284
+ if not body_parts or (not rationale and not description and not impact):
1248
1285
  body_parts.append("No description provided.")
1249
1286
  body_parts.append("")
1250
1287
 
@@ -1268,8 +1305,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
1268
1305
  # Create work item via Azure DevOps API
1269
1306
  url = f"{self.base_url}/{org}/{project}/_apis/wit/workitems/${work_item_type}?api-version=7.1"
1270
1307
  headers = {
1271
- "Authorization": f"Basic {self._encode_pat(self.api_token)}",
1272
1308
  "Content-Type": "application/json-patch+json",
1309
+ **self._auth_headers(),
1273
1310
  }
1274
1311
 
1275
1312
  # Build JSON Patch document for work item creation
@@ -1406,8 +1443,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
1406
1443
  # Update work item state via Azure DevOps API
1407
1444
  url = f"{self.base_url}/{org}/{project}/_apis/wit/workitems/{work_item_id}?api-version=7.1"
1408
1445
  headers = {
1409
- "Authorization": f"Basic {self._encode_pat(self.api_token)}",
1410
1446
  "Content-Type": "application/json-patch+json",
1447
+ **self._auth_headers(),
1411
1448
  }
1412
1449
  patch_document = [{"op": "replace", "path": "/fields/System.State", "value": ado_state}]
1413
1450
 
@@ -1450,6 +1487,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
1450
1487
  title = proposal_data.get("title", "Untitled Change Proposal")
1451
1488
  description = proposal_data.get("description", "")
1452
1489
  rationale = proposal_data.get("rationale", "")
1490
+ impact = proposal_data.get("impact", "")
1453
1491
  status = proposal_data.get("status", "proposed")
1454
1492
  change_id = proposal_data.get("change_id", "unknown")
1455
1493
  raw_title, raw_body = self._extract_raw_fields(proposal_data)
@@ -1485,8 +1523,16 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
1485
1523
  body_parts.append(line)
1486
1524
  body_parts.append("") # Blank line
1487
1525
 
1526
+ if impact:
1527
+ body_parts.append("## Impact")
1528
+ body_parts.append("")
1529
+ impact_lines = impact.strip().split("\n")
1530
+ for line in impact_lines:
1531
+ body_parts.append(line)
1532
+ body_parts.append("")
1533
+
1488
1534
  # If no content, add placeholder
1489
- if not body_parts or (not rationale and not description):
1535
+ if not body_parts or (not rationale and not description and not impact):
1490
1536
  body_parts.append("No description provided.")
1491
1537
  body_parts.append("")
1492
1538
 
@@ -1507,8 +1553,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
1507
1553
  # Update work item body and state via Azure DevOps API
1508
1554
  url = f"{self.base_url}/{org}/{project}/_apis/wit/workitems/{work_item_id}?api-version=7.1"
1509
1555
  headers = {
1510
- "Authorization": f"Basic {self._encode_pat(self.api_token)}",
1511
1556
  "Content-Type": "application/json-patch+json",
1557
+ **self._auth_headers(),
1512
1558
  }
1513
1559
 
1514
1560
  # Build JSON Patch document for work item update
@@ -1623,8 +1669,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
1623
1669
  # Update work item state via Azure DevOps API
1624
1670
  url = f"{self.base_url}/{org}/{project}/_apis/wit/workitems/{work_item_id}?api-version=7.1"
1625
1671
  headers = {
1626
- "Authorization": f"Basic {self._encode_pat(self.api_token)}",
1627
1672
  "Content-Type": "application/json-patch+json",
1673
+ **self._auth_headers(),
1628
1674
  }
1629
1675
 
1630
1676
  # Build JSON Patch document for state update
@@ -1971,8 +2017,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin):
1971
2017
  # Azure DevOps API for adding comments to work items
1972
2018
  url = f"{self.base_url}/{org}/{project}/_apis/wit/workitems/{work_item_id}/comments?api-version=7.1"
1973
2019
  headers = {
1974
- "Authorization": f"Basic {self._encode_pat(self.api_token)}",
1975
2020
  "Content-Type": "application/json",
2021
+ **self._auth_headers(),
1976
2022
  }
1977
2023
 
1978
2024
  # Build request body for comment
@@ -30,6 +30,7 @@ from specfact_cli.adapters.base import BridgeAdapter
30
30
  from specfact_cli.models.bridge import BridgeConfig
31
31
  from specfact_cli.models.capabilities import ToolCapabilities
32
32
  from specfact_cli.models.change import ChangeProposal, ChangeTracking
33
+ from specfact_cli.utils.auth_tokens import get_token
33
34
 
34
35
 
35
36
  console = Console()
@@ -96,23 +97,38 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin):
96
97
  Args:
97
98
  repo_owner: GitHub repository owner (optional, can be auto-detected)
98
99
  repo_name: GitHub repository name (optional, can be auto-detected)
99
- api_token: GitHub API token (optional, uses GITHUB_TOKEN env var or gh CLI)
100
+ api_token: GitHub API token (optional, uses GITHUB_TOKEN env var, stored auth token, or gh CLI)
100
101
  use_gh_cli: If True, try to get token from GitHub CLI (`gh auth token`)
101
102
  """
102
103
  self.repo_owner = repo_owner
103
104
  self.repo_name = repo_name
104
105
 
105
- # Token resolution order: explicit token > env var > gh CLI (if enabled)
106
+ stored_token = get_token("github")
107
+
108
+ # Token resolution order: explicit token > env var > stored token > gh CLI (if enabled)
109
+ token_source = "none"
106
110
  if api_token:
107
111
  self.api_token = api_token
112
+ token_source = "explicit"
108
113
  elif os.environ.get("GITHUB_TOKEN"):
109
114
  self.api_token = os.environ.get("GITHUB_TOKEN")
115
+ token_source = "env"
116
+ elif stored_token:
117
+ self.api_token = stored_token.get("access_token")
118
+ token_source = "stored"
110
119
  elif use_gh_cli:
111
120
  self.api_token = _get_github_token_from_gh_cli()
121
+ if self.api_token:
122
+ token_source = "gh_cli"
112
123
  else:
113
124
  self.api_token = None
114
125
 
115
- self.base_url = "https://api.github.com"
126
+ env_api_url = os.environ.get("GITHUB_API_URL")
127
+ stored_api_url = stored_token.get("api_base_url") if stored_token else None
128
+ if token_source == "stored":
129
+ self.base_url = stored_api_url or env_api_url or "https://api.github.com"
130
+ else:
131
+ self.base_url = env_api_url or stored_api_url or "https://api.github.com"
116
132
 
117
133
  # BacklogAdapterMixin abstract method implementations
118
134
 
@@ -225,12 +241,13 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin):
225
241
  body = item_data.get("body", "") or ""
226
242
  description = ""
227
243
  rationale = ""
244
+ impact = ""
228
245
 
229
246
  # Parse markdown sections (Why, What Changes)
230
247
  if body:
231
248
  # Extract "Why" section (stop at What Changes or OpenSpec footer)
232
249
  why_match = re.search(
233
- r"##\s+Why\s*\n(.*?)(?=\n##\s+What\s+Changes\s|\n---\s*\n\*OpenSpec Change Proposal:|\Z)",
250
+ r"##\s+Why\s*\n(.*?)(?=\n##\s+What\s+Changes\s|\n##\s+Impact\s|\n---\s*\n\*OpenSpec Change Proposal:|\Z)",
234
251
  body,
235
252
  re.DOTALL | re.IGNORECASE,
236
253
  )
@@ -239,7 +256,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin):
239
256
 
240
257
  # Extract "What Changes" section (stop at OpenSpec footer)
241
258
  what_match = re.search(
242
- r"##\s+What\s+Changes\s*\n(.*?)(?=\n---\s*\n\*OpenSpec Change Proposal:|\Z)",
259
+ r"##\s+What\s+Changes\s*\n(.*?)(?=\n##\s+Impact\s|\n---\s*\n\*OpenSpec Change Proposal:|\Z)",
243
260
  body,
244
261
  re.DOTALL | re.IGNORECASE,
245
262
  )
@@ -250,6 +267,14 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin):
250
267
  body_clean = re.sub(r"\n---\s*\n\*OpenSpec Change Proposal:.*", "", body, flags=re.DOTALL)
251
268
  description = body_clean.strip()
252
269
 
270
+ impact_match = re.search(
271
+ r"##\s+Impact\s*\n(.*?)(?=\n---\s*\n\*OpenSpec Change Proposal:|\Z)",
272
+ body,
273
+ re.DOTALL | re.IGNORECASE,
274
+ )
275
+ if impact_match:
276
+ impact = impact_match.group(1).strip()
277
+
253
278
  # Extract change ID from OpenSpec metadata footer or issue number
254
279
  change_id = None
255
280
  if body:
@@ -330,6 +355,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin):
330
355
  "title": title,
331
356
  "description": description,
332
357
  "rationale": rationale,
358
+ "impact": impact,
333
359
  "status": status,
334
360
  "created_at": created_at,
335
361
  "timeline": timeline,
@@ -529,7 +555,8 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin):
529
555
  " 1. Set GITHUB_TOKEN environment variable\n"
530
556
  " 2. Provide via --github-token option\n"
531
557
  " 3. Use GitHub CLI: `gh auth login` (auto-detected if available)\n"
532
- " 4. Use --use-gh-cli flag to explicitly use GitHub CLI token"
558
+ " 4. Use --use-gh-cli flag to explicitly use GitHub CLI token\n"
559
+ " 5. Run `specfact auth github` for device code authentication"
533
560
  )
534
561
  raise ValueError(msg)
535
562
 
@@ -922,6 +949,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin):
922
949
  title = proposal_data.get("title", "Untitled Change Proposal")
923
950
  description = proposal_data.get("description", "")
924
951
  rationale = proposal_data.get("rationale", "")
952
+ impact = proposal_data.get("impact", "")
925
953
  status = proposal_data.get("status", "proposed")
926
954
  change_id = proposal_data.get("change_id", "unknown")
927
955
  raw_title, raw_body = self._extract_raw_fields(proposal_data)
@@ -959,14 +987,24 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin):
959
987
  body_parts.append(line)
960
988
  body_parts.append("") # Blank line
961
989
 
990
+ # Add Impact section if present
991
+ if impact:
992
+ body_parts.append("## Impact")
993
+ body_parts.append("")
994
+ impact_lines = impact.strip().split("\n")
995
+ for line in impact_lines:
996
+ body_parts.append(line)
997
+ body_parts.append("")
998
+
962
999
  # If no content, add placeholder
963
1000
  if not body_parts or (not rationale and not description):
964
1001
  body_parts.append("No description provided.")
965
1002
  body_parts.append("")
966
1003
 
967
- # Add OpenSpec metadata footer
968
- body_parts.append("---")
969
- body_parts.append(f"*OpenSpec Change Proposal: `{change_id}`*")
1004
+ # Add OpenSpec metadata footer (avoid duplicates)
1005
+ if not any("OpenSpec Change Proposal:" in line for line in body_parts):
1006
+ body_parts.append("---")
1007
+ body_parts.append(f"*OpenSpec Change Proposal: `{change_id}`*")
970
1008
 
971
1009
  body = "\n".join(body_parts)
972
1010
 
@@ -1172,6 +1210,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin):
1172
1210
  title = proposal_data.get("title", "Untitled Change Proposal")
1173
1211
  description = proposal_data.get("description", "")
1174
1212
  rationale = proposal_data.get("rationale", "")
1213
+ impact = proposal_data.get("impact", "")
1175
1214
  change_id = proposal_data.get("change_id", "unknown")
1176
1215
  status = proposal_data.get("status", "proposed")
1177
1216
  raw_title, raw_body = self._extract_raw_fields(proposal_data)
@@ -1238,6 +1277,15 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin):
1238
1277
  body_parts.append(line)
1239
1278
  body_parts.append("") # Blank line
1240
1279
 
1280
+ # Add Impact section if present
1281
+ if impact:
1282
+ body_parts.append("## Impact")
1283
+ body_parts.append("")
1284
+ impact_lines = impact.strip().split("\n")
1285
+ for line in impact_lines:
1286
+ body_parts.append(line)
1287
+ body_parts.append("") # Blank line
1288
+
1241
1289
  # If no content, add placeholder
1242
1290
  if not body_parts or (not rationale and not description):
1243
1291
  body_parts.append("No description provided.")
@@ -1252,9 +1300,10 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin):
1252
1300
  body_parts.append("") # Blank line before preserved section
1253
1301
  body_parts.append(preserved_clean)
1254
1302
 
1255
- # Add OpenSpec metadata footer
1256
- body_parts.append("---")
1257
- body_parts.append(f"*OpenSpec Change Proposal: `{change_id}`*")
1303
+ # Add OpenSpec metadata footer (avoid duplicates)
1304
+ if not any("OpenSpec Change Proposal:" in line for line in body_parts):
1305
+ body_parts.append("---")
1306
+ body_parts.append(f"*OpenSpec Change Proposal: `{change_id}`*")
1258
1307
 
1259
1308
  body = "\n".join(body_parts)
1260
1309
 
@@ -400,6 +400,7 @@ Dependencies: {len(dependencies)} dependency files found
400
400
 
401
401
 
402
402
  # CrossHair property-based test functions
403
+ # CrossHair: skip (side-effectful imports via GitPython)
403
404
  # These functions are designed for CrossHair symbolic execution analysis
404
405
  @beartype
405
406
  def test_generate_prompt_property(command: str, context: dict[str, Any] | None) -> None:
@@ -55,6 +55,7 @@ from specfact_cli import __version__, runtime
55
55
  # Import command modules
56
56
  from specfact_cli.commands import (
57
57
  analyze,
58
+ auth,
58
59
  contract_cmd,
59
60
  drift,
60
61
  enforce,
@@ -303,11 +304,14 @@ def main(
303
304
  # 1. Setup & Initialization
304
305
  app.add_typer(init.app, name="init", help="Initialize SpecFact for IDE integration")
305
306
 
307
+ # 1.5. Authentication
308
+ app.add_typer(auth.app, name="auth", help="Authenticate with DevOps providers (GitHub, Azure DevOps)")
309
+
306
310
  # 2. Import & Analysis
307
311
  app.add_typer(
308
312
  import_cmd.app,
309
313
  name="import",
310
- help="Import codebases and external tool projects (e.g., Spec-Kit, OpenSpec, GitHub, ADO, Linear, Jira)",
314
+ help="Import codebases and external tool projects (e.g., Spec-Kit, OpenSpec, generic-markdown)",
311
315
  )
312
316
 
313
317
  # 2.5. Migration
@@ -6,6 +6,7 @@ This package contains all CLI command implementations.
6
6
 
7
7
  from specfact_cli.commands import (
8
8
  analyze,
9
+ auth,
9
10
  contract_cmd,
10
11
  drift,
11
12
  enforce,
@@ -25,6 +26,7 @@ from specfact_cli.commands import (
25
26
 
26
27
  __all__ = [
27
28
  "analyze",
29
+ "auth",
28
30
  "contract_cmd",
29
31
  "drift",
30
32
  "enforce",