specfact-cli 0.26.10__tar.gz → 0.26.13__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 (248) hide show
  1. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/PKG-INFO +2 -1
  2. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/README.md +1 -0
  3. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/pyproject.toml +1 -1
  4. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/__init__.py +1 -1
  5. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/__init__.py +1 -1
  6. specfact_cli-0.26.13/src/specfact_cli/__main__.py +6 -0
  7. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/ado.py +114 -83
  8. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/github.py +8 -0
  9. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/cli.py +4 -2
  10. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/analyze.py +28 -0
  11. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/auth.py +57 -1
  12. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/backlog_commands.py +161 -4
  13. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/drift.py +26 -0
  14. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/enforce.py +71 -0
  15. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/generate.py +142 -1
  16. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/import_cmd.py +64 -1
  17. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/init.py +10 -1
  18. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/migrate.py +69 -0
  19. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/plan.py +34 -0
  20. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/project_cmd.py +50 -0
  21. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/repro.py +34 -0
  22. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/sdd.py +11 -1
  23. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/spec.py +35 -0
  24. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/sync.py +41 -1
  25. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/update.py +37 -0
  26. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/validate.py +42 -1
  27. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/common/logger_setup.py +68 -0
  28. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/runtime.py +126 -4
  29. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/.gitignore +0 -0
  30. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/LICENSE.md +0 -0
  31. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/mappings/node-async.yaml +0 -0
  32. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/mappings/python-async.yaml +0 -0
  33. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/mappings/speckit-default.yaml +0 -0
  34. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/shared/cli-enforcement.md +0 -0
  35. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.01-import.md +0 -0
  36. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.02-plan.md +0 -0
  37. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.03-review.md +0 -0
  38. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.04-sdd.md +0 -0
  39. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.05-enforce.md +0 -0
  40. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.06-sync.md +0 -0
  41. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.07-contracts.md +0 -0
  42. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.backlog-refine.md +0 -0
  43. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.compare.md +0 -0
  44. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.sync-backlog.md +0 -0
  45. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.validate.md +0 -0
  46. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/schemas/deviation.schema.json +0 -0
  47. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/schemas/plan.schema.json +0 -0
  48. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/schemas/protocol.schema.json +0 -0
  49. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/defaults/defect_v1.yaml +0 -0
  50. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/defaults/enabler_v1.yaml +0 -0
  51. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/defaults/spike_v1.yaml +0 -0
  52. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/defaults/user_story_v1.yaml +0 -0
  53. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/field_mappings/ado_agile.yaml +0 -0
  54. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/field_mappings/ado_default.yaml +0 -0
  55. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/field_mappings/ado_kanban.yaml +0 -0
  56. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/field_mappings/ado_safe.yaml +0 -0
  57. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/field_mappings/ado_scrum.yaml +0 -0
  58. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/frameworks/safe/safe_feature_v1.yaml +0 -0
  59. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/frameworks/scrum/user_story_v1.yaml +0 -0
  60. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/personas/developer/developer_task_v1.yaml +0 -0
  61. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/personas/product-owner/user_story_v1.yaml +0 -0
  62. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/providers/ado/work_item_v1.yaml +0 -0
  63. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/github-action.yml.j2 +0 -0
  64. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/persona/architect.md.j2 +0 -0
  65. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/persona/developer.md.j2 +0 -0
  66. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/persona/product-owner.md.j2 +0 -0
  67. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/plan.bundle.yaml.j2 +0 -0
  68. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/pr-template.md.j2 +0 -0
  69. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/protocol.yaml.j2 +0 -0
  70. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/telemetry.yaml.example +0 -0
  71. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/__init__.py +0 -0
  72. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/backlog_base.py +0 -0
  73. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/base.py +0 -0
  74. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/openspec.py +0 -0
  75. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/openspec_parser.py +0 -0
  76. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/registry.py +0 -0
  77. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/speckit.py +0 -0
  78. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/agents/__init__.py +0 -0
  79. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/agents/analyze_agent.py +0 -0
  80. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/agents/base.py +0 -0
  81. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/agents/plan_agent.py +0 -0
  82. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/agents/registry.py +0 -0
  83. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/agents/sync_agent.py +0 -0
  84. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/__init__.py +0 -0
  85. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
  86. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
  87. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
  88. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
  89. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
  90. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
  91. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
  92. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
  93. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
  94. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/__init__.py +0 -0
  95. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
  96. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/adapters/base.py +0 -0
  97. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/adapters/local_yaml_adapter.py +0 -0
  98. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/ai_refiner.py +0 -0
  99. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/converter.py +0 -0
  100. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/filters.py +0 -0
  101. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/format_detector.py +0 -0
  102. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/formats/__init__.py +0 -0
  103. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/formats/base.py +0 -0
  104. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/formats/markdown_format.py +0 -0
  105. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/formats/structured_format.py +0 -0
  106. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
  107. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
  108. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/mappers/base.py +0 -0
  109. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
  110. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
  111. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/template_detector.py +0 -0
  112. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/__init__.py +0 -0
  113. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/contract_cmd.py +0 -0
  114. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/common/__init__.py +0 -0
  115. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/common/logging_utils.py +0 -0
  116. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/common/text_utils.py +0 -0
  117. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/common/utils.py +0 -0
  118. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/comparators/__init__.py +0 -0
  119. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/comparators/plan_comparator.py +0 -0
  120. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/contracts/__init__.py +0 -0
  121. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/contracts/crosshair_props.py +0 -0
  122. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
  123. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
  124. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/__init__.py +0 -0
  125. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/contract_generator.py +0 -0
  126. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/openapi_extractor.py +0 -0
  127. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/persona_exporter.py +0 -0
  128. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/plan_generator.py +0 -0
  129. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/protocol_generator.py +0 -0
  130. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/report_generator.py +0 -0
  131. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/task_generator.py +0 -0
  132. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/test_to_openapi.py +0 -0
  133. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/workflow_generator.py +0 -0
  134. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/importers/__init__.py +0 -0
  135. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/importers/speckit_converter.py +0 -0
  136. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/importers/speckit_scanner.py +0 -0
  137. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/integrations/__init__.py +0 -0
  138. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/integrations/specmatic.py +0 -0
  139. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/merge/__init__.py +0 -0
  140. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/merge/resolver.py +0 -0
  141. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/migrations/__init__.py +0 -0
  142. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/migrations/plan_migrator.py +0 -0
  143. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/__init__.py +0 -0
  144. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/backlog_item.py +0 -0
  145. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/bridge.py +0 -0
  146. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/capabilities.py +0 -0
  147. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/change.py +0 -0
  148. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/contract.py +0 -0
  149. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/deviation.py +0 -0
  150. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/dor_config.py +0 -0
  151. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/enforcement.py +0 -0
  152. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/persona_template.py +0 -0
  153. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/plan.py +0 -0
  154. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/project.py +0 -0
  155. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/protocol.py +0 -0
  156. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/quality.py +0 -0
  157. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/sdd.py +0 -0
  158. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/source_tracking.py +0 -0
  159. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/task.py +0 -0
  160. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/modes/__init__.py +0 -0
  161. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/modes/detector.py +0 -0
  162. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/modes/router.py +0 -0
  163. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/parsers/__init__.py +0 -0
  164. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/parsers/persona_importer.py +0 -0
  165. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/resources/semgrep/async.yml +0 -0
  166. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
  167. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
  168. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/__init__.py +0 -0
  169. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/bridge_probe.py +0 -0
  170. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/bridge_sync.py +0 -0
  171. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/bridge_watch.py +0 -0
  172. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/change_detector.py +0 -0
  173. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/code_to_spec.py +0 -0
  174. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/drift_detector.py +0 -0
  175. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/repository_sync.py +0 -0
  176. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/spec_to_code.py +0 -0
  177. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/spec_to_tests.py +0 -0
  178. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/watcher.py +0 -0
  179. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
  180. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/telemetry.py +0 -0
  181. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/__init__.py +0 -0
  182. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/bridge_templates.py +0 -0
  183. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
  184. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
  185. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
  186. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
  187. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
  188. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
  189. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
  190. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/registry.py +0 -0
  191. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/specification_templates.py +0 -0
  192. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/__init__.py +0 -0
  193. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
  194. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/auth_tokens.py +0 -0
  195. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/bundle_loader.py +0 -0
  196. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/code_change_detector.py +0 -0
  197. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/console.py +0 -0
  198. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/content_sanitizer.py +0 -0
  199. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/context_detection.py +0 -0
  200. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/enrichment_context.py +0 -0
  201. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/enrichment_parser.py +0 -0
  202. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/env_manager.py +0 -0
  203. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/feature_keys.py +0 -0
  204. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/git.py +0 -0
  205. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/github_annotations.py +0 -0
  206. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/ide_setup.py +0 -0
  207. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/incremental_check.py +0 -0
  208. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/metadata.py +0 -0
  209. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/optional_deps.py +0 -0
  210. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/performance.py +0 -0
  211. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/progress.py +0 -0
  212. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
  213. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/prompts.py +0 -0
  214. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/sdd_discovery.py +0 -0
  215. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/source_scanner.py +0 -0
  216. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/startup_checks.py +0 -0
  217. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/structure.py +0 -0
  218. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/structured_io.py +0 -0
  219. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/suggestions.py +0 -0
  220. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/terminal.py +0 -0
  221. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/yaml_utils.py +0 -0
  222. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/__init__.py +0 -0
  223. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/agile_validation.py +0 -0
  224. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
  225. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/cli_first_validator.py +0 -0
  226. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/contract_validator.py +0 -0
  227. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/fsm.py +0 -0
  228. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/repro_checker.py +0 -0
  229. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/schema.py +0 -0
  230. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
  231. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
  232. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
  233. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
  234. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
  235. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
  236. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
  237. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
  238. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
  239. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
  240. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
  241. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
  242. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
  243. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/models.py +0 -0
  244. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
  245. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
  246. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
  247. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/versioning/__init__.py +0 -0
  248. {specfact_cli-0.26.10 → specfact_cli-0.26.13}/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.26.10
