specfact-cli 0.26.9__tar.gz → 0.26.11__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.9 → specfact_cli-0.26.11}/.gitignore +4 -1
  2. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/PKG-INFO +1 -1
  3. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/pyproject.toml +1 -1
  4. specfact_cli-0.26.11/src/__init__.py +6 -0
  5. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/__init__.py +1 -1
  6. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/ado.py +14 -2
  7. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/openspec.py +11 -6
  8. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/openspec_parser.py +34 -1
  9. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/converter.py +18 -1
  10. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/backlog_commands.py +156 -4
  11. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/backlog_item.py +5 -1
  12. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/bridge.py +1 -1
  13. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/bridge_sync.py +14 -1
  14. specfact_cli-0.26.9/src/__init__.py +0 -6
  15. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/LICENSE.md +0 -0
  16. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/README.md +0 -0
  17. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/mappings/node-async.yaml +0 -0
  18. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/mappings/python-async.yaml +0 -0
  19. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/mappings/speckit-default.yaml +0 -0
  20. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/shared/cli-enforcement.md +0 -0
  21. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.01-import.md +0 -0
  22. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.02-plan.md +0 -0
  23. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.03-review.md +0 -0
  24. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.04-sdd.md +0 -0
  25. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.05-enforce.md +0 -0
  26. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.06-sync.md +0 -0
  27. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.07-contracts.md +0 -0
  28. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.backlog-refine.md +0 -0
  29. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.compare.md +0 -0
  30. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.sync-backlog.md +0 -0
  31. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.validate.md +0 -0
  32. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/schemas/deviation.schema.json +0 -0
  33. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/schemas/plan.schema.json +0 -0
  34. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/schemas/protocol.schema.json +0 -0
  35. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/defaults/defect_v1.yaml +0 -0
  36. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/defaults/enabler_v1.yaml +0 -0
  37. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/defaults/spike_v1.yaml +0 -0
  38. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/defaults/user_story_v1.yaml +0 -0
  39. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/field_mappings/ado_agile.yaml +0 -0
  40. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/field_mappings/ado_default.yaml +0 -0
  41. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/field_mappings/ado_kanban.yaml +0 -0
  42. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/field_mappings/ado_safe.yaml +0 -0
  43. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/field_mappings/ado_scrum.yaml +0 -0
  44. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/frameworks/safe/safe_feature_v1.yaml +0 -0
  45. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/frameworks/scrum/user_story_v1.yaml +0 -0
  46. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/personas/developer/developer_task_v1.yaml +0 -0
  47. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/personas/product-owner/user_story_v1.yaml +0 -0
  48. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/providers/ado/work_item_v1.yaml +0 -0
  49. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/github-action.yml.j2 +0 -0
  50. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/persona/architect.md.j2 +0 -0
  51. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/persona/developer.md.j2 +0 -0
  52. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/persona/product-owner.md.j2 +0 -0
  53. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/plan.bundle.yaml.j2 +0 -0
  54. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/pr-template.md.j2 +0 -0
  55. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/protocol.yaml.j2 +0 -0
  56. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/telemetry.yaml.example +0 -0
  57. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/__init__.py +0 -0
  58. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/backlog_base.py +0 -0
  59. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/base.py +0 -0
  60. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/github.py +0 -0
  61. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/registry.py +0 -0
  62. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/speckit.py +0 -0
  63. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/agents/__init__.py +0 -0
  64. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/agents/analyze_agent.py +0 -0
  65. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/agents/base.py +0 -0
  66. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/agents/plan_agent.py +0 -0
  67. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/agents/registry.py +0 -0
  68. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/agents/sync_agent.py +0 -0
  69. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/__init__.py +0 -0
  70. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
  71. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
  72. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
  73. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
  74. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
  75. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
  76. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
  77. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
  78. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
  79. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/__init__.py +0 -0
  80. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
  81. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/adapters/base.py +0 -0
  82. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/adapters/local_yaml_adapter.py +0 -0
  83. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/ai_refiner.py +0 -0
  84. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/filters.py +0 -0
  85. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/format_detector.py +0 -0
  86. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/formats/__init__.py +0 -0
  87. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/formats/base.py +0 -0
  88. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/formats/markdown_format.py +0 -0
  89. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/formats/structured_format.py +0 -0
  90. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
  91. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
  92. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/mappers/base.py +0 -0
  93. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
  94. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
  95. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/template_detector.py +0 -0
  96. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/cli.py +0 -0
  97. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/__init__.py +0 -0
  98. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/analyze.py +0 -0
  99. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/auth.py +0 -0
  100. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/contract_cmd.py +0 -0
  101. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/drift.py +0 -0
  102. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/enforce.py +0 -0
  103. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/generate.py +0 -0
  104. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/import_cmd.py +0 -0
  105. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/init.py +0 -0
  106. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/migrate.py +0 -0
  107. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/plan.py +0 -0
  108. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/project_cmd.py +0 -0
  109. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/repro.py +0 -0
  110. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/sdd.py +0 -0
  111. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/spec.py +0 -0
  112. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/sync.py +0 -0
  113. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/update.py +0 -0
  114. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/validate.py +0 -0
  115. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/common/__init__.py +0 -0
  116. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/common/logger_setup.py +0 -0
  117. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/common/logging_utils.py +0 -0
  118. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/common/text_utils.py +0 -0
  119. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/common/utils.py +0 -0
  120. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/comparators/__init__.py +0 -0
  121. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/comparators/plan_comparator.py +0 -0
  122. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/contracts/__init__.py +0 -0
  123. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/contracts/crosshair_props.py +0 -0
  124. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
  125. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
  126. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/__init__.py +0 -0
  127. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/contract_generator.py +0 -0
  128. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/openapi_extractor.py +0 -0
  129. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/persona_exporter.py +0 -0
  130. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/plan_generator.py +0 -0
  131. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/protocol_generator.py +0 -0
  132. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/report_generator.py +0 -0
  133. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/task_generator.py +0 -0
  134. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/test_to_openapi.py +0 -0
  135. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/workflow_generator.py +0 -0
  136. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/importers/__init__.py +0 -0
  137. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/importers/speckit_converter.py +0 -0
  138. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/importers/speckit_scanner.py +0 -0
  139. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/integrations/__init__.py +0 -0
  140. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/integrations/specmatic.py +0 -0
  141. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/merge/__init__.py +0 -0
  142. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/merge/resolver.py +0 -0
  143. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/migrations/__init__.py +0 -0
  144. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/migrations/plan_migrator.py +0 -0
  145. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/__init__.py +0 -0
  146. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/capabilities.py +0 -0
  147. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/change.py +0 -0
  148. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/contract.py +0 -0
  149. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/deviation.py +0 -0
  150. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/dor_config.py +0 -0
  151. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/enforcement.py +0 -0
  152. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/persona_template.py +0 -0
  153. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/plan.py +0 -0
  154. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/project.py +0 -0
  155. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/protocol.py +0 -0
  156. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/quality.py +0 -0
  157. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/sdd.py +0 -0
  158. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/source_tracking.py +0 -0
  159. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/task.py +0 -0
  160. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/modes/__init__.py +0 -0
  161. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/modes/detector.py +0 -0
  162. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/modes/router.py +0 -0
  163. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/parsers/__init__.py +0 -0
  164. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/parsers/persona_importer.py +0 -0
  165. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/resources/semgrep/async.yml +0 -0
  166. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
  167. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
  168. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/runtime.py +0 -0
  169. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/__init__.py +0 -0
  170. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/bridge_probe.py +0 -0
  171. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/bridge_watch.py +0 -0
  172. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/change_detector.py +0 -0
  173. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/code_to_spec.py +0 -0
  174. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/drift_detector.py +0 -0
  175. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/repository_sync.py +0 -0
  176. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/spec_to_code.py +0 -0
  177. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/spec_to_tests.py +0 -0
  178. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/watcher.py +0 -0
  179. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
  180. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/telemetry.py +0 -0
  181. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/__init__.py +0 -0
  182. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/bridge_templates.py +0 -0
  183. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
  184. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
  185. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
  186. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
  187. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
  188. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
  189. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
  190. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/registry.py +0 -0
  191. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/specification_templates.py +0 -0
  192. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/__init__.py +0 -0
  193. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
  194. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/auth_tokens.py +0 -0
  195. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/bundle_loader.py +0 -0
  196. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/code_change_detector.py +0 -0
  197. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/console.py +0 -0
  198. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/content_sanitizer.py +0 -0
  199. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/context_detection.py +0 -0
  200. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/enrichment_context.py +0 -0
  201. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/enrichment_parser.py +0 -0
  202. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/env_manager.py +0 -0
  203. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/feature_keys.py +0 -0
  204. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/git.py +0 -0
  205. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/github_annotations.py +0 -0
  206. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/ide_setup.py +0 -0
  207. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/incremental_check.py +0 -0
  208. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/metadata.py +0 -0
  209. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/optional_deps.py +0 -0
  210. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/performance.py +0 -0
  211. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/progress.py +0 -0
  212. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
  213. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/prompts.py +0 -0
  214. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/sdd_discovery.py +0 -0
  215. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/source_scanner.py +0 -0
  216. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/startup_checks.py +0 -0
  217. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/structure.py +0 -0
  218. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/structured_io.py +0 -0
  219. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/suggestions.py +0 -0
  220. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/terminal.py +0 -0
  221. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/yaml_utils.py +0 -0
  222. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/__init__.py +0 -0
  223. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/agile_validation.py +0 -0
  224. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
  225. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/cli_first_validator.py +0 -0
  226. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/contract_validator.py +0 -0
  227. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/fsm.py +0 -0
  228. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/repro_checker.py +0 -0
  229. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/schema.py +0 -0
  230. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
  231. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
  232. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
  233. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
  234. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
  235. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
  236. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
  237. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
  238. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
  239. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
  240. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
  241. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
  242. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
  243. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/models.py +0 -0
  244. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
  245. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
  246. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
  247. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/versioning/__init__.py +0 -0
  248. {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/versioning/analyzer.py +0 -0
@@ -96,7 +96,10 @@ docs/internal/
96
96
  # Ignore .specify artifacts
97
97
  .specify/
98
98
  .cursor/commands/speckit.*
99
- specs/
99
+ /specs/
100
+
101
+ # Include openspec/specs/ directory
102
+ !openspec/
100
103
 
101
104
  # Ignore specfact-cli prompt templates
102
105
  .cursor/commands/specfact-*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specfact-cli
3
- Version: 0.26.9
3
+ Version: 0.26.11
4
4
  Summary: Brownfield-first CLI: Reverse engineer legacy Python → specs → enforced contracts. Automate legacy code documentation and prevent modernization regressions.
5
5
  Project-URL: Homepage, https://github.com/nold-ai/specfact-cli
6
6
  Project-URL: Repository, https://github.com/nold-ai/specfact-cli.git
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "specfact-cli"
7
- version = "0.26.9"
7
+ version = "0.26.11"
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"
@@ -0,0 +1,6 @@
1
+ """
2
+ SpecFact CLI - Spec→Contract→Sentinel tool for contract-driven development.
3
+ """
4
+
5
+ # Package version: keep in sync with pyproject.toml, setup.py, src/specfact_cli/__init__.py
6
+ __version__ = "0.26.11"
@@ -9,6 +9,6 @@ This package provides command-line tools for:
9
9
  - Validating reproducibility
10
10
  """
11
11
 
12
- __version__ = "0.26.8"
12
+ __version__ = "0.26.11"
13
13
 
14
14
  __all__ = ["__version__"]
@@ -2938,7 +2938,13 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
2938
2938
  from specfact_cli.backlog.converter import convert_ado_work_item_to_backlog_item
2939
2939
 
2940
2940
  for work_item in work_items_data.get("value", []):
2941
- backlog_item = convert_ado_work_item_to_backlog_item(work_item, provider="ado")
2941
+ backlog_item = convert_ado_work_item_to_backlog_item(
2942
+ work_item,
2943
+ provider="ado",
2944
+ base_url=self.base_url,
2945
+ org=self.org,
2946
+ project_name=self.project,
2947
+ )
2942
2948
  items.append(backlog_item)
2943
2949
 
2944
2950
  # Apply post-fetch filters that ADO API doesn't support directly
@@ -3258,4 +3264,10 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
3258
3264
  # Convert back to BacklogItem
3259
3265
  from specfact_cli.backlog.converter import convert_ado_work_item_to_backlog_item
3260
3266
 
3261
- return convert_ado_work_item_to_backlog_item(updated_work_item, provider="ado")
3267
+ return convert_ado_work_item_to_backlog_item(
3268
+ updated_work_item,
3269
+ provider="ado",
3270
+ base_url=self.base_url,
3271
+ org=self.org,
3272
+ project_name=self.project,
3273
+ )
@@ -57,11 +57,12 @@ class OpenSpecAdapter(BridgeAdapter):
57
57
  if bridge_config and bridge_config.external_base_path:
58
58
  base_path = bridge_config.external_base_path
59
59
 
60
- # Check for OpenSpec structure
60
+ # Check for OpenSpec structure (OPSX: config.yaml; legacy: project.md; or specs dir)
61
+ config_yaml = base_path / "openspec" / "config.yaml"
61
62
  project_md = base_path / "openspec" / "project.md"
62
63
  specs_dir = base_path / "openspec" / "specs"
63
64
 
64
- return project_md.exists() or (specs_dir.exists() and specs_dir.is_dir())
65
+ return config_yaml.exists() or project_md.exists() or (specs_dir.exists() and specs_dir.is_dir())
65
66
 
66
67
  @beartype
67
68
  @require(lambda repo_path: repo_path.exists(), "Repository path must exist")
@@ -179,8 +180,9 @@ class OpenSpecAdapter(BridgeAdapter):
179
180
  """
180
181
  config = BridgeConfig.preset_openspec()
181
182
 
182
- # Check if OpenSpec is in external repo
183
- if not (repo_path / "openspec" / "project.md").exists():
183
+ # Check if OpenSpec is in external repo (OPSX: config.yaml; legacy: project.md)
184
+ openspec_dir = repo_path / "openspec"
185
+ if not (openspec_dir / "config.yaml").exists() and not (openspec_dir / "project.md").exists():
184
186
  # Try to find external OpenSpec (this is a simple heuristic)
185
187
  # In practice, external_base_path should be provided via CLI option
186
188
  pass
@@ -422,10 +424,13 @@ class OpenSpecAdapter(BridgeAdapter):
422
424
  bridge_config: BridgeConfig | None,
423
425
  base_path: Path | None,
424
426
  ) -> None:
425
- """Import project context from OpenSpec project.md."""
427
+ """Import project context from OpenSpec project.md (legacy) or config.yaml (OPSX)."""
426
428
  from specfact_cli.models.plan import Idea
427
429
 
428
- parsed = self.parser.parse_project_md(project_md_path)
430
+ if project_md_path.name == "config.yaml" or project_md_path.suffix in (".yaml", ".yml"):
431
+ parsed = self.parser.parse_config_yaml(project_md_path)
432
+ else:
433
+ parsed = self.parser.parse_project_md(project_md_path)
429
434
 
430
435
  # Create Idea if it doesn't exist
431
436
  if not hasattr(project_bundle, "idea") or project_bundle.idea is None:
@@ -2,7 +2,8 @@
2
2
  OpenSpec parser for adapter-specific OpenSpec format parsing.
3
3
 
4
4
  This module provides parsing functionality for OpenSpec artifacts:
5
- - project.md (project context)
5
+ - project.md (project context, legacy)
6
+ - config.yaml (OPSX project context)
6
7
  - spec.md (feature specifications)
7
8
  - proposal.md (change proposals)
8
9
  - spec.md with ADDED/MODIFIED/REMOVED markers (delta specs)
@@ -13,6 +14,7 @@ from __future__ import annotations
13
14
  from pathlib import Path
