specfact-cli 0.26.13__tar.gz → 0.26.15__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.13 → specfact_cli-0.26.15}/PKG-INFO +2 -2
  2. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/README.md +1 -1
  3. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/pyproject.toml +1 -1
  4. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.backlog-refine.md +26 -0
  5. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/__init__.py +1 -1
  6. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/__init__.py +1 -1
  7. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/ado.py +89 -23
  8. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/cli.py +1 -0
  9. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/backlog_commands.py +88 -7
  10. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/sync.py +3 -3
  11. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/bridge_sync.py +11 -6
  12. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/.gitignore +0 -0
  13. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/LICENSE.md +0 -0
  14. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/mappings/node-async.yaml +0 -0
  15. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/mappings/python-async.yaml +0 -0
  16. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/mappings/speckit-default.yaml +0 -0
  17. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/shared/cli-enforcement.md +0 -0
  18. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.01-import.md +0 -0
  19. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.02-plan.md +0 -0
  20. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.03-review.md +0 -0
  21. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.04-sdd.md +0 -0
  22. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.05-enforce.md +0 -0
  23. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.06-sync.md +0 -0
  24. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.07-contracts.md +0 -0
  25. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.compare.md +0 -0
  26. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.sync-backlog.md +0 -0
  27. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.validate.md +0 -0
  28. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/schemas/deviation.schema.json +0 -0
  29. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/schemas/plan.schema.json +0 -0
  30. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/schemas/protocol.schema.json +0 -0
  31. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/defaults/defect_v1.yaml +0 -0
  32. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/defaults/enabler_v1.yaml +0 -0
  33. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/defaults/spike_v1.yaml +0 -0
  34. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/defaults/user_story_v1.yaml +0 -0
  35. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/field_mappings/ado_agile.yaml +0 -0
  36. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/field_mappings/ado_default.yaml +0 -0
  37. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/field_mappings/ado_kanban.yaml +0 -0
  38. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/field_mappings/ado_safe.yaml +0 -0
  39. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/field_mappings/ado_scrum.yaml +0 -0
  40. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/frameworks/safe/safe_feature_v1.yaml +0 -0
  41. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/frameworks/scrum/user_story_v1.yaml +0 -0
  42. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/personas/developer/developer_task_v1.yaml +0 -0
  43. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/personas/product-owner/user_story_v1.yaml +0 -0
  44. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/providers/ado/work_item_v1.yaml +0 -0
  45. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/github-action.yml.j2 +0 -0
  46. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/persona/architect.md.j2 +0 -0
  47. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/persona/developer.md.j2 +0 -0
  48. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/persona/product-owner.md.j2 +0 -0
  49. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/plan.bundle.yaml.j2 +0 -0
  50. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/pr-template.md.j2 +0 -0
  51. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/protocol.yaml.j2 +0 -0
  52. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/telemetry.yaml.example +0 -0
  53. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/__main__.py +0 -0
  54. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/__init__.py +0 -0
  55. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/backlog_base.py +0 -0
  56. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/base.py +0 -0
  57. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/github.py +0 -0
  58. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/openspec.py +0 -0
  59. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/openspec_parser.py +0 -0
  60. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/registry.py +0 -0
  61. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/speckit.py +0 -0
  62. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/agents/__init__.py +0 -0
  63. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/agents/analyze_agent.py +0 -0
  64. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/agents/base.py +0 -0
  65. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/agents/plan_agent.py +0 -0
  66. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/agents/registry.py +0 -0
  67. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/agents/sync_agent.py +0 -0
  68. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/__init__.py +0 -0
  69. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
  70. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
  71. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
  72. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
  73. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
  74. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
  75. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
  76. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
  77. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
  78. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/__init__.py +0 -0
  79. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
  80. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/adapters/base.py +0 -0
  81. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/adapters/local_yaml_adapter.py +0 -0
  82. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/ai_refiner.py +0 -0
  83. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/converter.py +0 -0
  84. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/filters.py +0 -0
  85. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/format_detector.py +0 -0
  86. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/formats/__init__.py +0 -0
  87. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/formats/base.py +0 -0
  88. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/formats/markdown_format.py +0 -0
  89. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/formats/structured_format.py +0 -0
  90. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
  91. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
  92. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/mappers/base.py +0 -0
  93. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
  94. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
  95. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/template_detector.py +0 -0
  96. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/__init__.py +0 -0
  97. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/analyze.py +0 -0
  98. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/auth.py +0 -0
  99. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/contract_cmd.py +0 -0
  100. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/drift.py +0 -0
  101. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/enforce.py +0 -0
  102. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/generate.py +0 -0
  103. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/import_cmd.py +0 -0
  104. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/init.py +0 -0
  105. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/migrate.py +0 -0
  106. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/plan.py +0 -0
  107. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/project_cmd.py +0 -0
  108. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/repro.py +0 -0
  109. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/sdd.py +0 -0
  110. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/spec.py +0 -0
  111. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/update.py +0 -0
  112. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/validate.py +0 -0
  113. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/common/__init__.py +0 -0
  114. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/common/logger_setup.py +0 -0
  115. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/common/logging_utils.py +0 -0
  116. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/common/text_utils.py +0 -0
  117. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/common/utils.py +0 -0
  118. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/comparators/__init__.py +0 -0
  119. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/comparators/plan_comparator.py +0 -0
  120. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/contracts/__init__.py +0 -0
  121. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/contracts/crosshair_props.py +0 -0
  122. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
  123. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
  124. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/__init__.py +0 -0
  125. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/contract_generator.py +0 -0
  126. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/openapi_extractor.py +0 -0
  127. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/persona_exporter.py +0 -0
  128. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/plan_generator.py +0 -0
  129. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/protocol_generator.py +0 -0
  130. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/report_generator.py +0 -0
  131. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/task_generator.py +0 -0
  132. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/test_to_openapi.py +0 -0
  133. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/workflow_generator.py +0 -0
  134. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/importers/__init__.py +0 -0
  135. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/importers/speckit_converter.py +0 -0
  136. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/importers/speckit_scanner.py +0 -0
  137. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/integrations/__init__.py +0 -0
  138. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/integrations/specmatic.py +0 -0
  139. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/merge/__init__.py +0 -0
  140. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/merge/resolver.py +0 -0
  141. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/migrations/__init__.py +0 -0
  142. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/migrations/plan_migrator.py +0 -0
  143. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/__init__.py +0 -0
  144. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/backlog_item.py +0 -0
  145. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/bridge.py +0 -0
  146. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/capabilities.py +0 -0
  147. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/change.py +0 -0
  148. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/contract.py +0 -0
  149. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/deviation.py +0 -0
  150. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/dor_config.py +0 -0
  151. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/enforcement.py +0 -0
  152. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/persona_template.py +0 -0
  153. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/plan.py +0 -0
  154. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/project.py +0 -0
  155. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/protocol.py +0 -0
  156. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/quality.py +0 -0
  157. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/sdd.py +0 -0
  158. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/source_tracking.py +0 -0
  159. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/task.py +0 -0
  160. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/modes/__init__.py +0 -0
  161. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/modes/detector.py +0 -0
  162. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/modes/router.py +0 -0
  163. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/parsers/__init__.py +0 -0
  164. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/parsers/persona_importer.py +0 -0
  165. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/resources/semgrep/async.yml +0 -0
  166. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
  167. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
  168. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/runtime.py +0 -0
  169. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/__init__.py +0 -0
  170. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/bridge_probe.py +0 -0
  171. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/bridge_watch.py +0 -0
  172. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/change_detector.py +0 -0
  173. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/code_to_spec.py +0 -0
  174. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/drift_detector.py +0 -0
  175. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/repository_sync.py +0 -0
  176. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/spec_to_code.py +0 -0
  177. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/spec_to_tests.py +0 -0
  178. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/watcher.py +0 -0
  179. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
  180. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/telemetry.py +0 -0
  181. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/__init__.py +0 -0
  182. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/bridge_templates.py +0 -0
  183. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
  184. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
  185. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
  186. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
  187. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
  188. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
  189. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
  190. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/registry.py +0 -0
  191. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/specification_templates.py +0 -0
  192. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/__init__.py +0 -0
  193. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
  194. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/auth_tokens.py +0 -0
  195. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/bundle_loader.py +0 -0
  196. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/code_change_detector.py +0 -0
  197. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/console.py +0 -0
  198. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/content_sanitizer.py +0 -0
  199. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/context_detection.py +0 -0
  200. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/enrichment_context.py +0 -0
  201. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/enrichment_parser.py +0 -0
  202. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/env_manager.py +0 -0
  203. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/feature_keys.py +0 -0
  204. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/git.py +0 -0
  205. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/github_annotations.py +0 -0
  206. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/ide_setup.py +0 -0
  207. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/incremental_check.py +0 -0
  208. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/metadata.py +0 -0
  209. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/optional_deps.py +0 -0
  210. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/performance.py +0 -0
  211. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/progress.py +0 -0
  212. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
  213. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/prompts.py +0 -0
  214. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/sdd_discovery.py +0 -0
  215. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/source_scanner.py +0 -0
  216. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/startup_checks.py +0 -0
  217. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/structure.py +0 -0
  218. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/structured_io.py +0 -0
  219. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/suggestions.py +0 -0
  220. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/terminal.py +0 -0
  221. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/yaml_utils.py +0 -0
  222. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/__init__.py +0 -0
  223. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/agile_validation.py +0 -0
  224. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
  225. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/cli_first_validator.py +0 -0
  226. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/contract_validator.py +0 -0
  227. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/fsm.py +0 -0
  228. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/repro_checker.py +0 -0
  229. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/schema.py +0 -0
  230. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
  231. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
  232. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
  233. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
  234. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
  235. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
  236. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
  237. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
  238. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
  239. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
  240. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
  241. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
  242. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
  243. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/models.py +0 -0
  244. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
  245. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
  246. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
  247. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/versioning/__init__.py +0 -0
  248. {specfact_cli-0.26.13 → specfact_cli-0.26.15}/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.13
3
+ Version: 0.26.15
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,7 +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
+ - 🔍 **Debugging I/O or API issues?** Run with `--debug`; logs are written to `~/.specfact/logs/specfact-debug.log`. With `--debug`, ADO API errors include response snippet and patch paths in the log. See [Debug Logging](docs/reference/debug-logging.md).
621
621
  - 📧 **Need help?** [hello@noldai.com](mailto:hello@noldai.com)
622
622
  - 🌐 **Learn more:** [specfact.com](https://specfact.com) • [specfact.io](https://specfact.io) • [specfact.dev](https://specfact.dev)
623
623
 
@@ -339,7 +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
+ - 🔍 **Debugging I/O or API issues?** Run with `--debug`; logs are written to `~/.specfact/logs/specfact-debug.log`. With `--debug`, ADO API errors include response snippet and patch paths in the log. See [Debug Logging](docs/reference/debug-logging.md).
343
343
  - 📧 **Need help?** [hello@noldai.com](mailto:hello@noldai.com)
344
344
  - 🌐 **Learn more:** [specfact.com](https://specfact.com) • [specfact.io](https://specfact.io) • [specfact.dev](https://specfact.dev)
345
345
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "specfact-cli"
7
- version = "0.26.13"
7
+ version = "0.26.15"
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"
@@ -69,6 +69,8 @@ Refine backlog items from DevOps tools (GitHub Issues, Azure DevOps, etc.) into
69
69
  - Ambiguous name-only matches will prompt for explicit iteration path
70
70
  - `--release RELEASE` - Filter by release identifier (case-insensitive)
71
71
  - `--limit N` - Maximum number of items to process in this refinement session (caps batch size)
72
+ - `--ignore-refined` / `--no-ignore-refined` - When set (default), exclude already-refined items so `--limit` applies to items that need refinement. Use `--no-ignore-refined` to process the first N items in order.
73
+ - `--id ISSUE_ID` - Refine only this backlog item (issue or work item ID). Other items are ignored.
72
74
  - `--persona PERSONA` - Filter templates by persona (product-owner, architect, developer)
73
75
  - `--framework FRAMEWORK` - Filter templates by framework (agile, scrum, safe, kanban)
74
76
 
@@ -162,6 +164,30 @@ specfact backlog refine $ADAPTER \
162
164
  - Use `:quit` or `:abort` to cancel the entire session gracefully
163
165
  - Session cancellation shows summary and exits without error
164
166
 
167
+ ### Interactive refinement (Copilot mode)
168
+
169
+ When refining backlog items in Copilot mode (e.g. after export to tmp or during a refinement session), follow this **per-story loop** so the PO and stakeholders can review and approve before any update:
170
+
171
+ 1. **For each story** (one at a time):
172
+ - **Present** the refined story in a clear, readable format:
173
+ - Use headings for Title, Body, Acceptance Criteria, Metrics.
174
+ - Use tables or panels for structured data so it is easy to scan.
175
+ - **Assess specification level** so the DevOps team knows if the story is ready, under-specified, or over-specified:
176
+ - **Under-specified**: Missing acceptance criteria, vague scope, unclear "so that" or user value. List evidence (e.g. "No AC", "Scope could mean X or Y"). Suggest what to add.
177
+ - **Over-specified**: Too much implementation detail, too many sub-steps for one story, or solution prescribed instead of outcome. List evidence and suggest what to trim or split.
178
+ - **Fit for scope and intent**: Clear persona, capability, benefit, and testable AC; appropriate size. State briefly why it is ready (and, if DoR is in use, that DoR is satisfied).
179
+ - **List ambiguities** or open questions (e.g. unclear scope, missing acceptance criteria, conflicting assumptions).
180
+ - **Ask** the PO and other stakeholders for clarification: "Please review the refined story above. Do you want any changes? Any ambiguities to resolve? Should this story be split?"
181
+ - **If the user provides feedback**: Re-refine the story incorporating the feedback, then repeat from "Present" for this story.
182
+ - **Only when the user explicitly approves** (e.g. "looks good", "approved", "no changes"): Mark this story as done and move to the **next** story.
183
+ - **Do not update** the backlog item (or write to the refined file as final) until the user has approved this story.
184
+
185
+ 2. **Formatting**:
186
+ - Use clear headings, bullet lists, and optional tables/panels so refinement sessions are easy to follow and enjoyable.
187
+ - Keep each story’s block self-contained so stakeholders can focus on one item at a time.
188
+
189
+ 3. **Rule**: The backlog item (or exported block) must only be updated/finalized **after** the user has approved the refined content for that story. Then proceed to the next story with the same process.
190
+
165
191
  ### Step 3: Present Results
166
192
 
167
193
  Display refinement results:
@@ -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.13"
6
+ __version__ = "0.26.15"
@@ -9,6 +9,6 @@ This package provides command-line tools for:
9
9
  - Validating reproducibility
10
10
  """
11
11
 
12
- __version__ = "0.26.13"
12
+ __version__ = "0.26.15"
13
13
 
14
14
  __all__ = ["__version__"]
@@ -11,8 +11,7 @@ This follows the backlog adapter patterns established by the GitHub adapter.
11
11
  from __future__ import annotations
12
12
 
13
13
  import os
14
-
15
- # import re
14
+ import re
16
15
  from datetime import UTC, datetime
17
16
  from pathlib import Path
18
17
  from typing import Any
@@ -28,6 +27,7 @@ from specfact_cli.adapters.base import BridgeAdapter
28
27
  from specfact_cli.backlog.adapters.base import BacklogAdapter
29
28
  from specfact_cli.backlog.filters import BacklogFilters
30
29
  from specfact_cli.backlog.mappers.ado_mapper import AdoFieldMapper
30
+ from specfact_cli.common.logger_setup import LoggerSetup
31
31
  from specfact_cli.models.backlog_item import BacklogItem
32
32
  from specfact_cli.models.bridge import BridgeConfig
33
33
  from specfact_cli.models.capabilities import ToolCapabilities
@@ -36,9 +36,68 @@ from specfact_cli.runtime import debug_log_operation, debug_print, is_debug_mode
36
36
  from specfact_cli.utils.auth_tokens import get_token, set_token
37
37
 
38
38
 
39
+ _MAX_RESPONSE_BODY_LOG = 2048
40
+
39
41
  console = Console()
40
42
 
41
43
 
44
+ def _log_ado_patch_failure(
45
+ response: requests.Response | None,
46
+ operations: list[dict[str, Any]],
47
+ url: str,
48
+ context: str = "",
49
+ ) -> str:
50
+ """
51
+ Log ADO PATCH failure to debug log (when debug on) and return user-facing message.
52
+
53
+ Parses response body (JSON message or truncated text), extracts patch paths,
54
+ redacts/truncates for debug log, and builds a user message with ADO text and hint.
55
+ """
56
+ paths = [op.get("path", "") for op in operations if isinstance(op, dict)]
57
+ snippet = ""
58
+ if response is not None:
59
+ try:
60
+ body = response.json()
61
+ snippet = str(body.get("message", response.text[:500]))
62
+ except Exception:
63
+ snippet = (response.text or "")[:_MAX_RESPONSE_BODY_LOG]
64
+ snippet = snippet[:_MAX_RESPONSE_BODY_LOG]
65
+ snippet = str(LoggerSetup.redact_secrets(snippet))
66
+
67
+ if is_debug_mode():
68
+ debug_log_operation(
69
+ "ado_patch",
70
+ url,
71
+ "failed",
72
+ error=context or snippet[:500],
73
+ extra={"response_body": snippet, "patch_paths": paths},
74
+ )
75
+
76
+ return _build_ado_user_message(response)
77
+
78
+
79
+ def _build_ado_user_message(response: requests.Response | None) -> str:
80
+ """Build user-facing error message from ADO response and append mapping hint."""
81
+ hint = " Check custom field mapping; see ado_custom.yaml or documentation."
82
+ if response is None:
83
+ return f"Azure DevOps request failed.{hint}"
84
+ try:
85
+ body = response.json()
86
+ msg = body.get("message", "") or (response.text or "")[:500]
87
+ except Exception:
88
+ msg = (response.text or "")[:500]
89
+ if not msg:
90
+ return f"Azure DevOps request failed (HTTP {getattr(response, 'status_code', '')}).{hint}"
91
+
92
+ m = re.search(r"Cannot find field\s+([^\s]+)", msg, re.IGNORECASE)
93
+ if m:
94
+ field = m.group(1).strip().rstrip(".")
95
+ user_msg = f"Field '{field}' not found.{hint}"
96
+ else:
97
+ user_msg = f"{msg}{hint}"
98
+ return user_msg
99
+
100
+
42
101
  class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
43
102
  """
44
103
  Azure DevOps bridge adapter implementing BridgeAdapter interface.
@@ -1642,10 +1701,10 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
1642
1701
  "state": ado_state,
1643
1702
  }
1644
1703
  except requests.RequestException as e:
1645
- if is_debug_mode():
1646
- debug_log_operation("ado_patch", url, "error", error=str(e))
1647
- msg = f"Failed to create Azure DevOps work item: {e}"
1648
- console.print(f"[bold red]✗[/bold red] {msg}")
1704
+ resp = getattr(e, "response", None)
1705
+ user_msg = _log_ado_patch_failure(resp, patch_document, url)
1706
+ e.ado_user_message = user_msg
1707
+ console.print(f"[bold red]✗[/bold red] {user_msg}")
1649
1708
  raise
1650
1709
 
1651
1710
  def _update_work_item_status(
@@ -1744,8 +1803,9 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
1744
1803
  "state": ado_state,
1745
1804
  }
1746
1805
  except requests.RequestException as e:
1747
- msg = f"Failed to update Azure DevOps work item #{work_item_id}: {e}"
1748
- console.print(f"[bold red]✗[/bold red] {msg}")
1806
+ resp = getattr(e, "response", None)
1807
+ user_msg = _log_ado_patch_failure(resp, patch_document, url)
1808
+ console.print(f"[bold red]✗[/bold red] {user_msg}")
1749
1809
  raise
1750
1810
 
1751
1811
  def _update_work_item_body(
@@ -1876,8 +1936,9 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
1876
1936
  "state": ado_state,
1877
1937
  }
1878
1938
  except requests.RequestException as e:
1879
- msg = f"Failed to update Azure DevOps work item #{work_item_id}: {e}"
1880
- console.print(f"[bold red]✗[/bold red] {msg}")
1939
+ resp = getattr(e, "response", None)
1940
+ user_msg = _log_ado_patch_failure(resp, patch_document, url)
1941
+ console.print(f"[bold red]✗[/bold red] {user_msg}")
1881
1942
  raise
1882
1943
 
1883
1944
  @beartype
@@ -1983,8 +2044,10 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
1983
2044
  "new_state": ado_state,
1984
2045
  }