3
+ Version: 0.26.13
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
@@ -617,6 +617,7 @@ hatch run contract-test-full
617
617
 
618
618
  - 💬 **Questions?** [GitHub Discussions](https://github.com/nold-ai/specfact-cli/discussions)
619
619
  - 🐛 **Found a bug?** [GitHub Issues](https://github.com/nold-ai/specfact-cli/issues)
620
+ - 🔍 **Debugging I/O or API issues?** Run with `--debug`; logs are written to `~/.specfact/logs/specfact-debug.log`. See [Debug Logging](docs/reference/debug-logging.md).
620
621
  - 📧 **Need help?** [hello@noldai.com](mailto:hello@noldai.com)
621
622
  - 🌐 **Learn more:** [specfact.com](https://specfact.com) • [specfact.io](https://specfact.io) • [specfact.dev](https://specfact.dev)
622
623
 
@@ -339,6 +339,7 @@ hatch run contract-test-full
339
339
 
340
340
  - 💬 **Questions?** [GitHub Discussions](https://github.com/nold-ai/specfact-cli/discussions)
341
341
  - 🐛 **Found a bug?** [GitHub Issues](https://github.com/nold-ai/specfact-cli/issues)
342
+ - 🔍 **Debugging I/O or API issues?** Run with `--debug`; logs are written to `~/.specfact/logs/specfact-debug.log`. See [Debug Logging](docs/reference/debug-logging.md).
342
343
  - 📧 **Need help?** [hello@noldai.com](mailto:hello@noldai.com)
343
344
  - 🌐 **Learn more:** [specfact.com](https://specfact.com) • [specfact.io](https://specfact.io) • [specfact.dev](https://specfact.dev)
344
345
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "specfact-cli"
7
- version = "0.26.10"
7
+ version = "0.26.13"
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
  # Package version: keep in sync with pyproject.toml, setup.py, src/specfact_cli/__init__.py
6
- __version__ = "0.26.10"
6
+ __version__ = "0.26.13"
@@ -9,6 +9,6 @@ This package provides command-line tools for:
9
9
  - Validating reproducibility
10
10
  """
11
11
 
12
- __version__ = "0.26.10"
12
+ __version__ = "0.26.13"
13
13
 
14
14
  __all__ = ["__version__"]
@@ -0,0 +1,6 @@
1
+ """Allow running the CLI as python -m specfact_cli."""
2
+
3
+ if __name__ == "__main__":
4
+ from specfact_cli.cli import cli_main
5
+
6
+ cli_main()
@@ -11,7 +11,8 @@ This follows the backlog adapter patterns established by the GitHub adapter.
11
11
  from __future__ import annotations
12
12
 
13
13
  import os
14
- import re
14
+
15
+ # import re
15
16
  from datetime import UTC, datetime
16
17
  from pathlib import Path
17
18
  from typing import Any
@@ -31,7 +32,7 @@ from specfact_cli.models.backlog_item import BacklogItem
31
32
  from specfact_cli.models.bridge import BridgeConfig
32
33
  from specfact_cli.models.capabilities import ToolCapabilities
33
34
  from specfact_cli.models.change import ChangeProposal, ChangeTracking
34
- from specfact_cli.runtime import debug_print
35
+ from specfact_cli.runtime import debug_log_operation, debug_print, is_debug_mode
35
36
  from specfact_cli.utils.auth_tokens import get_token, set_token
36
37
 
37
38
 
@@ -372,6 +373,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
372
373
  rationale = ""
373
374
  impact = ""
374
375
 
376
+ import re
377
+
375
378
  # Parse markdown sections (Why, What Changes)
376
379
  if description_raw:
377
380
  # Extract "Why" section (stop at What Changes or OpenSpec footer)
@@ -696,6 +699,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
696
699
  ValueError: If required configuration is missing
697
700
  requests.RequestException: If Azure DevOps API call fails
698
701
  """
702
+ import re as _re
703
+
699
704
  if not self.api_token:
700
705
  msg = (
701
706
  "Azure DevOps API token required. Options:\n"
@@ -751,7 +756,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
751
756
  parsed = urlparse(source_url)
752
757
  if parsed.hostname and parsed.hostname.lower() == "dev.azure.com":
753
758
  target_org = target_repo.split("/")[0]
754
- ado_org_match = re.search(r"dev\.azure\.com/([^/]+)/", source_url)
759
+ ado_org_match = _re.search(r"dev\.azure\.com/([^/]+)/", source_url)
755
760
  if ado_org_match and ado_org_match.group(1) == target_org:
756
761
  # Org matches - this is likely the same ADO organization
757
762
  work_item_id = entry.get("source_id")
@@ -773,8 +778,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
773
778
  # 3. AND (project is unknown in entry OR project is unknown in target OR both contain GUIDs)
774
779
  # This prevents matching org/project-a with org/project-b when both have known project names
775
780
  source_url = entry.get("source_url", "")
776
- entry_has_guid = source_url and re.search(
777
- r"dev\.azure\.com/[^/]+/[0-9a-f-]{36}", source_url, re.IGNORECASE
781
+ entry_has_guid = source_url and _re.search(
782
+ r"dev\.azure\.com/[^/]+/[0-9a-f-]{36}", source_url, _re.IGNORECASE
778
783
  )
779
784
  project_unknown = (
780
785
  not entry_project # Entry has no project part
@@ -967,8 +972,10 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
967
972
  Returns:
968
973
  Tuple of (org, project, work_item_id)
969
974
  """
975
+ import re as _re
976
+
970
977
  cleaned = item_ref.strip().lstrip("#")
971
- url_match = re.search(r"dev\.azure\.com/([^/]+)/([^/]+)/.*?/(\d+)", cleaned, re.IGNORECASE)
978
+ url_match = _re.search(r"dev\.azure\.com/([^/]+)/([^/]+)/.*?/(\d+)", cleaned, _re.IGNORECASE)
972
979
  if url_match:
973
980
  return url_match.group(1), url_match.group(2), int(url_match.group(3))
974
981
 
@@ -1439,6 +1446,13 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
1439
1446
 
1440
1447
  try:
1441
1448
  response = requests.post(url, json=wiql, headers=headers, timeout=10)
1449
+ if is_debug_mode():
1450
+ debug_log_operation(
1451
+ "ado_wiql",
1452
+ url,
1453
+ str(response.status_code),
1454
+ error=None if response.ok else (response.text[:200] if response.text else None),
1455
+ )
1442
1456
  if response.status_code != 200:
1443
1457
  return None
1444
1458
  work_items = response.json().get("workItems", [])
@@ -1453,7 +1467,9 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
1453
1467
  "source_type": "ado",
1454
1468
  "source_repo": f"{org}/{project}",
1455
1469
  }
1456
- except requests.RequestException:
1470
+ except requests.RequestException as e:
1471
+ if is_debug_mode():
1472
+ debug_log_operation("ado_wiql", url, "error", error=str(e))
1457
1473
  return None
1458
1474
 
1459
1475
  def _create_work_item_from_proposal(
@@ -1473,6 +1489,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
1473
1489
  Returns:
1474
1490
  Dict with work item data: {"work_item_id": int, "work_item_url": str, "state": str}
1475
1491
  """
1492
+ import re as _re
1493
+
1476
1494
  title = proposal_data.get("title", "Untitled Change Proposal")
1477
1495
  description = proposal_data.get("description", "")
1478
1496
  rationale = proposal_data.get("rationale", "")
@@ -1489,7 +1507,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
1489
1507
  else:
1490
1508
  body_parts = []
1491
1509
 
1492
- display_title = re.sub(r"^\[change\]\s*", "", title, flags=re.IGNORECASE).strip()
1510
+ display_title = _re.sub(r"^\[change\]\s*", "", title, flags=_re.IGNORECASE).strip()
1493
1511
  if display_title:
1494
1512
  body_parts.append(f"# {display_title}")
1495
1513
  body_parts.append("")
@@ -1572,6 +1590,13 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
1572
1590
 
1573
1591
  try:
1574
1592
  response = requests.patch(url, json=patch_document, headers=headers, timeout=30)
1593
+ if is_debug_mode():
1594
+ debug_log_operation(
1595
+ "ado_patch",
1596
+ url,
1597
+ str(response.status_code),
1598
+ error=None if response.ok else (response.text[:200] if response.text else None),
1599
+ )
1575
1600
  response.raise_for_status()
1576
1601
  work_item_data = response.json()
1577
1602
 
@@ -1617,6 +1642,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
1617
1642
  "state": ado_state,
1618
1643
  }
1619
1644
  except requests.RequestException as e:
1645
+ if is_debug_mode():
1646
+ debug_log_operation("ado_patch", url, "error", error=str(e))
1620
1647
  msg = f"Failed to create Azure DevOps work item: {e}"
1621
1648
  console.print(f"[bold red]✗[/bold red] {msg}")
1622
1649
  raise
@@ -1740,6 +1767,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
1740
1767
  Returns:
1741
1768
  Dict with updated work item data: {"work_item_id": int, "work_item_url": str, "state": str}
1742
1769
  """
1770
+ import re as _re
1771
+
1743
1772
  title = proposal_data.get("title", "Untitled Change Proposal")
1744
1773
  description = proposal_data.get("description", "")
1745
1774
  rationale = proposal_data.get("rationale", "")
@@ -1756,7 +1785,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
1756
1785
  else:
1757
1786
  body_parts = []
1758
1787
 
1759
- display_title = re.sub(r"^\[change\]\s*", "", title, flags=re.IGNORECASE).strip()
1788
+ display_title = _re.sub(r"^\[change\]\s*", "", title, flags=_re.IGNORECASE).strip()
1760
1789
  if display_title:
1761
1790
  body_parts.append(f"# {display_title}")
1762
1791
  body_parts.append("")
@@ -2908,8 +2937,22 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
2908
2937
 
2909
2938
  try:
2910
2939
  response = requests.get(url, headers=workitems_headers, params=params, timeout=30)
2940
+ if is_debug_mode():
2941
+ debug_log_operation(
2942
+ "ado_workitems_get",
2943
+ url,
2944
+ str(response.status_code),
2945
+ error=None if response.ok else (response.text[:200] if response.text else None),
2946
+ )
2911
2947
  response.raise_for_status()
2912
2948
  except requests.HTTPError as e:
2949
+ if is_debug_mode():
2950
+ debug_log_operation(
2951
+ "ado_workitems_get",
2952
+ url,
2953
+ "error",
2954
+ error=str(e.response.status_code) if e.response is not None else str(e),
2955
+ )
2913
2956
  # Provide better error message with URL details
2914
2957
  error_detail = ""
2915
2958
  if e.response is not None:
@@ -3095,11 +3138,12 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
3095
3138
 
3096
3139
  # Update description (body_markdown) - always use System.Description
3097
3140
  if update_fields is None or "body" in update_fields or "body_markdown" in update_fields:
3098
- # Convert TODO markers to proper Markdown checkboxes for ADO rendering
3099
3141
  import re
3100
3142
 
3101
- markdown_content = item.body_markdown
3102
- # Pattern matches: * [TODO: ...] or - [TODO: ...] or *[TODO: ...] or -[TODO: ...]
3143
+ # Never send null: ADO rejects null for /fields/System.Description (HTTP 400)
3144
+ raw_body = item.body_markdown
3145
+ markdown_content = raw_body if raw_body is not None else ""
3146
+ # Convert TODO markers to proper Markdown checkboxes for ADO rendering
3103
3147
  todo_pattern = r"^(\s*)[-*]\s*\[TODO[:\s]+([^\]]+)\](.*)$"
3104
3148
  markdown_content = re.sub(
3105
3149
  todo_pattern,
@@ -3108,11 +3152,9 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
3108
3152
  flags=re.MULTILINE | re.IGNORECASE,
3109
3153
  )
3110
3154
 
3111
- # Get mapped description field name (honors custom mappings)
3112
3155
  description_field = reverse_mappings.get("description", "System.Description")
3113
- # Set multiline field format to Markdown FIRST (before setting content)
3156
+ # Set multiline field format to Markdown first (optional; many ADO instances return 400 for this path)
3114
3157
  operations.append({"op": "add", "path": f"/multilineFieldsFormat/{description_field}", "value": "Markdown"})
3115
- # Then set description content with Markdown format
3116
3158
  operations.append({"op": "replace", "path": f"/fields/{description_field}", "value": markdown_content})
3117
3159
 
3118
3160
  # Update acceptance criteria using mapped field name (honors custom mappings)
@@ -3158,7 +3200,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
3158
3200
  response = requests.patch(url, headers=headers, json=operations, timeout=30)
3159
3201
  response.raise_for_status()
3160
3202
  except requests.HTTPError as e:
3161
- # Handle various error cases
3203
+ # Handle 400/422: often caused by /multilineFieldsFormat/ not being supported by ADO API
3204
+ response = None
3162
3205
  if e.response and e.response.status_code in (400, 422):
3163
3206
  error_message = ""
3164
3207
  try:
@@ -3167,91 +3210,79 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
3167
3210
  except Exception:
3168
3211
  pass
3169
3212
 
3170
- # Check if error is about multilineFieldsFormat already existing (use "replace" instead)
3171
- if "already exists" in error_message.lower() or "cannot add" in error_message.lower():
3172
- # Try with "replace" operation for multilineFieldsFormat
3213
+ # First retry: omit multilineFieldsFormat entirely (only /fields/ updates).
3214
+ # Many ADO instances reject /multilineFieldsFormat/ path with 400 Bad Request.
3215
+ operations_no_format = [
3216
+ op for op in operations if not (op.get("path") or "").startswith("/multilineFieldsFormat/")
3217
+ ]
3218
+ if operations_no_format != operations:
3219
+ try:
3220
+ resp = requests.patch(url, headers=headers, json=operations_no_format, timeout=30)
3221
+ resp.raise_for_status()
3222
+ response = resp
3223
+ except requests.HTTPError as retry_error:
3224
+ # Retry with operations_no_format failed; continue to next fallback strategy.
3225
+ if is_debug_mode():
3226
+ debug_log_operation(
3227
+ "ado_patch",
3228
+ url,
3229
+ "failed",
3230
+ error=str(retry_error),
3231
+ )
3232
+
3233
+ if response is None and (
3234
+ "already exists" in error_message.lower() or "cannot add" in error_message.lower()
3235
+ ):
3236
+ # Second: try "replace" instead of "add" for multilineFieldsFormat
3173
3237
  operations_replace = []
3174
3238
  for op in operations:
3175
- if op.get("path") == "/multilineFieldsFormat/System.Description":
3176
- # Change to replace operation
3177
- operations_replace.append({"op": "replace", "path": op["path"], "value": op["value"]})
3239
+ path = op.get("path") or ""
3240
+ if path.startswith("/multilineFieldsFormat/"):
3241
+ operations_replace.append({"op": "replace", "path": path, "value": op["value"]})
3178
3242
  else:
3179
3243
  operations_replace.append(op)
3180
-
3181
3244
  try:
3182
- response = requests.patch(url, headers=headers, json=operations_replace, timeout=30)
3183
- response.raise_for_status()
3245
+ resp = requests.patch(url, headers=headers, json=operations_replace, timeout=30)
3246
+ resp.raise_for_status()
3247
+ response = resp
3184
3248
  except requests.HTTPError:
3185
- # If replace also fails, fallback to HTML conversion
3186
- console.print("[yellow]⚠ Markdown format not supported, converting to HTML[/yellow]")
3187
- operations_html = [
3188
- op for op in operations if "/multilineFieldsFormat/" not in op.get("path", "")
3189
- ]
3190
- # Find description operation and convert markdown to HTML
3191
- for op in operations_html:
3192
- if op.get("path") == "/fields/System.Description":
3193
- # Convert TODO markers to HTML checkboxes before converting to HTML
3194
- import re
3195
-
3196
- markdown_for_html = op["value"]
3197
- # Convert TODO markers to checkboxes first
3198
- todo_pattern = r"^(\s*)[-*]\s*\[TODO[:\s]+([^\]]+)\](.*)$"
3199
- markdown_for_html = re.sub(
3200
- todo_pattern,
3201
- r"\1- [ ] \2",
3202
- markdown_for_html,
3203
- flags=re.MULTILINE | re.IGNORECASE,
3204
- )
3205
- # Simple markdown to HTML conversion (basic)
3206
- try:
3207
- import markdown
3208
-
3209
- html_body = markdown.markdown(
3210
- markdown_for_html, extensions=["fenced_code", "tables"]
3211
- )
3212
- op["value"] = html_body
3213
- except ImportError:
3214
- # markdown library not available - use raw text
3215
- console.print("[yellow]⚠ markdown library not available, using raw text[/yellow]")
3216
- # Keep original markdown as-is (ADO may still render it)
3217
- break
3218
-
3219
- response = requests.patch(url, headers=headers, json=operations_html, timeout=30)
3220
- response.raise_for_status()
3221
- else:
3222
- # Other 400/422 errors - try HTML fallback
3223
- console.print("[yellow]⚠ Markdown format not supported, converting to HTML[/yellow]")
3224
- operations_html = [op for op in operations if "/multilineFieldsFormat/" not in op.get("path", "")]
3225
- # Find description operation and convert markdown to HTML
3249
+ pass
3250
+
3251
+ if response is None:
3252
+ # Third: HTML fallback (no multilineFieldsFormat, description as HTML)
3253
+ import re as _re
3254
+
3255
+ console.print("[yellow]⚠ Markdown format not supported, converting description to HTML[/yellow]")
3256
+ operations_html = [
3257
+ op for op in operations if not (op.get("path") or "").startswith("/multilineFieldsFormat/")
3258
+ ]
3259
+ description_field = reverse_mappings.get("description", "System.Description")
3260
+ desc_path = f"/fields/{description_field}"
3226
3261
  for op in operations_html:
3227
- if op.get("path") == "/fields/System.Description":
3228
- # Convert TODO markers to HTML checkboxes before converting to HTML
3229
- import re
3230
-
3231
- markdown_for_html = op["value"]
3232
- # Convert TODO markers to checkboxes first
3262
+ if op.get("path") == desc_path:
3263
+ markdown_for_html = op.get("value") or ""
3233
3264
  todo_pattern = r"^(\s*)[-*]\s*\[TODO[:\s]+([^\]]+)\](.*)$"
3234
- markdown_for_html = re.sub(
3265
+ markdown_for_html = _re.sub(
3235
3266
  todo_pattern,
3236
3267
  r"\1- [ ] \2",
3237
3268
  markdown_for_html,
3238
- flags=re.MULTILINE | re.IGNORECASE,
3269
+ flags=_re.MULTILINE | _re.IGNORECASE,
3239
3270
  )
3240
- # Simple markdown to HTML conversion (basic)
3241
3271
  try:
3242
3272
  import markdown
3243
3273
 
3244
- html_body = markdown.markdown(markdown_for_html, extensions=["fenced_code", "tables"])
3245
- op["value"] = html_body
3274
+ op["value"] = markdown.markdown(markdown_for_html, extensions=["fenced_code", "tables"])
3246
3275
  except ImportError:
3247
- # markdown library not available - use raw text
3248
- console.print("[yellow]⚠ markdown library not available, using raw text[/yellow]")
3249
- # Keep original markdown as-is (ADO may still render it)
3276
+ pass
3250
3277
  break
3278
+ try:
3279
+ resp = requests.patch(url, headers=headers, json=operations_html, timeout=30)
3280
+ resp.raise_for_status()
3281
+ response = resp
3282
+ except requests.HTTPError:
3283
+ raise
3251
3284
 
3252
- response = requests.patch(url, headers=headers, json=operations_html, timeout=30)
3253
- response.raise_for_status()
3254
- else:
3285
+ if response is None:
3255
3286
  raise
3256
3287
 
3257
3288
  updated_work_item = response.json()
@@ -35,6 +35,7 @@ from specfact_cli.models.backlog_item import BacklogItem
35
35
  from specfact_cli.models.bridge import BridgeConfig
36
36
  from specfact_cli.models.capabilities import ToolCapabilities
37
37
  from specfact_cli.models.change import ChangeProposal, ChangeTracking
38
+ from specfact_cli.runtime import debug_log_operation, is_debug_mode
38
39
  from specfact_cli.utils.auth_tokens import get_token
39
40
 
40
41
 
@@ -817,6 +818,13 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
817
818
  "Accept": "application/vnd.github.v3+json",
818
819
  }
819
820
  response = requests.get(url, headers=headers, timeout=30)
821
+ if is_debug_mode():
822
+ debug_log_operation(
823
+ "github_api_get",
824
+ url,
825
+ str(response.status_code),
826
+ error=None if response.ok else (response.text[:200] if response.text else None),
827
+ )
820
828
  response.raise_for_status()
821
829
  return response.json()
822
830
 
@@ -75,7 +75,7 @@ from specfact_cli.commands import (
75
75
  validate,
76
76
  )
77
77
  from specfact_cli.modes import OperationalMode, detect_mode
78
- from specfact_cli.runtime import get_configured_console, set_debug_mode
78
+ from specfact_cli.runtime import get_configured_console, init_debug_log_file, set_debug_mode
79
79
  from specfact_cli.utils.progressive_disclosure import ProgressiveDisclosureGroup
80
80
  from specfact_cli.utils.structured_io import StructuredFormat
81
81
 
@@ -246,7 +246,7 @@ def main(
246
246
  debug: bool = typer.Option(
247
247
  False,
248
248
  "--debug",
249
- help="Enable debug output (shows detailed logging and diagnostic information)",
249
+ help="Enable debug output: console diagnostics and log file at ~/.specfact/logs/specfact-debug.log (operation metadata for file I/O and API calls)",
250
250
  ),
251
251
  skip_checks: bool = typer.Option(
252
252
  False,
@@ -297,6 +297,8 @@ def main(
297
297
 
298
298
  # Set debug mode
299
299
  set_debug_mode(debug)
300
+ if debug:
301
+ init_debug_log_file()
300
302
 
301
303
  runtime.configure_io_formats(input_format=input_format, output_format=output_format)
302
304
  # Invert logic: --interactive means not non-interactive, --no-interactive means non-interactive
@@ -17,6 +17,7 @@ from rich.console import Console
17
17
  from rich.table import Table
18
18
 
19
19
  from specfact_cli.models.quality import CodeQuality, QualityTracking
20
+ from specfact_cli.runtime import debug_log_operation, debug_print, is_debug_mode
20
21
  from specfact_cli.telemetry import telemetry
21
22
  from specfact_cli.utils import print_error, print_success
22
23
  from specfact_cli.utils.progress import load_bundle_with_progress
@@ -63,12 +64,23 @@ def analyze_contracts(
63
64
  **Examples:**
64
65
  specfact analyze contracts --repo . --bundle legacy-api
65
66
  """
67
+ if is_debug_mode():
68
+ debug_log_operation("command", "analyze contracts", "started", extra={"repo": str(repo), "bundle": bundle})
69
+ debug_print("[dim]analyze contracts: started[/dim]")
66
70
  console = Console()
67
71
 
68
72
  # Use active plan as default if bundle not provided
69
73
  if bundle is None:
70
74
  bundle = SpecFactStructure.get_active_bundle_name(repo)
71
75
  if bundle is None:
76
+ if is_debug_mode():
77
+ debug_log_operation(
78
+ "command",
79
+ "analyze contracts",
80
+ "failed",
81
+ error="Bundle name required",
82
+ extra={"reason": "no_bundle"},
83
+ )
72
84
  console.print("[bold red]✗[/bold red] Bundle name required")
73
85
  console.print("[yellow]→[/yellow] Use --bundle option or run 'specfact plan select' to set active plan")
74
86
  raise typer.Exit(1)
@@ -78,6 +90,14 @@ def analyze_contracts(
78
90
  bundle_dir = SpecFactStructure.project_dir(base_path=repo_path, bundle_name=bundle)
79
91
 
80
92
  if not bundle_dir.exists():
93
+ if is_debug_mode():
94
+ debug_log_operation(
95
+ "command",
96
+ "analyze contracts",
97
+ "failed",
98
+ error=f"Bundle not found: {bundle_dir}",
99
+ extra={"reason": "bundle_missing"},
100
+ )
81
101
  print_error(f"Project bundle not found: {bundle_dir}")
82
102
  raise typer.Exit(1)
83
103
 
@@ -200,6 +220,14 @@ def analyze_contracts(
200
220
  "files_with_crosshair": files_with_crosshair,
201
221
  }
202
222
  )
223
+ if is_debug_mode():
224
+ debug_log_operation(
225
+ "command",
226
+ "analyze contracts",
227
+ "success",
228
+ extra={"files_analyzed": files_analyzed, "bundle": bundle},
229
+ )
230
+ debug_print("[dim]analyze contracts: success[/dim]")
203
231
 
204
232
 
205
233
  def _analyze_file_quality(file_path: Path) -> CodeQuality: