specfact-cli 0.40.1__tar.gz → 0.40.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/PKG-INFO +3 -1
  2. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/README.md +2 -0
  3. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/pyproject.toml +1 -2
  4. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/__init__.py +1 -1
  5. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/__init__.py +1 -1
  6. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/adapters/ado.py +15 -0
  7. specfact_cli-0.40.3/src/specfact_cli/backlog/__init__.py +14 -0
  8. specfact_cli-0.40.3/src/specfact_cli/backlog/adapters/__init__.py +8 -0
  9. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/common/logger_setup.py +11 -5
  10. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/common/logging_utils.py +7 -1
  11. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/groups/__init__.py +1 -3
  12. specfact_cli-0.40.3/src/specfact_cli/groups/member_group.py +60 -0
  13. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modules/module_registry/module-package.yaml +3 -3
  14. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modules/module_registry/src/commands.py +22 -4
  15. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/module_discovery.py +7 -0
  16. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/module_installer.py +1 -1
  17. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/module_packages.py +22 -18
  18. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/ide_setup.py +0 -4
  19. specfact_cli-0.40.3/src/specfact_cli/validation/__init__.py +14 -0
  20. specfact_cli-0.40.3/src/specfact_cli/validation/command_audit.py +165 -0
  21. specfact_cli-0.40.1/resources/prompts/specfact.backlog-add.md +0 -90
  22. specfact_cli-0.40.1/resources/prompts/specfact.backlog-daily.md +0 -125
  23. specfact_cli-0.40.1/resources/prompts/specfact.backlog-refine.md +0 -557
  24. specfact_cli-0.40.1/resources/prompts/specfact.sync-backlog.md +0 -557
  25. specfact_cli-0.40.1/resources/templates/backlog/field_mappings/ado_agile.yaml +0 -24
  26. specfact_cli-0.40.1/resources/templates/backlog/field_mappings/ado_default.yaml +0 -24
  27. specfact_cli-0.40.1/resources/templates/backlog/field_mappings/ado_kanban.yaml +0 -26
  28. specfact_cli-0.40.1/resources/templates/backlog/field_mappings/ado_safe.yaml +0 -29
  29. specfact_cli-0.40.1/resources/templates/backlog/field_mappings/ado_scrum.yaml +0 -24
  30. specfact_cli-0.40.1/resources/templates/backlog/frameworks/safe/safe_feature_v1.yaml +0 -26
  31. specfact_cli-0.40.1/resources/templates/backlog/personas/developer/developer_task_v1.yaml +0 -28
  32. specfact_cli-0.40.1/src/specfact_cli/backlog/__init__.py +0 -24
  33. specfact_cli-0.40.1/src/specfact_cli/backlog/adapters/__init__.py +0 -13
  34. specfact_cli-0.40.1/src/specfact_cli/backlog/adapters/local_yaml_adapter.py +0 -177
  35. specfact_cli-0.40.1/src/specfact_cli/backlog/ai_refiner.py +0 -523
  36. specfact_cli-0.40.1/src/specfact_cli/backlog/format_detector.py +0 -50
  37. specfact_cli-0.40.1/src/specfact_cli/backlog/formats/__init__.py +0 -14
  38. specfact_cli-0.40.1/src/specfact_cli/backlog/formats/base.py +0 -98
  39. specfact_cli-0.40.1/src/specfact_cli/backlog/formats/markdown_format.py +0 -130
  40. specfact_cli-0.40.1/src/specfact_cli/backlog/formats/structured_format.py +0 -97
  41. specfact_cli-0.40.1/src/specfact_cli/backlog/template_detector.py +0 -288
  42. specfact_cli-0.40.1/src/specfact_cli/commands/backlog_commands.py +0 -18
  43. specfact_cli-0.40.1/src/specfact_cli/groups/backlog_group.py +0 -63
  44. specfact_cli-0.40.1/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -22
  45. specfact_cli-0.40.1/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -21
  46. specfact_cli-0.40.1/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -20
  47. specfact_cli-0.40.1/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -19
  48. specfact_cli-0.40.1/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -23
  49. specfact_cli-0.40.1/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -24
  50. specfact_cli-0.40.1/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -21
  51. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/.gitignore +0 -0
  52. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/LICENSE +0 -0
  53. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/keys/README.md +0 -0
  54. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/keys/module-signing-public.pem +0 -0
  55. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/mappings/node-async.yaml +0 -0
  56. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/mappings/python-async.yaml +0 -0
  57. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/mappings/speckit-default.yaml +0 -0
  58. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/prompts/shared/cli-enforcement.md +0 -0
  59. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/prompts/specfact.01-import.md +0 -0
  60. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/prompts/specfact.02-plan.md +0 -0
  61. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/prompts/specfact.03-review.md +0 -0
  62. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/prompts/specfact.04-sdd.md +0 -0
  63. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/prompts/specfact.05-enforce.md +0 -0
  64. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/prompts/specfact.06-sync.md +0 -0
  65. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/prompts/specfact.07-contracts.md +0 -0
  66. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/prompts/specfact.compare.md +0 -0
  67. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/prompts/specfact.validate.md +0 -0
  68. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/schemas/deviation.schema.json +0 -0
  69. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/schemas/plan.schema.json +0 -0
  70. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/schemas/protocol.schema.json +0 -0
  71. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/templates/github-action.yml.j2 +0 -0
  72. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/templates/persona/architect.md.j2 +0 -0
  73. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/templates/persona/developer.md.j2 +0 -0
  74. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/templates/persona/product-owner.md.j2 +0 -0
  75. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/templates/plan.bundle.yaml.j2 +0 -0
  76. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/templates/policies/kanban.yaml +0 -0
  77. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/templates/policies/mixed.yaml +0 -0
  78. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/templates/policies/safe.yaml +0 -0
  79. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/templates/policies/scrum.yaml +0 -0
  80. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/templates/pr-template.md.j2 +0 -0
  81. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/templates/protocol.yaml.j2 +0 -0
  82. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/resources/templates/telemetry.yaml.example +0 -0
  83. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/__main__.py +0 -0
  84. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/adapters/__init__.py +0 -0
  85. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/adapters/backlog_base.py +0 -0
  86. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/adapters/base.py +0 -0
  87. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/adapters/github.py +0 -0
  88. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/adapters/openspec.py +0 -0
  89. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/adapters/openspec_parser.py +0 -0
  90. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/adapters/registry.py +0 -0
  91. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/adapters/speckit.py +0 -0
  92. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/agents/__init__.py +0 -0
  93. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/agents/analyze_agent.py +0 -0
  94. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/agents/base.py +0 -0
  95. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/agents/plan_agent.py +0 -0
  96. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/agents/registry.py +0 -0
  97. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/agents/sync_agent.py +0 -0
  98. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/analyzers/__init__.py +0 -0
  99. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
  100. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
  101. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
  102. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
  103. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
  104. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
  105. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
  106. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
  107. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
  108. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/backlog/adapters/base.py +0 -0
  109. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/backlog/converter.py +0 -0
  110. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/backlog/filters.py +0 -0
  111. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
  112. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
  113. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/backlog/mappers/base.py +0 -0
  114. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
  115. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
  116. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/cli.py +0 -0
  117. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/__init__.py +0 -0
  118. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/_bundle_shim.py +0 -0
  119. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/analyze.py +0 -0
  120. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/contract_cmd.py +0 -0
  121. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/drift.py +0 -0
  122. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/enforce.py +0 -0
  123. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/generate.py +0 -0
  124. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/import_cmd.py +0 -0
  125. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/init.py +0 -0
  126. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/migrate.py +0 -0
  127. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/plan.py +0 -0
  128. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/project_cmd.py +0 -0
  129. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/repro.py +0 -0
  130. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/sdd.py +0 -0
  131. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/spec.py +0 -0
  132. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/sync.py +0 -0
  133. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/update.py +0 -0
  134. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/commands/validate.py +0 -0
  135. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/common/__init__.py +0 -0
  136. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/common/bundle_factory.py +0 -0
  137. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/common/text_utils.py +0 -0
  138. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/common/utils.py +0 -0
  139. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/comparators/__init__.py +0 -0
  140. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/comparators/plan_comparator.py +0 -0
  141. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/contracts/__init__.py +0 -0
  142. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/contracts/crosshair_props.py +0 -0
  143. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/contracts/module_interface.py +0 -0
  144. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
  145. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
  146. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/generators/__init__.py +0 -0
  147. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/generators/contract_generator.py +0 -0
  148. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/generators/openapi_extractor.py +0 -0
  149. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/generators/persona_exporter.py +0 -0
  150. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/generators/plan_generator.py +0 -0
  151. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/generators/protocol_generator.py +0 -0
  152. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/generators/report_generator.py +0 -0
  153. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/generators/task_generator.py +0 -0
  154. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/generators/test_to_openapi.py +0 -0
  155. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/generators/workflow_generator.py +0 -0
  156. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/groups/codebase_group.py +0 -0
  157. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/groups/govern_group.py +0 -0
  158. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/groups/project_group.py +0 -0
  159. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/groups/spec_group.py +0 -0
  160. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/importers/__init__.py +0 -0
  161. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/importers/speckit_converter.py +0 -0
  162. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/importers/speckit_scanner.py +0 -0
  163. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/integrations/__init__.py +0 -0
  164. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/integrations/specmatic.py +0 -0
  165. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/merge/__init__.py +0 -0
  166. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/merge/resolver.py +0 -0
  167. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/migrations/__init__.py +0 -0
  168. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/migrations/plan_migrator.py +0 -0
  169. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/__init__.py +0 -0
  170. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/backlog_item.py +0 -0
  171. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/bridge.py +0 -0
  172. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/capabilities.py +0 -0
  173. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/change.py +0 -0
  174. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/contract.py +0 -0
  175. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/deviation.py +0 -0
  176. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/dor_config.py +0 -0
  177. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/enforcement.py +0 -0
  178. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/module_package.py +0 -0
  179. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/persona_template.py +0 -0
  180. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/plan.py +0 -0
  181. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/project.py +0 -0
  182. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/protocol.py +0 -0
  183. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/quality.py +0 -0
  184. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/sdd.py +0 -0
  185. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/source_tracking.py +0 -0
  186. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/task.py +0 -0
  187. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/models/validation.py +0 -0
  188. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modes/__init__.py +0 -0
  189. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modes/detector.py +0 -0
  190. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modes/router.py +0 -0
  191. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modules/__init__.py +0 -0
  192. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modules/_bundle_import.py +0 -0
  193. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modules/init/module-package.yaml +0 -0
  194. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modules/init/src/__init__.py +0 -0
  195. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modules/init/src/app.py +0 -0
  196. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modules/init/src/commands.py +0 -0
  197. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modules/init/src/first_run_selection.py +0 -0
  198. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modules/module_io_shim.py +0 -0
  199. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modules/module_registry/src/__init__.py +0 -0
  200. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modules/module_registry/src/app.py +0 -0
  201. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modules/upgrade/module-package.yaml +0 -0
  202. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modules/upgrade/src/__init__.py +0 -0
  203. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modules/upgrade/src/app.py +0 -0
  204. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/modules/upgrade/src/commands.py +0 -0
  205. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/parsers/__init__.py +0 -0
  206. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/parsers/persona_importer.py +0 -0
  207. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/__init__.py +0 -0
  208. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/alias_manager.py +0 -0
  209. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/bootstrap.py +0 -0
  210. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/bridge_registry.py +0 -0
  211. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/crypto_validator.py +0 -0
  212. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/custom_registries.py +0 -0
  213. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/dependency_resolver.py +0 -0
  214. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/extension_registry.py +0 -0
  215. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/help_cache.py +0 -0
  216. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/marketplace_client.py +0 -0
  217. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/metadata.py +0 -0
  218. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/module_grouping.py +0 -0
  219. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/module_lifecycle.py +0 -0
  220. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/module_security.py +0 -0
  221. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/module_state.py +0 -0
  222. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/registry/registry.py +0 -0
  223. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/resources/semgrep/async.yml +0 -0
  224. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
  225. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
  226. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/runtime.py +0 -0
  227. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/sync/__init__.py +0 -0
  228. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/sync/bridge_probe.py +0 -0
  229. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/sync/bridge_sync.py +0 -0
  230. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/sync/bridge_watch.py +0 -0
  231. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/sync/change_detector.py +0 -0
  232. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/sync/code_to_spec.py +0 -0
  233. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/sync/drift_detector.py +0 -0
  234. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/sync/repository_sync.py +0 -0
  235. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/sync/spec_to_code.py +0 -0
  236. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/sync/spec_to_tests.py +0 -0
  237. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/sync/watcher.py +0 -0
  238. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
  239. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/telemetry.py +0 -0
  240. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/templates/__init__.py +0 -0
  241. {specfact_cli-0.40.1/resources/templates/backlog → specfact_cli-0.40.3/src/specfact_cli/templates}/defaults/defect_v1.yaml +0 -0
  242. {specfact_cli-0.40.1/resources/templates/backlog → specfact_cli-0.40.3/src/specfact_cli/templates}/defaults/enabler_v1.yaml +0 -0
  243. {specfact_cli-0.40.1/resources/templates/backlog → specfact_cli-0.40.3/src/specfact_cli/templates}/defaults/spike_v1.yaml +0 -0
  244. {specfact_cli-0.40.1/resources/templates/backlog → specfact_cli-0.40.3/src/specfact_cli/templates}/defaults/user_story_v1.yaml +0 -0
  245. {specfact_cli-0.40.1/resources/templates/backlog → specfact_cli-0.40.3/src/specfact_cli/templates}/frameworks/scrum/user_story_v1.yaml +0 -0
  246. {specfact_cli-0.40.1/resources/templates/backlog → specfact_cli-0.40.3/src/specfact_cli/templates}/personas/product-owner/user_story_v1.yaml +0 -0
  247. {specfact_cli-0.40.1/resources/templates/backlog → specfact_cli-0.40.3/src/specfact_cli/templates}/providers/ado/work_item_v1.yaml +0 -0
  248. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/templates/registry.py +0 -0
  249. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/templates/specification_templates.py +0 -0
  250. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/__init__.py +0 -0
  251. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
  252. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/auth_tokens.py +0 -0
  253. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/bundle_converters.py +0 -0
  254. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/bundle_loader.py +0 -0
  255. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/code_change_detector.py +0 -0
  256. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/console.py +0 -0
  257. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/content_sanitizer.py +0 -0
  258. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/context_detection.py +0 -0
  259. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/enrichment_context.py +0 -0
  260. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/enrichment_parser.py +0 -0
  261. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/env_manager.py +0 -0
  262. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/feature_keys.py +0 -0
  263. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/git.py +0 -0
  264. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/github_annotations.py +0 -0
  265. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/incremental_check.py +0 -0
  266. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/metadata.py +0 -0
  267. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/optional_deps.py +0 -0
  268. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/performance.py +0 -0
  269. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/persona_ownership.py +0 -0
  270. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/progress.py +0 -0
  271. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
  272. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/prompts.py +0 -0
  273. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/sdd_discovery.py +0 -0
  274. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/source_scanner.py +0 -0
  275. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/startup_checks.py +0 -0
  276. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/structure.py +0 -0
  277. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/structured_io.py +0 -0
  278. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/suggestions.py +0 -0
  279. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/terminal.py +0 -0
  280. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/utils/yaml_utils.py +0 -0
  281. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/__init__.py +0 -0
  282. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/agile_validation.py +0 -0
  283. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
  284. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/cli_first_validator.py +0 -0
  285. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/contract_validator.py +0 -0
  286. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/fsm.py +0 -0
  287. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/repro_checker.py +0 -0
  288. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/schema.py +0 -0
  289. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
  290. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
  291. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
  292. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
  293. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
  294. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
  295. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
  296. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
  297. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
  298. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
  299. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
  300. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
  301. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
  302. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/models.py +0 -0
  303. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
  304. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
  305. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
  306. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/versioning/__init__.py +0 -0
  307. {specfact_cli-0.40.1 → specfact_cli-0.40.3}/src/specfact_cli/versioning/analyzer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specfact-cli
3
- Version: 0.40.1
3
+ Version: 0.40.3
4
4
  Summary: The swiss knife CLI for agile DevOps teams. Keep backlog, specs, tests, and code in sync with validation and contract enforcement for new projects and long-lived codebases.
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
@@ -353,6 +353,8 @@ As of `0.40.0`, flat root commands are removed. Use grouped commands:
353
353
  ### Backlog Bridge (60 seconds)
354
354
 
355
355
  SpecFact's USP is closing the drift gap between **backlog -> specs -> code**.
356
+ These commands require the backlog bundle to be installed first, for example via
357
+ `specfact init --profile backlog-team` or `specfact init --install backlog`.
356
358
 
357
359
  ```bash
358
360
  # 1) Initialize backlog config + field mapping
@@ -74,6 +74,8 @@ As of `0.40.0`, flat root commands are removed. Use grouped commands:
74
74
  ### Backlog Bridge (60 seconds)
75
75
 
76
76
  SpecFact's USP is closing the drift gap between **backlog -> specs -> code**.
77
+ These commands require the backlog bundle to be installed first, for example via
78
+ `specfact init --profile backlog-team` or `specfact init --install backlog`.
77
79
 
78
80
  ```bash
79
81
  # 1) Initialize backlog config + field mapping
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "specfact-cli"
7
- version = "0.40.1"
7
+ version = "0.40.3"
8
8
  description = "The swiss knife CLI for agile DevOps teams. Keep backlog, specs, tests, and code in sync with validation and contract enforcement for new projects and long-lived codebases."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -388,7 +388,6 @@ sources = ["src"]
388
388
  "resources/schemas" = "specfact_cli/resources/schemas"
389
389
  "resources/mappings" = "specfact_cli/resources/mappings"
390
390
  "resources/keys" = "specfact_cli/resources/keys"
391
- "modules/backlog-core" = "specfact_cli/modules/backlog-core"
392
391
  "modules/bundle-mapper" = "specfact_cli/modules/bundle-mapper"
393
392
  # Note: resources/semgrep files are in src/specfact_cli/resources/semgrep/ and are automatically included
394
393
 
@@ -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.40.1"
6
+ __version__ = "0.40.3"
@@ -42,6 +42,6 @@ def _bootstrap_bundle_paths() -> None:
42
42
 
43
43
  _bootstrap_bundle_paths()
44
44
 
45
- __version__ = "0.40.1"
45
+ __version__ = "0.40.3"
46
46
 
47
47
  __all__ = ["__version__"]
@@ -3414,6 +3414,21 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
3414
3414
  }
3415
3415
  )
3416
3416
 
3417
+ provider_fields = payload.get("provider_fields")
3418
+ provider_field_values = provider_fields.get("fields") if isinstance(provider_fields, dict) else None
3419
+ if isinstance(provider_field_values, dict):
3420
+ for field_name, field_value in provider_field_values.items():
3421
+ normalized_field = str(field_name).strip()
3422
+ if not normalized_field:
3423
+ continue
3424
+ patch_document.append(
3425
+ {
3426
+ "op": "add",
3427
+ "path": f"/fields/{normalized_field}",
3428
+ "value": field_value,
3429
+ }
3430
+ )
3431
+
3417
3432
  sprint = str(payload.get("sprint") or "").strip()
3418
3433
  if sprint:
3419
3434
  patch_document.append(
@@ -0,0 +1,14 @@
1
+ """Shared backlog conversion helpers retained by core adapters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from specfact_cli.backlog.converter import (
6
+ convert_ado_work_item_to_backlog_item,
7
+ convert_github_issue_to_backlog_item,
8
+ )
9
+
10
+
11
+ __all__ = [
12
+ "convert_ado_work_item_to_backlog_item",
13
+ "convert_github_issue_to_backlog_item",
14
+ ]
@@ -0,0 +1,8 @@
1
+ """Backlog adapter contracts retained by core provider integrations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from specfact_cli.backlog.adapters.base import BacklogAdapter
6
+
7
+
8
+ __all__ = ["BacklogAdapter"]
@@ -396,6 +396,7 @@ class LoggerSetup:
396
396
  use_rotating_file: bool = True,
397
397
  append_mode: bool = True,
398
398
  preserve_test_format: bool = False,
399
+ emit_to_console: bool = True,
399
400
  ) -> logging.Logger:
400
401
  """
401
402
  Creates a new logger or returns an existing one with the specified configuration.
@@ -513,15 +514,20 @@ class LoggerSetup:
513
514
  with contextlib.suppress(Exception):
514
515
  logger.info("[LoggerSetup] File logger initialized: %s", log_file_path)
515
516
  else:
516
- # If no log file is specified, set up a listener with a console handler
517
+ # If no log file is specified, stream to console only when explicitly requested.
517
518
  log_queue = Queue(-1)
518
519
  cls._log_queues[logger_name] = log_queue
519
520
 
520
- console_handler = logging.StreamHandler(_safe_console_stream())
521
- console_handler.setFormatter(log_format)
522
- console_handler.setLevel(level)
521
+ sink_handler: logging.Handler
522
+ if emit_to_console:
523
+ sink_handler = logging.StreamHandler(_safe_console_stream())
524
+ sink_handler.setFormatter(log_format)
525
+ sink_handler.setLevel(level)
526
+ else:
527
+ sink_handler = logging.NullHandler()
528
+ sink_handler.setLevel(level)
523
529
 
524
- listener = QueueListener(log_queue, console_handler, respect_handler_level=True)
530
+ listener = QueueListener(log_queue, sink_handler, respect_handler_level=True)
525
531
  listener.start()
526
532
  cls._log_listeners[logger_name] = listener
527
533
 
@@ -38,4 +38,10 @@ def _try_common_logger(name: str, level: str) -> logging.Logger | None:
38
38
  from specfact_cli.common.logger_setup import LoggerSetup # type: ignore[import]
39
39
  except ImportError:
40
40
  return None
41
- return LoggerSetup.create_logger(name, log_level=level)
41
+ try:
42
+ from specfact_cli.runtime import is_debug_mode # type: ignore[import]
43
+
44
+ emit_to_console = is_debug_mode()
45
+ except ImportError:
46
+ emit_to_console = False
47
+ return LoggerSetup.create_logger(name, log_level=level, emit_to_console=emit_to_console)
@@ -1,8 +1,7 @@
1
- """Category group commands: project, backlog, code, spec, govern."""
1
+ """Category group commands: project, code, spec, govern."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from specfact_cli.groups.backlog_group import app as backlog_app
6
5
  from specfact_cli.groups.codebase_group import app as codebase_app