1985
2046
  except requests.RequestException as e:
1986
- msg = f"Failed to sync status to Azure DevOps work item #{work_item_id}: {e}"
1987
- console.print(f"[bold red]✗[/bold red] {msg}")
2047
+ resp = getattr(e, "response", None)
2048
+ user_msg = _log_ado_patch_failure(resp, patch_document, url)
2049
+ e.ado_user_message = user_msg
2050
+ console.print(f"[bold red]✗[/bold red] {user_msg}")
1988
2051
  raise
1989
2052
 
1990
2053
  @beartype
@@ -2361,8 +2424,10 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
2361
2424
  "comment_added": True,
2362
2425
  }
2363
2426
  except requests.RequestException as e:
2364
- msg = f"Failed to add comment to Azure DevOps work item #{work_item_id}: {e}"
2365
- console.print(f"[bold red]✗[/bold red] {msg}")
2427
+ resp = getattr(e, "response", None)
2428
+ user_msg = _log_ado_patch_failure(resp, [], url)
2429
+ e.ado_user_message = user_msg
2430
+ console.print(f"[bold red]✗[/bold red] {user_msg}")
2366
2431
  raise
2367
2432
 
2368
2433
  @beartype
@@ -3200,7 +3265,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
3200
3265
  response = requests.patch(url, headers=headers, json=operations, timeout=30)