14
15
  from typing import Any
15
16
 
17
+ import yaml
16
18
  from beartype import beartype
17
19
  from icontract import ensure, require
18
20
 
@@ -55,6 +57,37 @@ class OpenSpecParser:
55
57
  # Return None on parse error (consistent with missing file)
56
58
  return None
57
59
 
60
+ @beartype
61
+ @require(lambda path: isinstance(path, Path), "Path must be Path")
62
+ @ensure(lambda result: result is None or isinstance(result, dict), "Must return dict or None")
63
+ def parse_config_yaml(self, path: Path) -> dict[str, Any] | None:
64
+ """
65
+ Parse OpenSpec OPSX config.yaml (project context).
66
+
67
+ Args:
68
+ path: Path to openspec/config.yaml file
69
+
70
+ Returns:
71
+ Dictionary compatible with project context import:
72
+ - "context": List of context string(s) from context: block
73
+ - "purpose": Optional (empty list if not in YAML)
74
+ - "raw_content": Full file content
75
+ """
76
+ if not path.exists():
77
+ return None
78
+
79
+ try:
80
+ content = path.read_text(encoding="utf-8")
81
+ data = yaml.safe_load(content) or {}
82
+ result: dict[str, Any] = {"purpose": [], "context": [], "raw_content": content}
83
+ if isinstance(data.get("context"), str):
84
+ result["context"] = [data["context"].strip()]
85
+ elif isinstance(data.get("context"), list):
86
+ result["context"] = [str(c).strip() for c in data["context"] if c]
87
+ return result
88
+ except Exception:
89
+ return None
90
+
58
91
  @beartype