7
6
  from specfact_cli.groups.govern_group import app as govern_app
8
7
  from specfact_cli.groups.project_group import app as project_app
@@ -10,7 +9,6 @@ from specfact_cli.groups.spec_group import app as spec_app
10
9
 
11
10
 
12
11
  __all__ = [
13
- "backlog_app",
14
12
  "codebase_app",
15
13
  "govern_app",
16
14
  "project_app",
@@ -0,0 +1,60 @@
1
+ """Generic category group builder for bundle-owned command surfaces."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+
7
+ import typer
8
+ from beartype import beartype
9
+ from icontract import ensure, require
10
+
11
+ from specfact_cli.registry.registry import CommandRegistry
12
+
13
+
14
+ @require(lambda app: app is not None)
15
+ @beartype
16
+ def _register_members(app: typer.Typer, members: Sequence[tuple[str, str]]) -> int:
17
+ """Register member module sub-apps and return how many were added."""
18
+ added = 0
19
+ for display_name, cmd_name in members:
20
+ try:
21
+ member_app = CommandRegistry.get_module_typer(cmd_name)
22
+ if member_app is not None:
23
+ app.add_typer(member_app, name=display_name)
24
+ added += 1
25
+ except ValueError:
26
+ continue
27
+ return added
28
+
29
+
30
+ @require(lambda name: isinstance(name, str) and len(name) > 0)
31
+ @require(lambda help_text: isinstance(help_text, str) and len(help_text) > 0)
32
+ @ensure(lambda result: isinstance(result, typer.Typer))
33
+ @beartype
34
+ def build_member_group(
35
+ *,
36
+ name: str,
37
+ help_text: str,
38
+ members: Sequence[tuple[str, str]],
39
+ flatten_same_name: str | None = None,
40
+ install_hint_module: str | None = None,
41
+ ) -> typer.Typer:
42
+ """Build a lazy category group from registered member modules."""
43
+ app = typer.Typer(name=name, help=help_text, no_args_is_help=True)
44
+ added = _register_members(app, members)
45
+
46
+ if added == 0 and install_hint_module:
47
+ placeholder = typer.Typer(help=f"{help_text} (module not loaded).")
48
+
49
+ @placeholder.command("install")
50
+ def _install_hint() -> None:
51
+ from specfact_cli.utils.prompts import print_warning
52
+
53
+ print_warning(f"No {name} module loaded. Install with: specfact module install {install_hint_module}")
54
+
55
+ app.add_typer(placeholder, name=name)
56
+
57
+ if flatten_same_name:
58
+ app._specfact_flatten_same_name = flatten_same_name
59
+
60
+ return app
@@ -1,5 +1,5 @@
1
1
  name: module-registry
2
- version: 0.1.9
2
+ version: 0.1.10
3
3
  commands:
4
4
  - module
5
5
  category: core
@@ -17,5 +17,5 @@ publisher:
17
17
  description: 'Manage modules: search, list, show, install, and upgrade.'
18
18
  license: Apache-2.0
19
19
  integrity:
20
- checksum: sha256:d8d4103bfe44bc638fd5affa2734bbb063f9c86f2873055f745beca9ee0a9db3
21
- signature: OJtCXdfZfnLZhB543+ODtFRXgyYamZk6xrvLfHubE+kwU+jCPWaZDJ83YqheuR7kQlqMlRue5UZb3DbOu4pwBQ==
20
+ checksum: sha256:85e40c4c083982f0bab2bde2a27a08a4e6832fe6d93ae3d62c4659c138fd6295
21
+ signature: INFP+nx7iPCqZPnGIwB39L6GpckB16+EqPwaPW/kwwBtlLBX9RaZ1DcMbhu5f6tPqjA9sETRMBtrQ5GiBbVfCQ==
@@ -7,6 +7,7 @@ import shutil
7
7
  from pathlib import Path
8
8
 
9
9
  import typer
10
+ import yaml
10
11
  from beartype import beartype
11
12
  from rich.console import Console
12
13
  from rich.table import Table
@@ -39,6 +40,20 @@ app = typer.Typer(help="Manage marketplace modules")
39
40
  console = Console()
40
41
 
41
42
 
43
+ def _read_installed_module_version(module_dir: Path) -> str:
44
+ """Read installed module version from its manifest, if available."""
45
+ manifest_path = module_dir / "module-package.yaml"
46
+ if not manifest_path.exists():
47
+ return "unknown"
48
+ try:
49
+ loaded = yaml.safe_load(manifest_path.read_text(encoding="utf-8"))
50
+ except Exception:
51
+ return "unknown"
52
+ if not isinstance(loaded, dict):
53
+ return "unknown"
54
+ return str(loaded.get("version", "unknown"))
55
+
56
+
42
57
  def _publisher_from_module_id(module_id: str) -> str:
43
58
  """Extract normalized publisher namespace from module id."""
44
59
  return module_id.split("/", 1)[0].strip().lower() if "/" in module_id else ""
@@ -803,19 +818,22 @@ def upgrade(
803
818
  # If module isn't discovered locally, still attempt marketplace install/upgrade by ID.
804
819
  target_ids = [prefixed]
805
820
 
806
- upgraded: list[str] = []
821
+ upgraded: list[tuple[str, str, str]] = []
807
822
  failed: list[str] = []
808
823
  for target in target_ids:
809
824
  try:
810
825
  module_id = target if "/" in target else f"specfact/{target}"
811
- install_module(module_id, reinstall=True)
812
- upgraded.append(module_id)
826
+ previous_version = str(by_id.get(target, {}).get("version", "unknown"))
827
+ installed_path = install_module(module_id, reinstall=True)
828
+ upgraded.append((module_id, previous_version, _read_installed_module_version(installed_path)))
813
829
  except Exception as exc:
814
830
  console.print(f"[red]Failed upgrading {target}: {exc}[/red]")
815
831
  failed.append(target)
816
832
 
817
833
  if upgraded:
818
- console.print(f"[green]Upgraded[/green] {', '.join(upgraded)}")
834
+ console.print("[green]Upgraded:[/green]")
835
+ for module_id, previous_version, new_version in upgraded:
836
+ console.print(f" {module_id}: {previous_version} -> {new_version}")
819
837
  if failed:
820
838
  raise typer.Exit(1)
821
839
 
@@ -56,7 +56,14 @@ def discover_all_modules(
56
56
  effective_custom_root = custom_root or CUSTOM_MODULES_ROOT
57
57
 
58
58
  roots: list[tuple[str, Path]] = [("builtin", effective_builtin_root)]
59
+ project_matches_user_root = False
59
60
  if effective_project_root is not None:
61
+ try:
62
+ project_matches_user_root = effective_project_root.resolve() == effective_user_root.resolve()
63
+ except OSError:
64
+ project_matches_user_root = effective_project_root == effective_user_root
65
+
66
+ if effective_project_root is not None and not project_matches_user_root:
60
67
  roots.append(("project", effective_project_root))
61
68
  roots.extend(
62
69
  [
@@ -730,7 +730,7 @@ def install_module(
730
730
  dependency_manifest = target_root / dependency_name / "module-package.yaml"
731
731
  if dependency_manifest.exists():
732
732
  dependency_version = _installed_dependency_version(dependency_manifest)
733
- logger.warning(
733
+ logger.info(
734
734
  "Dependency %s already satisfied (version %s)", dependency_module_id, dependency_version
735
735
  )
736
736
  continue
@@ -879,14 +879,24 @@ def get_installed_bundles(
879
879
 
880
880
  # Bundle name -> (group_name, help_str, build_app_fn) for conditional category mounting.
881
881
  def _build_bundle_to_group() -> dict[str, tuple[str, str, Any]]:
882
- from specfact_cli.groups.backlog_group import build_app as build_backlog_app
883
882
  from specfact_cli.groups.codebase_group import build_app as build_codebase_app
884
883
  from specfact_cli.groups.govern_group import build_app as build_govern_app
884
+ from specfact_cli.groups.member_group import build_member_group
885
885
  from specfact_cli.groups.project_group import build_app as build_project_app
886
886
  from specfact_cli.groups.spec_group import build_app as build_spec_app
887
887
 
888
888
  return {
889
- "specfact-backlog": ("backlog", "Backlog and policy commands.", build_backlog_app),
889
+ "specfact-backlog": (
890
+ "backlog",
891
+ "Backlog and policy commands.",
892
+ lambda: build_member_group(
893
+ name="backlog",
894
+ help_text="Backlog and policy commands.",
895
+ members=(("backlog", "backlog"), ("policy", "policy")),
896
+ flatten_same_name="backlog",
897
+ install_hint_module="nold-ai/specfact-backlog",
898
+ ),
899
+ ),
890
900
  "specfact-codebase": (
891
901
  "code",
892
902
  "Codebase quality commands: analyze, drift, validate, repro.",
@@ -1171,26 +1181,20 @@ def register_module_package_commands(
1171
1181
  if category_grouping_enabled:
1172
1182
  _mount_installed_category_groups(packages, enabled_map)
1173
1183
  discovered_count = protocol_full + protocol_partial + protocol_legacy
1174
- if discovered_count and (protocol_partial > 0 or protocol_legacy > 0):
1175
- print_warning(
1176
- "Module compatibility check: "
1177
- f"{protocol_full + protocol_partial}/{discovered_count} compliant "
1178
- f"(full={protocol_full}, partial={protocol_partial}, legacy={protocol_legacy})."
1184
+ if discovered_count and (protocol_partial > 0 or protocol_legacy > 0) and is_debug_mode():
1185
+ logger.info(
1186
+ "Module compatibility check: %s/%s compliant (full=%s, partial=%s, legacy=%s)",
1187
+ protocol_full + protocol_partial,
1188
+ discovered_count,
1189
+ protocol_full,
1190
+ protocol_partial,
1191
+ protocol_legacy,
1179
1192
  )
1180
1193
  if partial_modules:
1181
1194
  partial_desc = ", ".join(f"{name} ({'/'.join(ops)})" for name, ops in sorted(partial_modules))
1182
- print_warning(f"Partially compliant modules: {partial_desc}")
1195
+ logger.info("Partially compliant modules: %s", partial_desc)
1183
1196
  if legacy_modules:
1184
- print_warning(f"Legacy modules: {', '.join(sorted(set(legacy_modules)))}")
1185
- if is_debug_mode():
1186
- logger.info(
1187
- "Protocol-compliant: %s/%s modules (Full=%s, Partial=%s, Legacy=%s)",
1188
- protocol_full + protocol_partial,
1189
- discovered_count,
1190
- protocol_full,
1191
- protocol_partial,
1192
- protocol_legacy,
1193
- )
1197
+ logger.info("Legacy modules: %s", ", ".join(sorted(set(legacy_modules))))
1194
1198
  for module_id, reason in skipped:
1195
1199
  logger.debug("Skipped module '%s': %s", module_id, reason)
1196
1200
 
@@ -121,10 +121,6 @@ SPECFACT_COMMANDS = [
121
121
  "specfact.06-sync",
122
122
  "specfact.07-contracts",
123
123
  "specfact.compare",
124
- "specfact.sync-backlog",
125
- "specfact.backlog-daily",
126
- "specfact.backlog-refine",
127
- "specfact.backlog-add",
128
124
  "specfact.validate",
129
125
  ]
130
126
 
@@ -0,0 +1,14 @@
1
+ """Validation helpers for release and command-surface audits."""
2
+
3
+ from specfact_cli.validation.command_audit import (
4
+ CommandAuditCase,
5
+ build_command_audit_cases,
6
+ official_marketplace_module_ids,
7
+ )
8
+
9
+
10
+ __all__ = [
11
+ "CommandAuditCase",
12
+ "build_command_audit_cases",
13
+ "official_marketplace_module_ids",
14
+ ]
@@ -0,0 +1,165 @@
1
+ """Helpers for command-package runtime validation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import os
7
+ import sys
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+ from typing import Literal
11
+
12
+
13
+ AuditMode = Literal["help-only", "fixture-backed", "dry-run"]
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class CommandAuditCase:
18
+ """One command-path audit case."""
19
+
20
+ command_path: str
21
+ argv: tuple[str, ...]
22
+ phase: str
23
+ mode: AuditMode
24
+ owner: str
25
+
26
+
27
+ def _resolve_modules_repo_root() -> Path | None:
28
+ configured = os.environ.get("SPECFACT_MODULES_REPO", "").strip()
29
+ if configured:
30
+ candidate = Path(configured).expanduser().resolve()
31
+ if candidate.exists():
32
+ return candidate
33
+
34
+ current = Path(__file__).resolve()
35
+ for parent in current.parents:
36
+ sibling = parent.parent / "specfact-cli-modules"
37
+ if sibling.exists():
38
+ return sibling.resolve()
39
+ direct = parent / "specfact-cli-modules"
40
+ if direct.exists():
41
+ return direct.resolve()
42
+ return None
43
+
44
+
45
+ def _ensure_bundle_sources_on_sys_path() -> None:
46
+ modules_repo = _resolve_modules_repo_root()
47
+ if modules_repo is None:
48
+ return
49
+ packages_root = modules_repo / "packages"
50
+ if not packages_root.exists():
51
+ return
52
+ for bundle_src in packages_root.glob("*/src"):
53
+ bundle_src_str = str(bundle_src.resolve())
54
+ if bundle_src_str not in sys.path:
55
+ sys.path.insert(0, bundle_src_str)
56
+
57
+
58
+ def _command_info_name(command_info: object) -> str:
59
+ explicit_name = getattr(command_info, "name", None)
60
+ if isinstance(explicit_name, str) and explicit_name:
61
+ return explicit_name
62
+ callback = getattr(command_info, "callback", None)
63
+ callback_name = getattr(callback, "__name__", "")
64
+ return callback_name.replace("_", "-") if callback_name else ""
65
+
66
+
67
+ def _collect_typer_paths(app: object, prefix: str) -> set[str]:
68
+ paths: set[str] = set()
69
+
70
+ for command_info in list(getattr(app, "registered_commands", [])):
71
+ command_name = _command_info_name(command_info)
72
+ if command_name:
73
+ paths.add(f"{prefix} {command_name}")
74
+
75
+ for group_info in list(getattr(app, "registered_groups", [])):
76
+ group_name = getattr(group_info, "name", "") or ""
77
+ if not group_name:
78
+ nested_app = getattr(group_info, "typer_instance", None)
79
+ nested_info = getattr(nested_app, "info", None) if nested_app is not None else None
80
+ group_name = getattr(nested_info, "name", "") or ""
81
+ if not group_name:
82
+ continue
83
+ group_prefix = f"{prefix} {group_name}"
84
+ paths.add(group_prefix)
85
+ nested_app = getattr(group_info, "typer_instance", None)
86
+ if nested_app is not None:
87
+ paths.update(_collect_typer_paths(nested_app, group_prefix))
88
+
89
+ return paths
90
+
91
+
92
+ def _import_typer(module_path: str, attr_name: str = "app") -> object:
93
+ module = importlib.import_module(module_path)
94
+ return getattr(module, attr_name)
95
+
96
+
97
+ def official_marketplace_module_ids() -> tuple[str, ...]:
98
+ """Return the official marketplace module ids that make up the full CLI surface."""
99
+ return (
100
+ "nold-ai/specfact-project",
101
+ "nold-ai/specfact-spec",
102
+ "nold-ai/specfact-codebase",
103
+ "nold-ai/specfact-backlog",
104
+ "nold-ai/specfact-govern",
105
+ )
106
+
107
+
108
+ def _explicit_cases() -> list[CommandAuditCase]:
109
+ return [
110
+ CommandAuditCase("specfact", ("--help",), "root", "help-only", "specfact-core"),
111
+ CommandAuditCase("project", ("project", "--help"), "project", "help-only", "nold-ai/specfact-project"),
112
+ CommandAuditCase("spec", ("spec", "--help"), "spec", "help-only", "nold-ai/specfact-spec"),
113
+ CommandAuditCase("code", ("code", "--help"), "code", "help-only", "nold-ai/specfact-codebase"),
114
+ CommandAuditCase("backlog", ("backlog", "--help"), "backlog", "help-only", "nold-ai/specfact-backlog"),
115
+ CommandAuditCase("govern", ("govern", "--help"), "govern", "help-only", "nold-ai/specfact-govern"),
116
+ CommandAuditCase(
117
+ "module init", ("module", "init", "--scope", "user"), "core", "fixture-backed", "specfact-core"
118
+ ),
119
+ CommandAuditCase("module search", ("module", "search", "specfact"), "core", "fixture-backed", "specfact-core"),
120
+ CommandAuditCase("module list", ("module", "list", "--show-origin"), "core", "fixture-backed", "specfact-core"),
121
+ CommandAuditCase(
122
+ "module show",
123
+ ("module", "show", "nold-ai/specfact-backlog"),
124
+ "core",
125
+ "fixture-backed",
126
+ "specfact-core",
127
+ ),
128
+ ]
129
+
130
+
131
+ def build_command_audit_cases() -> list[CommandAuditCase]:
132
+ """Build the full command audit matrix for core and official bundle command paths."""
133
+ _ensure_bundle_sources_on_sys_path()
134
+ app_specs = [
135
+ ("specfact_cli.modules.init.src.commands", "init", "core", "specfact-core", "import"),
136
+ ("specfact_cli.modules.module_registry.src.commands", "module", "core", "specfact-core", "import"),
137
+ ("specfact_cli.modules.upgrade.src.commands", "upgrade", "core", "specfact-core", "import"),
138
+ ("specfact_project.project.commands", "project", "project", "nold-ai/specfact-project", "import"),
139
+ ("specfact_spec.spec.commands", "spec", "spec", "nold-ai/specfact-spec", "import"),
140
+ ("specfact_codebase.code.commands", "code", "code", "nold-ai/specfact-codebase", "import"),
141
+ ("specfact_backlog.backlog.commands", "backlog", "backlog", "nold-ai/specfact-backlog", "import"),
142
+ ("specfact_govern.govern.commands", "govern", "govern", "nold-ai/specfact-govern", "import"),
143
+ ]
144
+
145
+ cases: dict[str, CommandAuditCase] = {case.command_path: case for case in _explicit_cases()}
146
+ for module_path, prefix, phase, owner, _load_mode in app_specs:
147
+ app = _import_typer(module_path)
148
+ cases.setdefault(
149
+ prefix,
150
+ CommandAuditCase(prefix, (*tuple(prefix.split()), "--help"), phase, "help-only", owner),
151
+ )
152
+ for command_path in sorted(_collect_typer_paths(app, prefix)):
153
+ cases.setdefault(
154
+ command_path,
155
+ CommandAuditCase(
156
+ command_path,
157
+ (*tuple(command_path.split()), "--help"),
158
+ phase,
159
+ "help-only",
160
+ owner,
161
+ ),
162
+ )
163
+
164
+ phase_order = {"root": 0, "core": 1, "project": 2, "spec": 3, "code": 4, "backlog": 5, "govern": 6}
165
+ return sorted(cases.values(), key=lambda case: (phase_order.get(case.phase, 99), case.command_path))