3201
3266
  response.raise_for_status()
3202
3267
  except requests.HTTPError as e:
3203
- # Handle 400/422: often caused by /multilineFieldsFormat/ not being supported by ADO API
3268
+ user_msg = _log_ado_patch_failure(e.response, operations, url)
3269
+ e.ado_user_message = user_msg
3204
3270
  response = None
3205
3271
  if e.response and e.response.status_code in (400, 422):
3206
3272
  error_message = ""
@@ -3221,14 +3287,12 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
3221
3287
  resp.raise_for_status()
3222
3288
  response = resp
3223
3289
  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
- )
3290
+ _log_ado_patch_failure(
3291
+ retry_error.response,
3292
+ operations_no_format,
3293
+ url,
3294
+ context=str(retry_error),
3295
+ )
3232
3296
 
3233
3297
  if response is None and (
3234
3298
  "already exists" in error_message.lower() or "cannot add" in error_message.lower()
@@ -3280,9 +3344,11 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
3280
3344
  resp.raise_for_status()
3281
3345
  response = resp
3282
3346
  except requests.HTTPError:
3347
+ console.print(f"[bold red]✗[/bold red] {user_msg}")
3283
3348
  raise
3284
3349
 
3285
3350
  if response is None:
3351
+ console.print(f"[bold red]✗[/bold red] {user_msg}")
3286
3352
  raise
3287
3353
 
3288
3354
  updated_work_item = response.json()
@@ -454,6 +454,7 @@ def cli_main() -> None:
454
454
  console.print() # Empty line after banner
455
455
  elif not is_help_or_version and not is_test_mode:
456
456
  # Show simple version line like other CLIs (skip for help/version commands and in test mode)
457
+ # Printed before startup checks so users see output immediately (important with slow checks e.g. xagt)
457
458
  print_version_line()
458
459
 
459
460
  # Run startup checks (template validation and version check)
@@ -16,6 +16,7 @@ from __future__ import annotations
16
16
  import os
17
17
  import re
18
18
  import sys
19
+ import tempfile
19
20
  from datetime import datetime
20
21
  from pathlib import Path
21
22
  from typing import Any
@@ -295,6 +296,44 @@ def _parse_refined_export_markdown(content: str) -> dict[str, dict[str, Any]]:
295
296
  return result
296
297
 
297
298
 
299
+ @beartype
300
+ def _item_needs_refinement(
301
+ item: BacklogItem,
302
+ detector: TemplateDetector,
303
+ registry: TemplateRegistry,
304
+ template_id: str | None,
305
+ normalized_adapter: str | None,
306
+ normalized_framework: str | None,
307
+ normalized_persona: str | None,
308
+ ) -> bool:
309
+ """
310
+ Return True if the item needs refinement (should be processed); False if already refined (skip).
311
+
312
+ Mirrors the "already refined" skip logic used in the refine loop: checkboxes + all required
313
+ sections, or high confidence with no missing fields.
314
+ """
315
+ detection_result = detector.detect_template(
316
+ item,
317
+ provider=normalized_adapter,
318
+ framework=normalized_framework,
319
+ persona=normalized_persona,
320
+ )
321
+ if detection_result.template_id:
322
+ target = registry.get_template(detection_result.template_id) if detection_result.template_id else None
323
+ if target and target.required_sections:
324
+ has_checkboxes = bool(
325
+ re.search(r"^[\s]*- \[[ x]\]", item.body_markdown or "", re.MULTILINE | re.IGNORECASE)
326
+ )
327
+ all_present = all(
328
+ bool(re.search(rf"^#+\s+{re.escape(s)}\s*$", item.body_markdown or "", re.MULTILINE | re.IGNORECASE))
329
+ for s in target.required_sections
330
+ )
331
+ if has_checkboxes and all_present and not detection_result.missing_fields:
332
+ return False
333
+ already_refined = template_id is None and detection_result.confidence >= 0.8 and not detection_result.missing_fields
334
+ return not already_refined
335
+
336
+
298
337
  def _fetch_backlog_items(
299
338
  adapter_name: str,
300
339
  search_query: str | None = None,
@@ -423,6 +462,16 @@ def refine(
423
462
  "--limit",
424
463
  help="Maximum number of items to process in this refinement session. Use to cap batch size and avoid processing too many items at once.",
425
464
  ),
465
+ ignore_refined: bool = typer.Option(
466
+ True,
467
+ "--ignore-refined/--no-ignore-refined",
468
+ help="When set (default), exclude already-refined items from the batch so --limit applies to items that need refinement. Use --no-ignore-refined to process the first N items in order (already-refined skipped in loop).",
469
+ ),
470
+ issue_id: str | None = typer.Option(
471
+ None,
472
+ "--id",
473
+ help="Refine only this backlog item (issue or work item ID). Other items are ignored.",
474
+ ),
426
475
  template_id: str | None = typer.Option(None, "--template", "-t", help="Target template ID (default: auto-detect)"),
427
476
  auto_accept_high_confidence: bool = typer.Option(
428
477
  False, "--auto-accept-high-confidence", help="Auto-accept refinements with confidence >= 0.85"
@@ -445,12 +494,12 @@ def refine(
445
494
  export_to_tmp: bool = typer.Option(
446
495
  False,
447
496
  "--export-to-tmp",
448
- help="Export backlog items to temporary file for copilot processing (default: /tmp/specfact-backlog-refine-<timestamp>.md)",
497
+ help="Export backlog items to temporary file for copilot processing (default: <system-temp>/specfact-backlog-refine-<timestamp>.md)",
449
498
  ),
450
499
  import_from_tmp: bool = typer.Option(
451
500
  False,
452
501
  "--import-from-tmp",
453
- help="Import refined content from temporary file after copilot processing (default: /tmp/specfact-backlog-refine-<timestamp>-refined.md)",
502
+ help="Import refined content from temporary file after copilot processing (default: <system-temp>/specfact-backlog-refine-<timestamp>-refined.md)",
454
503
  ),
455
504
  tmp_file: Path | None = typer.Option(
456
505
  None,
@@ -650,6 +699,10 @@ def refine(
650
699
  init_progress.update(validate_task, description="[green]✓[/green] Configuration validated")
651
700
 
652
701
  # Fetch backlog items with filters
702
+ # When ignore_refined and limit are set, fetch more candidates so we have enough after filtering
703
+ fetch_limit: int | None = limit
704
+ if ignore_refined and limit is not None and limit > 0:
705
+ fetch_limit = limit * 5
653
706
  with Progress(
654
707
  SpinnerColumn(),
655
708
  TextColumn("[progress.description]{task.description}"),
@@ -667,7 +720,7 @@ def refine(
667
720
  iteration=iteration,
668
721
  sprint=sprint,
669
722
  release=release,
670
- limit=limit,
723
+ limit=fetch_limit,
671
724
  repo_owner=repo_owner,
672
725
  repo_name=repo_name,
673
726
  github_token=github_token,
@@ -705,6 +758,34 @@ def refine(
705
758
  console.print("[yellow]No backlog items found.[/yellow]")
706
759
  return
707
760
 
761
+ # Filter by issue ID when --id is set
762
+ if issue_id is not None:
763
+ items = [i for i in items if str(i.id) == str(issue_id)]
764
+ if not items:
765
+ console.print(
766
+ f"[bold red]✗[/bold red] No backlog item with id {issue_id!r} found. "
767
+ "Check filters and adapter configuration."
768
+ )
769
+ raise typer.Exit(1)
770
+
771
+ # When ignore_refined (default), keep only items that need refinement; then apply limit
772
+ if ignore_refined:
773
+ items = [
774
+ i
775
+ for i in items
776
+ if _item_needs_refinement(
777
+ i, detector, registry, template_id, normalized_adapter, normalized_framework, normalized_persona
778
+ )
779
+ ]
780
+ if limit is not None and len(items) > limit:
781
+ items = items[:limit]
782
+ if ignore_refined and (limit is not None or issue_id is not None):
783
+ console.print(
784
+ f"[dim]Filtered to {len(items)} item(s) needing refinement"
785
+ + (f" (limit {limit})" if limit is not None else "")
786
+ + "[/dim]"
787
+ )
788
+
708
789
  # Validate export/import flags
709
790
  if export_to_tmp and import_from_tmp:
710
791
  console.print("[bold red]✗[/bold red] --export-to-tmp and --import-from-tmp are mutually exclusive")
@@ -713,7 +794,7 @@ def refine(
713
794
  # Handle export mode
714
795
  if export_to_tmp:
715
796
  timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
716
- export_file = tmp_file or Path(f"/tmp/specfact-backlog-refine-{timestamp}.md")
797
+ export_file = tmp_file or (Path(tempfile.gettempdir()) / f"specfact-backlog-refine-{timestamp}.md")
717
798
 
718
799
  console.print(f"[bold cyan]Exporting {len(items)} backlog item(s) to: {export_file}[/bold cyan]")
719
800
 
@@ -764,7 +845,7 @@ def refine(
764
845
  # Handle import mode
765
846
  if import_from_tmp:
766
847
  timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
767
- import_file = tmp_file or Path(f"/tmp/specfact-backlog-refine-{timestamp}-refined.md")
848
+ import_file = tmp_file or (Path(tempfile.gettempdir()) / f"specfact-backlog-refine-{timestamp}-refined.md")
768
849
 
769
850
  if not import_file.exists():
770
851
  console.print(f"[bold red]✗[/bold red] Import file not found: {import_file}")
@@ -842,8 +923,8 @@ def refine(
842
923
  console.print(f"[green]✓ Updated {len(updated_items)} backlog item(s)[/green]")
843
924
  return
844
925
 
845
- # Apply limit if specified
846
- if limit and len(items) > limit:
926
+ # Apply limit if specified (when not ignore_refined; when ignore_refined we already filtered and sliced)
927
+ if not ignore_refined and limit is not None and len(items) > limit:
847
928
  items = items[:limit]
848
929
  console.print(f"[yellow]Limited to {limit} items (found {len(items)} total)[/yellow]")
849
930
  else:
@@ -1099,19 +1099,19 @@ def sync_bridge(
1099
1099
  export_to_tmp: bool = typer.Option(
1100
1100
  False,
1101
1101
  "--export-to-tmp",
1102
- help="Export proposal content to temporary file for LLM review (default: /tmp/specfact-proposal-<change-id>.md).",
1102
+ help="Export proposal content to temporary file for LLM review (default: <system-temp>/specfact-proposal-<change-id>.md).",
1103
1103
  hidden=True,
1104
1104
  ),
1105
1105
  import_from_tmp: bool = typer.Option(
1106
1106
  False,
1107
1107
  "--import-from-tmp",
1108
- help="Import sanitized content from temporary file after LLM review (default: /tmp/specfact-proposal-<change-id>-sanitized.md).",
1108
+ help="Import sanitized content from temporary file after LLM review (default: <system-temp>/specfact-proposal-<change-id>-sanitized.md).",
1109
1109
  hidden=True,
1110
1110
  ),
1111
1111
  tmp_file: Path | None = typer.Option(
1112
1112
  None,
1113
1113
  "--tmp-file",
1114
- help="Custom temporary file path (default: /tmp/specfact-proposal-<change-id>.md).",
1114
+ help="Custom temporary file path (default: <system-temp>/specfact-proposal-<change-id>.md).",
1115
1115
  hidden=True,
1116
1116
  ),
1117
1117
  update_existing: bool = typer.Option(
@@ -12,6 +12,7 @@ from __future__ import annotations
12
12
  import hashlib
13
13
  import re
14
14
  import subprocess
15
+ import tempfile
15
16
  from dataclasses import dataclass
16
17
  from urllib.parse import urlparse
17
18
 
@@ -557,7 +558,7 @@ class BridgeSync:
557
558
  change_ids: Optional list of change proposal IDs to filter. If None, exports all active proposals.
558
559
  export_to_tmp: If True, export proposal content to temporary file for LLM review.
559
560
  import_from_tmp: If True, import sanitized content from temporary file after LLM review.
560
- tmp_file: Optional custom temporary file path. Default: `/tmp/specfact-proposal-<change-id>.md`.
561
+ tmp_file: Optional custom temporary file path. Default: <system-temp>/specfact-proposal-<change-id>.md.
561
562
 
562
563
  Returns:
563
564
  SyncResult with operation details
@@ -904,7 +905,7 @@ class BridgeSync:
904
905
  # Handle temporary file workflow if requested
905
906
  if export_to_tmp:
906
907
  # Export proposal content to temporary file for LLM review
907
- tmp_file_path = tmp_file or Path(f"/tmp/specfact-proposal-{change_id}.md")
908
+ tmp_file_path = tmp_file or (Path(tempfile.gettempdir()) / f"specfact-proposal-{change_id}.md")
908
909
  try:
909
910
  # Create markdown content from proposal
910
911
  proposal_content = self._format_proposal_for_export(proposal)
@@ -919,7 +920,9 @@ class BridgeSync:
919
920
 
920
921
  if import_from_tmp:
921
922
  # Import sanitized content from temporary file
922
- sanitized_file_path = tmp_file or Path(f"/tmp/specfact-proposal-{change_id}-sanitized.md")
923
+ sanitized_file_path = tmp_file or (
924
+ Path(tempfile.gettempdir()) / f"specfact-proposal-{change_id}-sanitized.md"
925
+ )
923
926
  try:
924
927
  if not sanitized_file_path.exists():
925
928
  errors.append(
@@ -933,7 +936,7 @@ class BridgeSync:
933
936
  proposal_to_export = self._parse_sanitized_proposal(sanitized_content, proposal)
934
937
  # Cleanup temporary files after import
935
938
  try:
936
- original_tmp = Path(f"/tmp/specfact-proposal-{change_id}.md")
939
+ original_tmp = Path(tempfile.gettempdir()) / f"specfact-proposal-{change_id}.md"
937
940
  if original_tmp.exists():
938
941
  original_tmp.unlink()
939
942
  if sanitized_file_path.exists():
@@ -2504,7 +2507,7 @@ class BridgeSync:
2504
2507
  # Handle sanitized content updates (when import_from_tmp is used)
2505
2508
  if import_from_tmp:
2506
2509
  change_id = proposal.get("change_id", "unknown")
2507
- sanitized_file = tmp_file or Path(f"/tmp/specfact-proposal-{change_id}-sanitized.md")
2510
+ sanitized_file = tmp_file or (Path(tempfile.gettempdir()) / f"specfact-proposal-{change_id}-sanitized.md")
2508
2511
  if sanitized_file.exists():
2509
2512
  sanitized_content = sanitized_file.read_text(encoding="utf-8")
2510
2513
  proposal_for_hash = {
@@ -2603,7 +2606,9 @@ class BridgeSync:
2603
2606
  try:
2604
2607
  if import_from_tmp:
2605
2608
  change_id = proposal.get("change_id", "unknown")
2606
- sanitized_file = tmp_file or Path(f"/tmp/specfact-proposal-{change_id}-sanitized.md")
2609
+ sanitized_file = tmp_file or (
2610
+ Path(tempfile.gettempdir()) / f"specfact-proposal-{change_id}-sanitized.md"
2611
+ )
2607
2612
  if sanitized_file.exists():
2608
2613
  sanitized_content = sanitized_file.read_text(encoding="utf-8")
2609
2614
  proposal_for_update = {