59
92
  @require(lambda path: isinstance(path, Path), "Path must be Path")
60
93
  @ensure(lambda result: result is None or isinstance(result, dict), "Must return dict or None")
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
  from datetime import UTC, datetime
11
11
  from pathlib import Path
12
12
  from typing import Any
13
+ from urllib.parse import quote
13
14
 
14
15
  from beartype import beartype
15
16
  from icontract import ensure, require
@@ -157,7 +158,12 @@ def convert_github_issue_to_backlog_item(item_data: dict[str, Any], provider: st
157
158
  @require(lambda provider: isinstance(provider, str) and len(provider) > 0, "Provider must be non-empty string")
158
159
  @ensure(lambda result: isinstance(result, BacklogItem), "Must return BacklogItem")
159
160
  def convert_ado_work_item_to_backlog_item(
160
- item_data: dict[str, Any], provider: str = "ado", custom_mapping_file: str | Path | None = None
161
+ item_data: dict[str, Any],
162
+ provider: str = "ado",
163
+ custom_mapping_file: str | Path | None = None,
164
+ base_url: str | None = None,
165
+ org: str | None = None,
166
+ project_name: str | None = None,
161
167
  ) -> BacklogItem:
162
168
  """
163
169
  Convert Azure DevOps work item data to BacklogItem.
@@ -167,6 +173,10 @@ def convert_ado_work_item_to_backlog_item(
167
173
  Args:
168
174
  item_data: ADO work item data from API (dict)
169
175
  provider: Provider name (default: "ado")
176
+ custom_mapping_file: Optional path to custom ADO field mapping file.
177
+ base_url: ADO base URL (e.g. https://dev.azure.com) for canonical URL.
178
+ org: ADO organization name for canonical URL.
179
+ project_name: ADO project name (URL-encoded in canonical URL) for opening in browser.
170
180
 
171
181
  Returns:
172
182
  BacklogItem instance with normalized fields
@@ -294,10 +304,17 @@ def convert_ado_work_item_to_backlog_item(
294
304
  "_links": item_data.get("_links", {}),
295
305
  }
296
306
 
307
+ canonical_url = None
308
+ if base_url and org and project_name:
309
+ base = base_url.rstrip("/")
310
+ encoded_project = quote(project_name, safe="")
311
+ canonical_url = f"{base}/{org}/{encoded_project}/_workitems/edit/{work_item_id}"
312
+
297
313
  return BacklogItem(
298
314
  id=work_item_id,
299
315
  provider=provider,
300
316
  url=url,
317
+ canonical_url=canonical_url,
301
318
  title=title,
302
319
  body_markdown=body_markdown,
303
320
  state=state,
@@ -14,6 +14,7 @@ SpecFact CLI Architecture:
14
14
  from __future__ import annotations
15
15
 
16
16
  import os
17
+ import re
17
18
  import sys
18
19
  from datetime import datetime
19
20
  from pathlib import Path
@@ -202,6 +203,97 @@ def _build_adapter_kwargs(
202
203
  return kwargs
203
204
 
204
205
 
206
+ def _extract_body_from_block(block: str) -> str:
207
+ """
208
+ Extract **Body** content from a refined export block, handling nested fenced code.
209
+
210
+ The body is wrapped in ```markdown ... ```. If the body itself contains fenced
211
+ code blocks (e.g. ```python ... ```), the closing fence is matched by tracking
212
+ depth: a line that is exactly ``` closes the current fence (body or inner).
213
+ """
214
+ start_marker = "**Body**:"
215
+ fence_open = "```markdown"
216
+ if start_marker not in block or fence_open not in block:
217
+ return ""
218
+ idx = block.find(start_marker)
219
+ rest = block[idx + len(start_marker) :].lstrip()
220
+ if not rest.startswith("```"):
221
+ return ""
222
+ if not rest.startswith(fence_open + "\n") and not rest.startswith(fence_open + "\r\n"):
223
+ return ""
224
+ after_open = rest[len(fence_open) :].lstrip("\n\r")
225
+ if not after_open:
226
+ return ""
227
+ lines = after_open.split("\n")
228
+ body_lines: list[str] = []
229
+ depth = 1
230
+ for line in lines:
231
+ stripped = line.rstrip()
232
+ if stripped == "```":
233
+ if depth == 1:
234
+ break
235
+ depth -= 1
236
+ body_lines.append(line)
237
+ elif stripped.startswith("```") and stripped != "```":
238
+ depth += 1
239
+ body_lines.append(line)
240
+ else:
241
+ body_lines.append(line)
242
+ return "\n".join(body_lines).strip()
243
+
244
+
245
+ def _parse_refined_export_markdown(content: str) -> dict[str, dict[str, Any]]:
246
+ """
247
+ Parse refined export markdown (same format as --export-to-tmp) into id -> fields.
248
+
249
+ Splits by ## Item blocks, extracts **ID**, **Body** (from ```markdown ... ```),
250
+ **Acceptance Criteria**, and optionally title and **Metrics** (story_points,
251
+ business_value, priority). Body extraction is fence-aware so bodies containing
252
+ nested code blocks are parsed correctly. Returns a dict mapping item id to
253
+ parsed fields (body_markdown, acceptance_criteria, title?, story_points?,
254
+ business_value?, priority?).
255
+ """
256
+ result: dict[str, dict[str, Any]] = {}
257
+ blocks = re.split(r"\n## Item \d+:", content)
258
+ for block in blocks:
259
+ block = block.strip()
260
+ if not block or block.startswith("# SpecFact") or "**ID**:" not in block:
261
+ continue
262
+ id_match = re.search(r"\*\*ID\*\*:\s*(.+?)(?:\n|$)", block)
263
+ if not id_match:
264
+ continue
265
+ item_id = id_match.group(1).strip()
266
+ fields: dict[str, Any] = {}
267
+
268
+ fields["body_markdown"] = _extract_body_from_block(block)
269
+
270
+ ac_match = re.search(r"\*\*Acceptance Criteria\*\*:\s*\n(.*?)(?=\n\*\*|\n---|\Z)", block, re.DOTALL)
271
+ if ac_match:
272
+ fields["acceptance_criteria"] = ac_match.group(1).strip() or None
273
+ else:
274
+ fields["acceptance_criteria"] = None
275
+
276
+ first_line = block.split("\n")[0].strip() if block else ""
277
+ if first_line and not first_line.startswith("**"):
278
+ fields["title"] = first_line
279
+
280
+ if "Story Points:" in block:
281
+ sp_match = re.search(r"Story Points:\s*(\d+)", block)
282
+ if sp_match:
283
+ fields["story_points"] = int(sp_match.group(1))
284
+ if "Business Value:" in block:
285
+ bv_match = re.search(r"Business Value:\s*(\d+)", block)
286
+ if bv_match:
287
+ fields["business_value"] = int(bv_match.group(1))
288
+ if "Priority:" in block:
289
+ pri_match = re.search(r"Priority:\s*(\d+)", block)
290
+ if pri_match:
291
+ fields["priority"] = int(pri_match.group(1))
292
+
293
+ result[item_id] = fields
294
+ return result
295
+
296
+
205
297
  def _fetch_backlog_items(
206
298
  adapter_name: str,
207
299
  search_query: str | None = None,
@@ -635,6 +727,8 @@ def refine(
635
727
  export_content += f"## Item {idx}: {item.title}\n\n"
636
728
  export_content += f"**ID**: {item.id}\n"
637
729
  export_content += f"**URL**: {item.url}\n"
730
+ if item.canonical_url:
731
+ export_content += f"**Canonical URL**: {item.canonical_url}\n"
638
732
  export_content += f"**State**: {item.state}\n"
639
733
  export_content += f"**Provider**: {item.provider}\n"
640
734
 
@@ -678,9 +772,65 @@ def refine(
678
772
  raise typer.Exit(1)
679
773
 
680
774
  console.print(f"[bold cyan]Importing refined content from: {import_file}[/bold cyan]")
681
- # TODO: Implement import logic to parse refined content and apply to items
682
- console.print("[yellow]⚠ Import functionality pending implementation[/yellow]")
683
- console.print("[dim]For now, use interactive refinement with --write flag[/dim]")
775
+ raw = import_file.read_text(encoding="utf-8")
776
+ parsed_by_id = _parse_refined_export_markdown(raw)
777
+ if not parsed_by_id:
778
+ console.print(
779
+ "[yellow]No valid item blocks found in import file (expected ## Item N: and **ID**:)[/yellow]"
780
+ )
781
+ raise typer.Exit(1)
782
+
783
+ updated_items: list[BacklogItem] = []
784
+ for item in items:
785
+ if item.id not in parsed_by_id:
786
+ continue
787
+ data = parsed_by_id[item.id]
788
+ item.body_markdown = data.get("body_markdown", item.body_markdown or "")
789
+ if "acceptance_criteria" in data:
790
+ item.acceptance_criteria = data["acceptance_criteria"]
791
+ if data.get("title"):
792
+ item.title = data["title"]
793
+ if "story_points" in data:
794
+ item.story_points = data["story_points"]
795
+ if "business_value" in data:
796
+ item.business_value = data["business_value"]
797
+ if "priority" in data:
798
+ item.priority = data["priority"]
799
+ updated_items.append(item)
800
+
801
+ if not write:
802
+ console.print(f"[green]Would update {len(updated_items)} item(s)[/green]")
803
+ console.print("[dim]Run with --write to apply changes to the backlog[/dim]")
804
+ return
805
+
806
+ writeback_kwargs = _build_adapter_kwargs(
807
+ adapter,
808
+ repo_owner=repo_owner,
809
+ repo_name=repo_name,
810
+ github_token=github_token,
811
+ ado_org=ado_org,
812
+ ado_project=ado_project,
813
+ ado_team=ado_team,
814
+ ado_token=ado_token,
815
+ )
816
+ adapter_instance = adapter_registry.get_adapter(adapter, **writeback_kwargs)
817
+ if not isinstance(adapter_instance, BacklogAdapter):
818
+ console.print("[bold red]✗[/bold red] Adapter does not support backlog updates")
819
+ raise typer.Exit(1)
820
+
821
+ for item in updated_items:
822
+ update_fields_list = ["title", "body_markdown"]
823
+ if item.acceptance_criteria:
824
+ update_fields_list.append("acceptance_criteria")
825
+ if item.story_points is not None:
826
+ update_fields_list.append("story_points")
827
+ if item.business_value is not None:
828
+ update_fields_list.append("business_value")
829
+ if item.priority is not None:
830
+ update_fields_list.append("priority")
831
+ adapter_instance.update_backlog_item(item, update_fields=update_fields_list)
832
+ console.print(f"[green]✓ Updated backlog item: {item.url}[/green]")
833
+ console.print(f"[green]✓ Updated {len(updated_items)} backlog item(s)[/green]")
684
834
  return
685
835
 
686
836
  # Apply limit if specified
@@ -806,6 +956,8 @@ def refine(
806
956
  console.print("\n[bold]Preview Mode: Full Item Details[/bold]")
807
957
  console.print(f"[bold]Title:[/bold] {item.title}")
808
958
  console.print(f"[bold]URL:[/bold] {item.url}")
959
+ if item.canonical_url:
960
+ console.print(f"[bold]Canonical URL:[/bold] {item.canonical_url}")
809
961
  console.print(f"[bold]State:[/bold] {item.state}")
810
962
  console.print(f"[bold]Provider:[/bold] {item.provider}")
811
963
  console.print(f"[bold]Assignee:[/bold] {', '.join(item.assignees) if item.assignees else 'Unassigned'}")
@@ -1227,7 +1379,7 @@ def map_fields(
1227
1379
  import re
1228
1380
  import sys
1229
1381
 
1230
- import questionary
1382
+ import questionary # type: ignore[reportMissingImports]
1231
1383
  import requests
1232
1384
 
1233
1385
  from specfact_cli.backlog.mappers.template_config import FieldMappingConfig
@@ -32,7 +32,11 @@ class BacklogItem(BaseModel):
32
32
  # Identity fields
33
33
  id: str = Field(..., description="Backlog item identifier (provider-specific)")
34
34
  provider: str = Field(..., description="Provider name (github, ado, jira, linear, etc.)")
35
- url: str = Field(..., description="Backlog item URL")
35
+ url: str = Field(..., description="Backlog item URL (API or provider URL)")
36
+ canonical_url: str | None = Field(
37
+ default=None,
38
+ description="User-friendly URL for opening in browser (e.g. ADO: org/project-name/_workitems/edit/id)",
39
+ )
36
40
 
37
41
  # Content fields
38
42
  title: str = Field(..., description="Backlog item title")
@@ -499,7 +499,7 @@ class BridgeConfig(BaseModel):
499
499
  Returns:
500
500
  BridgeConfig for OpenSpec integration with artifact mappings for:
501
501
  - specification: openspec/specs/{feature_id}/spec.md
502
- - project_context: openspec/project.md
502
+ - project_context: openspec/config.yaml (OPSX) if present, else openspec/project.md (legacy)
503
503
  - change_proposal: openspec/changes/{change_name}/proposal.md
504
504
  - change_tasks: openspec/changes/{change_name}/tasks.md
505
505
  - change_spec_delta: openspec/changes/{change_name}/specs/{feature_id}/spec.md
@@ -31,7 +31,7 @@ from rich.progress import Progress
31
31
  from rich.table import Table
32
32
 
33
33
  from specfact_cli.adapters.registry import AdapterRegistry
34
- from specfact_cli.models.bridge import BridgeConfig
34
+ from specfact_cli.models.bridge import AdapterType, BridgeConfig
35
35
  from specfact_cli.runtime import get_configured_console
36
36
  from specfact_cli.sync.bridge_probe import BridgeProbe
37
37
  from specfact_cli.utils.bundle_loader import load_project_bundle, save_project_bundle
@@ -188,6 +188,19 @@ class BridgeSync:
188
188
  msg = "Bridge config not initialized"
189
189
  raise ValueError(msg)
190
190
 
191
+ base_path = self.repo_path
192
+ if self.bridge_config.external_base_path is not None:
193
+ base_path = self.bridge_config.external_base_path
194
+
195
+ if artifact_key == "project_context" and self.bridge_config.adapter == AdapterType.OPENSPEC:
196
+ config_yaml = base_path / "openspec" / "config.yaml"
197
+ project_md = base_path / "openspec" / "project.md"
198
+ if config_yaml.exists():
199
+ return config_yaml
200
+ if project_md.exists():
201
+ return project_md
202
+ return project_md
203
+
191
204
  context = {
192
205
  "feature_id": feature_id,
193
206
  "bundle_name": bundle_name,
@@ -1,6 +0,0 @@
1
- """
2
- SpecFact CLI - Spec→Contract→Sentinel tool for contract-driven development.
3
- """
4
-
5
- # Define the package version (kept in sync with pyproject.toml and setup.py)
6
- __version__ = "0.26.7"
File without changes
File without changes