specfact-cli 0.37.5__tar.gz → 0.38.1__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 (392) hide show
  1. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/.gitignore +3 -0
  2. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/PKG-INFO +1 -1
  3. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/pyproject.toml +1 -1
  4. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/__init__.py +1 -1
  5. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/__init__.py +1 -1
  6. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/ado.py +30 -16
  7. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/mappers/ado_mapper.py +3 -2
  8. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/cli.py +7 -3
  9. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/module_registry/module-package.yaml +3 -3
  10. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/module_registry/src/commands.py +140 -21
  11. specfact_cli-0.38.1/src/specfact_cli/registry/alias_manager.py +94 -0
  12. specfact_cli-0.38.1/src/specfact_cli/registry/custom_registries.py +139 -0
  13. specfact_cli-0.38.1/src/specfact_cli/registry/dependency_resolver.py +109 -0
  14. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/marketplace_client.py +39 -3
  15. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/module_installer.py +49 -0
  16. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/LICENSE +0 -0
  17. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/README.md +0 -0
  18. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/keys/README.md +0 -0
  19. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/keys/module-signing-public.pem +0 -0
  20. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/mappings/node-async.yaml +0 -0
  21. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/mappings/python-async.yaml +0 -0
  22. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/mappings/speckit-default.yaml +0 -0
  23. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/shared/cli-enforcement.md +0 -0
  24. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.01-import.md +0 -0
  25. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.02-plan.md +0 -0
  26. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.03-review.md +0 -0
  27. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.04-sdd.md +0 -0
  28. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.05-enforce.md +0 -0
  29. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.06-sync.md +0 -0
  30. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.07-contracts.md +0 -0
  31. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.backlog-add.md +0 -0
  32. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.backlog-daily.md +0 -0
  33. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.backlog-refine.md +0 -0
  34. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.compare.md +0 -0
  35. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.sync-backlog.md +0 -0
  36. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.validate.md +0 -0
  37. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/schemas/deviation.schema.json +0 -0
  38. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/schemas/plan.schema.json +0 -0
  39. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/schemas/protocol.schema.json +0 -0
  40. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/defaults/defect_v1.yaml +0 -0
  41. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/defaults/enabler_v1.yaml +0 -0
  42. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/defaults/spike_v1.yaml +0 -0
  43. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/defaults/user_story_v1.yaml +0 -0
  44. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/field_mappings/ado_agile.yaml +0 -0
  45. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/field_mappings/ado_default.yaml +0 -0
  46. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/field_mappings/ado_kanban.yaml +0 -0
  47. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/field_mappings/ado_safe.yaml +0 -0
  48. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/field_mappings/ado_scrum.yaml +0 -0
  49. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/frameworks/safe/safe_feature_v1.yaml +0 -0
  50. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/frameworks/scrum/user_story_v1.yaml +0 -0
  51. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/personas/developer/developer_task_v1.yaml +0 -0
  52. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/personas/product-owner/user_story_v1.yaml +0 -0
  53. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/providers/ado/work_item_v1.yaml +0 -0
  54. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/github-action.yml.j2 +0 -0
  55. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/persona/architect.md.j2 +0 -0
  56. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/persona/developer.md.j2 +0 -0
  57. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/persona/product-owner.md.j2 +0 -0
  58. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/plan.bundle.yaml.j2 +0 -0
  59. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/policies/kanban.yaml +0 -0
  60. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/policies/mixed.yaml +0 -0
  61. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/policies/safe.yaml +0 -0
  62. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/policies/scrum.yaml +0 -0
  63. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/pr-template.md.j2 +0 -0
  64. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/protocol.yaml.j2 +0 -0
  65. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/telemetry.yaml.example +0 -0
  66. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/__main__.py +0 -0
  67. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/__init__.py +0 -0
  68. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/backlog_base.py +0 -0
  69. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/base.py +0 -0
  70. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/github.py +0 -0
  71. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/openspec.py +0 -0
  72. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/openspec_parser.py +0 -0
  73. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/registry.py +0 -0
  74. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/speckit.py +0 -0
  75. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/agents/__init__.py +0 -0
  76. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/agents/analyze_agent.py +0 -0
  77. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/agents/base.py +0 -0
  78. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/agents/plan_agent.py +0 -0
  79. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/agents/registry.py +0 -0
  80. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/agents/sync_agent.py +0 -0
  81. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/__init__.py +0 -0
  82. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
  83. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
  84. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
  85. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
  86. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
  87. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
  88. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
  89. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
  90. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
  91. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/__init__.py +0 -0
  92. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
  93. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/adapters/base.py +0 -0
  94. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/adapters/local_yaml_adapter.py +0 -0
  95. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/ai_refiner.py +0 -0
  96. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/converter.py +0 -0
  97. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/filters.py +0 -0
  98. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/format_detector.py +0 -0
  99. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/formats/__init__.py +0 -0
  100. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/formats/base.py +0 -0
  101. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/formats/markdown_format.py +0 -0
  102. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/formats/structured_format.py +0 -0
  103. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
  104. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/mappers/base.py +0 -0
  105. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
  106. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
  107. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/template_detector.py +0 -0
  108. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/__init__.py +0 -0
  109. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/analyze.py +0 -0
  110. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/auth.py +0 -0
  111. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/backlog_commands.py +0 -0
  112. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/contract_cmd.py +0 -0
  113. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/drift.py +0 -0
  114. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/enforce.py +0 -0
  115. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/generate.py +0 -0
  116. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/import_cmd.py +0 -0
  117. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/init.py +0 -0
  118. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/migrate.py +0 -0
  119. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/plan.py +0 -0
  120. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/project_cmd.py +0 -0
  121. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/repro.py +0 -0
  122. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/sdd.py +0 -0
  123. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/spec.py +0 -0
  124. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/sync.py +0 -0
  125. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/update.py +0 -0
  126. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/validate.py +0 -0
  127. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/common/__init__.py +0 -0
  128. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/common/logger_setup.py +0 -0
  129. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/common/logging_utils.py +0 -0
  130. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/common/text_utils.py +0 -0
  131. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/common/utils.py +0 -0
  132. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/comparators/__init__.py +0 -0
  133. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/comparators/plan_comparator.py +0 -0
  134. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/contracts/__init__.py +0 -0
  135. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/contracts/crosshair_props.py +0 -0
  136. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/contracts/module_interface.py +0 -0
  137. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
  138. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
  139. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/__init__.py +0 -0
  140. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/contract_generator.py +0 -0
  141. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/openapi_extractor.py +0 -0
  142. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/persona_exporter.py +0 -0
  143. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/plan_generator.py +0 -0
  144. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/protocol_generator.py +0 -0
  145. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/report_generator.py +0 -0
  146. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/task_generator.py +0 -0
  147. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/test_to_openapi.py +0 -0
  148. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/workflow_generator.py +0 -0
  149. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/importers/__init__.py +0 -0
  150. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/importers/speckit_converter.py +0 -0
  151. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/importers/speckit_scanner.py +0 -0
  152. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/integrations/__init__.py +0 -0
  153. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/integrations/specmatic.py +0 -0
  154. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/merge/__init__.py +0 -0
  155. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/merge/resolver.py +0 -0
  156. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/migrations/__init__.py +0 -0
  157. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/migrations/plan_migrator.py +0 -0
  158. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/__init__.py +0 -0
  159. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/backlog_item.py +0 -0
  160. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/bridge.py +0 -0
  161. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/capabilities.py +0 -0
  162. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/change.py +0 -0
  163. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/contract.py +0 -0
  164. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/deviation.py +0 -0
  165. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/dor_config.py +0 -0
  166. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/enforcement.py +0 -0
  167. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/module_package.py +0 -0
  168. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/persona_template.py +0 -0
  169. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/plan.py +0 -0
  170. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/project.py +0 -0
  171. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/protocol.py +0 -0
  172. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/quality.py +0 -0
  173. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/sdd.py +0 -0
  174. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/source_tracking.py +0 -0
  175. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/task.py +0 -0
  176. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/validation.py +0 -0
  177. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modes/__init__.py +0 -0
  178. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modes/detector.py +0 -0
  179. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modes/router.py +0 -0
  180. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/__init__.py +0 -0
  181. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/analyze/module-package.yaml +0 -0
  182. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/analyze/src/__init__.py +0 -0
  183. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/analyze/src/app.py +0 -0
  184. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/analyze/src/commands.py +0 -0
  185. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/auth/module-package.yaml +0 -0
  186. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/auth/src/__init__.py +0 -0
  187. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/auth/src/app.py +0 -0
  188. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/auth/src/commands.py +0 -0
  189. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/module-package.yaml +0 -0
  190. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/__init__.py +0 -0
  191. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/adapters/__init__.py +0 -0
  192. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/adapters/ado.py +0 -0
  193. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/adapters/base.py +0 -0
  194. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/adapters/github.py +0 -0
  195. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/adapters/jira.py +0 -0
  196. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/adapters/linear.py +0 -0
  197. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/app.py +0 -0
  198. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/commands.py +0 -0
  199. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/contract/module-package.yaml +0 -0
  200. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/contract/src/__init__.py +0 -0
  201. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/contract/src/app.py +0 -0
  202. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/contract/src/commands.py +0 -0
  203. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/drift/module-package.yaml +0 -0
  204. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/drift/src/__init__.py +0 -0
  205. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/drift/src/app.py +0 -0
  206. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/drift/src/commands.py +0 -0
  207. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/enforce/module-package.yaml +0 -0
  208. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/enforce/src/__init__.py +0 -0
  209. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/enforce/src/app.py +0 -0
  210. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/enforce/src/commands.py +0 -0
  211. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/generate/module-package.yaml +0 -0
  212. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/generate/src/__init__.py +0 -0
  213. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/generate/src/app.py +0 -0
  214. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/generate/src/commands.py +0 -0
  215. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/import_cmd/module-package.yaml +0 -0
  216. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/import_cmd/src/__init__.py +0 -0
  217. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/import_cmd/src/app.py +0 -0
  218. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/import_cmd/src/commands.py +0 -0
  219. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/init/module-package.yaml +0 -0
  220. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/init/src/__init__.py +0 -0
  221. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/init/src/app.py +0 -0
  222. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/init/src/commands.py +0 -0
  223. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/migrate/module-package.yaml +0 -0
  224. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/migrate/src/__init__.py +0 -0
  225. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/migrate/src/app.py +0 -0
  226. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/migrate/src/commands.py +0 -0
  227. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/module_io_shim.py +0 -0
  228. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/module_registry/src/__init__.py +0 -0
  229. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/module_registry/src/app.py +0 -0
  230. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/module-package.yaml +0 -0
  231. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/app.py +0 -0
  232. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/commands.py +0 -0
  233. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/patch_mode/__init__.py +0 -0
  234. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/patch_mode/commands/__init__.py +0 -0
  235. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/patch_mode/commands/apply.py +0 -0
  236. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/patch_mode/pipeline/__init__.py +0 -0
  237. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/patch_mode/pipeline/applier.py +0 -0
  238. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/patch_mode/pipeline/generator.py +0 -0
  239. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/patch_mode/pipeline/idempotency.py +0 -0
  240. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/plan/module-package.yaml +0 -0
  241. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/plan/src/__init__.py +0 -0
  242. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/plan/src/app.py +0 -0
  243. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/plan/src/commands.py +0 -0
  244. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/module-package.yaml +0 -0
  245. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/app.py +0 -0
  246. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/commands.py +0 -0
  247. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/__init__.py +0 -0
  248. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/config/__init__.py +0 -0
  249. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/config/policy_config.py +0 -0
  250. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/config/templates.py +0 -0
  251. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/engine/__init__.py +0 -0
  252. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/engine/suggester.py +0 -0
  253. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/engine/validator.py +0 -0
  254. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/main.py +0 -0
  255. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/models/__init__.py +0 -0
  256. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/models/policy_result.py +0 -0
  257. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/policies/__init__.py +0 -0
  258. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/policies/kanban.py +0 -0
  259. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/policies/safe.py +0 -0
  260. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/policies/scrum.py +0 -0
  261. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/registry/__init__.py +0 -0
  262. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/registry/policy_registry.py +0 -0
  263. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/project/module-package.yaml +0 -0
  264. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/project/src/__init__.py +0 -0
  265. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/project/src/app.py +0 -0
  266. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/project/src/commands.py +0 -0
  267. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/repro/module-package.yaml +0 -0
  268. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/repro/src/__init__.py +0 -0
  269. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/repro/src/app.py +0 -0
  270. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/repro/src/commands.py +0 -0
  271. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/sdd/module-package.yaml +0 -0
  272. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/sdd/src/__init__.py +0 -0
  273. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/sdd/src/app.py +0 -0
  274. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/sdd/src/commands.py +0 -0
  275. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/spec/module-package.yaml +0 -0
  276. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/spec/src/__init__.py +0 -0
  277. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/spec/src/app.py +0 -0
  278. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/spec/src/commands.py +0 -0
  279. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/sync/module-package.yaml +0 -0
  280. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/sync/src/__init__.py +0 -0
  281. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/sync/src/app.py +0 -0
  282. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/sync/src/commands.py +0 -0
  283. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/upgrade/module-package.yaml +0 -0
  284. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/upgrade/src/__init__.py +0 -0
  285. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/upgrade/src/app.py +0 -0
  286. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/upgrade/src/commands.py +0 -0
  287. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/validate/module-package.yaml +0 -0
  288. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/validate/src/__init__.py +0 -0
  289. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/validate/src/app.py +0 -0
  290. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/validate/src/commands.py +0 -0
  291. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/parsers/__init__.py +0 -0
  292. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/parsers/persona_importer.py +0 -0
  293. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/__init__.py +0 -0
  294. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/bootstrap.py +0 -0
  295. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/bridge_registry.py +0 -0
  296. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/crypto_validator.py +0 -0
  297. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/extension_registry.py +0 -0
  298. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/help_cache.py +0 -0
  299. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/metadata.py +0 -0
  300. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/module_discovery.py +0 -0
  301. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/module_lifecycle.py +0 -0
  302. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/module_packages.py +0 -0
  303. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/module_security.py +0 -0
  304. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/module_state.py +0 -0
  305. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/registry.py +0 -0
  306. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/resources/semgrep/async.yml +0 -0
  307. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
  308. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
  309. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/runtime.py +0 -0
  310. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/__init__.py +0 -0
  311. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/bridge_probe.py +0 -0
  312. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/bridge_sync.py +0 -0
  313. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/bridge_watch.py +0 -0
  314. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/change_detector.py +0 -0
  315. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/code_to_spec.py +0 -0
  316. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/drift_detector.py +0 -0
  317. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/repository_sync.py +0 -0
  318. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/spec_to_code.py +0 -0
  319. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/spec_to_tests.py +0 -0
  320. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/watcher.py +0 -0
  321. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
  322. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/telemetry.py +0 -0
  323. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/__init__.py +0 -0
  324. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/bridge_templates.py +0 -0
  325. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
  326. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
  327. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
  328. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
  329. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
  330. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
  331. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
  332. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/registry.py +0 -0
  333. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/specification_templates.py +0 -0
  334. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/__init__.py +0 -0
  335. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
  336. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/auth_tokens.py +0 -0
  337. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/bundle_converters.py +0 -0
  338. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/bundle_loader.py +0 -0
  339. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/code_change_detector.py +0 -0
  340. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/console.py +0 -0
  341. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/content_sanitizer.py +0 -0
  342. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/context_detection.py +0 -0
  343. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/enrichment_context.py +0 -0
  344. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/enrichment_parser.py +0 -0
  345. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/env_manager.py +0 -0
  346. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/feature_keys.py +0 -0
  347. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/git.py +0 -0
  348. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/github_annotations.py +0 -0
  349. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/ide_setup.py +0 -0
  350. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/incremental_check.py +0 -0
  351. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/metadata.py +0 -0
  352. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/optional_deps.py +0 -0
  353. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/performance.py +0 -0
  354. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/persona_ownership.py +0 -0
  355. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/progress.py +0 -0
  356. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
  357. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/prompts.py +0 -0
  358. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/sdd_discovery.py +0 -0
  359. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/source_scanner.py +0 -0
  360. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/startup_checks.py +0 -0
  361. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/structure.py +0 -0
  362. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/structured_io.py +0 -0
  363. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/suggestions.py +0 -0
  364. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/terminal.py +0 -0
  365. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/yaml_utils.py +0 -0
  366. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/__init__.py +0 -0
  367. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/agile_validation.py +0 -0
  368. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
  369. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/cli_first_validator.py +0 -0
  370. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/contract_validator.py +0 -0
  371. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/fsm.py +0 -0
  372. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/repro_checker.py +0 -0
  373. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/schema.py +0 -0
  374. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
  375. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
  376. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
  377. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
  378. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
  379. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
  380. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
  381. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
  382. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
  383. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
  384. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
  385. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
  386. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
  387. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/models.py +0 -0
  388. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
  389. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
  390. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
  391. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/versioning/__init__.py +0 -0
  392. {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/versioning/analyzer.py +0 -0
@@ -23,6 +23,7 @@ __pycache__/
23
23
  *.pyo
24
24
  *.pyd
25
25
  *.py.bak
26
+ .cache/
26
27
 
27
28
  # ChromaDB data files
28
29
  data/
@@ -147,3 +148,5 @@ Language.ml
147
148
  Language.mli
148
149
 
149
150
  .artifacts
151
+ registry.bak/
152
+ .pr-body.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specfact-cli
3
- Version: 0.37.5
3
+ Version: 0.38.1
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "specfact-cli"
7
- version = "0.37.5"
7
+ version = "0.38.1"
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"
@@ -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.37.5"
6
+ __version__ = "0.38.1"
@@ -8,6 +8,6 @@ This package provides command-line tools for:
8
8
  - Supporting agile ceremonies and team workflows
9
9
  """
10
10
 
11
- __version__ = "0.37.5"
11
+ __version__ = "0.38.1"
12
12
 
13
13
  __all__ = ["__version__"]
@@ -38,6 +38,8 @@ from specfact_cli.utils.auth_tokens import get_token, set_token
38
38
 
39
39
 
40
40
  _MAX_RESPONSE_BODY_LOG = 2048
41
+ _ADO_STABLE_API_VERSION = "7.1"
42
+ _ADO_COMMENTS_API_VERSION = "7.1-preview.4"
41
43
 
42
44
  console = Console()
43
45
 
@@ -203,7 +205,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
203
205
  """
204
206
  return "dev.azure.com" not in self.base_url.lower()
205
207
 
206
- def _build_ado_url(self, path: str, api_version: str = "7.1") -> str:
208
+ def _build_ado_url(self, path: str, api_version: str = _ADO_STABLE_API_VERSION) -> str:
207
209
  """
208
210
  Build Azure DevOps API URL with proper formatting.
209
211
 
@@ -2474,7 +2476,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
2474
2476
  seen_tokens: set[str] = set()
2475
2477
 
2476
2478
  while True:
2477
- params: dict[str, Any] = {"api-version": "7.1-preview.4", "$top": 200, "order": "asc"}
2479
+ params: dict[str, Any] = {"api-version": _ADO_COMMENTS_API_VERSION, "$top": 200, "order": "asc"}
2478
2480
  if continuation_token:
2479
2481
  params["continuationToken"] = continuation_token
2480
2482
 
@@ -2536,7 +2538,10 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
2536
2538
  raise ValueError(msg)
2537
2539
 
2538
2540
  # Azure DevOps API for adding comments to work items
2539
- url = f"{self.base_url}/{org}/{project}/_apis/wit/workitems/{work_item_id}/comments?api-version=7.1"
2541
+ url = (
2542
+ f"{self.base_url}/{org}/{project}/_apis/wit/workitems/{work_item_id}/comments"
2543
+ f"?api-version={_ADO_COMMENTS_API_VERSION}"
2544
+ )
2540
2545
  headers = {
2541
2546
  "Content-Type": "application/json",
2542
2547
  **self._auth_headers(),
@@ -2944,16 +2949,21 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
2944
2949
  ]
2945
2950
 
2946
2951
  if filters.iteration:
2947
- resolved_iteration = filters.iteration
2948
- if filters.iteration.lower() == "current":
2949
- current_iteration = self._get_current_iteration()
2950
- if current_iteration:
2951
- resolved_iteration = current_iteration
2952
- else:
2953
- return []
2954
- filtered_items = [
2955
- item for item in filtered_items if item.iteration and item.iteration == resolved_iteration
2956
- ]
2952
+ normalized_iteration = BacklogFilters.normalize_filter_value(filters.iteration)
2953
+ if normalized_iteration not in (None, "any"):
2954
+ target_iteration = filters.iteration
2955
+ if normalized_iteration == "current":
2956
+ current_iteration = self._get_current_iteration()
2957
+ if not current_iteration:
2958
+ return []
2959
+ target_iteration = current_iteration
2960
+
2961
+ filtered_items = [
2962
+ item
2963
+ for item in filtered_items
2964
+ if BacklogFilters.normalize_filter_value(item.iteration)
2965
+ == BacklogFilters.normalize_filter_value(target_iteration)
2966
+ ]
2957
2967
 
2958
2968
  if filters.sprint:
2959
2969
  _, filtered_items = self._resolve_sprint_filter(
@@ -3698,7 +3708,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
3698
3708
  # Update story points using mapped field name (honors custom mappings)
3699
3709
  if update_fields is None or "story_points" in update_fields:
3700
3710
  story_points_field = ado_mapper.resolve_write_target_field("story_points", provider_field_names)
3701
- if story_points_field and item.story_points is not None:
3711
+ if story_points_field and item.story_points is not None and story_points_field in provider_field_names:
3702
3712
  operations.append(
3703
3713
  {"op": "replace", "path": f"/fields/{story_points_field}", "value": item.story_points}
3704
3714
  )
@@ -3706,7 +3716,11 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
3706
3716
  # Update business value using mapped field name (honors custom mappings)
3707
3717
  if update_fields is None or "business_value" in update_fields:
3708
3718
  business_value_field = ado_mapper.resolve_write_target_field("business_value", provider_field_names)
3709
- if business_value_field and item.business_value is not None:
3719
+ if (
3720
+ business_value_field
3721
+ and item.business_value is not None
3722
+ and business_value_field in provider_field_names
3723
+ ):
3710
3724
  operations.append(
3711
3725
  {"op": "replace", "path": f"/fields/{business_value_field}", "value": item.business_value}
3712
3726
  )
@@ -3714,7 +3728,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
3714
3728
  # Update priority using mapped field name (honors custom mappings)
3715
3729
  if update_fields is None or "priority" in update_fields:
3716
3730
  priority_field = ado_mapper.resolve_write_target_field("priority", provider_field_names)
3717
- if priority_field and item.priority is not None:
3731
+ if priority_field and item.priority is not None and priority_field in provider_field_names:
3718
3732
  operations.append({"op": "replace", "path": f"/fields/{priority_field}", "value": item.priority})
3719
3733
 
3720
3734
  if update_fields is None or "state" in update_fields:
@@ -171,8 +171,9 @@ class AdoFieldMapper(FieldMapper):
171
171
  @beartype
172
172
  @require(lambda self, canonical_field: isinstance(canonical_field, str), "Canonical field must be str")
173
173
  @require(
174
- lambda self, provider_field_names: provider_field_names is None
175
- or isinstance(provider_field_names, (set, frozenset)),
174
+ lambda self, provider_field_names: (
175
+ provider_field_names is None or isinstance(provider_field_names, (set, frozenset))
176
+ ),
176
177
  "provider_field_names must be set-like or None",
177
178
  )
178
179
  @ensure(lambda result: result is None or isinstance(result, str), "Must return str or None")
@@ -58,6 +58,7 @@ from specfact_cli.modes import OperationalMode, detect_mode
58
58
 
59
59
  # Command groups are registered via CommandRegistry (bootstrap); no top-level command imports.
60
60
  from specfact_cli.registry import CommandRegistry
61
+ from specfact_cli.registry.alias_manager import resolve_command
61
62
  from specfact_cli.registry.bootstrap import register_builtin_commands
62
63
  from specfact_cli.registry.metadata import CommandMetadata
63
64
  from specfact_cli.runtime import get_configured_console, init_debug_log_file, set_debug_mode
@@ -348,7 +349,8 @@ class _LazyDelegateGroup(click.Group):
348
349
  from typer.main import get_command
349
350
 
350
351
  ctx = click.get_current_context()
351
- real_typer = CommandRegistry.get_typer(cmd_name)
352
+ resolved_name = resolve_command(cmd_name)
353
+ real_typer = CommandRegistry.get_typer(resolved_name)
352
354
  click_cmd = get_command(real_typer)
353
355
  # Build full prog name from root (e.g. "specfact sync") so usage shows "specfact sync bridge", not "sync sync bridge"
354
356
  parts: list[str] = []
@@ -407,7 +409,8 @@ class _LazyDelegateGroup(click.Group):
407
409
  """Load and return the real command's Click Group, or None on failure."""
408
410
  from typer.main import get_command
409
411
 
410
- real_typer = CommandRegistry.get_typer(self._lazy_cmd_name)
412
+ resolved_name = resolve_command(self._lazy_cmd_name)
413
+ real_typer = CommandRegistry.get_typer(resolved_name)
411
414
  click_cmd = get_command(real_typer)
412
415
  if isinstance(click_cmd, click.Group):
413
416
  return click_cmd
@@ -417,7 +420,8 @@ class _LazyDelegateGroup(click.Group):
417
420
  """Show the real Typer's Rich help instead of plain Click group help."""
418
421
  from typer.main import get_command
419
422
 
420
- real_typer = CommandRegistry.get_typer(self._lazy_cmd_name)
423
+ resolved_name = resolve_command(self._lazy_cmd_name)
424
+ real_typer = CommandRegistry.get_typer(resolved_name)
421
425
  click_cmd = get_command(real_typer)
422
426
  prog_name = (
423
427
  f"{ctx.parent.command.name} {self._lazy_cmd_name}"
@@ -1,5 +1,5 @@
1
1
  name: module-registry
2
- version: 0.1.3
2
+ version: 0.1.5
3
3
  commands:
4
4
  - module
5
5
  command_help:
@@ -15,5 +15,5 @@ publisher:
15
15
  description: 'Manage modules: search, list, show, install, and upgrade.'
16
16
  license: Apache-2.0
17
17
  integrity:
18
- checksum: sha256:92a3673edb39974791afcc357a1e931b6e4152d1ad7af0e4a4d722c29431b90d
19
- signature: eynkV+88hx9PPGlOnB5rwWHkiEtgFe8wNIU+5I0C8PZR+WWoRIcTcvTe5TYYAyiHi6JmJZZLvrDsTIpe3K3VDQ==
18
+ checksum: sha256:4837d40c55ebde6eba87b434c3ec3ae3d0d842eb6a6984d4212ffbc6fd26eac2
19
+ signature: m2tJyNfaHOnil3dsT5NxUB93+4nnVJHBaF7QzQf/DC8F/LG7oJJMWHU063HY9x2/d9hFVXLwItf9TNgNjnirDQ==
@@ -12,7 +12,8 @@ from rich.console import Console
12
12
  from rich.table import Table
13
13
 
14
14
  from specfact_cli.modules import module_io_shim
15
- from specfact_cli.registry.marketplace_client import fetch_registry_index
15
+ from specfact_cli.registry.alias_manager import create_alias, list_aliases, remove_alias
16
+ from specfact_cli.registry.custom_registries import add_registry, fetch_all_indexes, list_registries, remove_registry
16
17
  from specfact_cli.registry.module_discovery import discover_all_modules
17
18
  from specfact_cli.registry.module_installer import (
18
19
  USER_MODULES_ROOT,
@@ -87,6 +88,16 @@ def install(
87
88
  "--trust-non-official",
88
89
  help="Trust and persist non-official publisher for this module install",
89
90
  ),
91
+ skip_deps: bool = typer.Option(
92
+ False,
93
+ "--skip-deps",
94
+ help="Skip dependency resolution before installing (install module only)",
95
+ ),
96
+ force: bool = typer.Option(
97
+ False,
98
+ "--force",
99
+ help="Force install even if dependency resolution reports conflicts",
100
+ ),
90
101
  ) -> None:
91
102
  """Install a module from bundled artifacts or marketplace registry."""
92
103
  scope_normalized = scope.strip().lower()
@@ -148,6 +159,8 @@ def install(
148
159
  install_root=target_root,
149
160
  trust_non_official=trust_non_official,
150
161
  non_interactive=is_non_interactive(),
162
+ skip_deps=skip_deps,
163
+ force=force,
151
164
  )
152
165
  except Exception as exc:
153
166
  console.print(f"[red]Failed installing {normalized}: {exc}[/red]")
@@ -243,6 +256,109 @@ def uninstall(
243
256
  console.print(f"[green]Uninstalled[/green] {normalized}")
244
257
 
245
258
 
259
+ alias_app = typer.Typer(help="Manage command aliases (map name to namespaced module)")
260
+
261
+
262
+ @alias_app.command(name="create")
263
+ @beartype
264
+ def alias_create(
265
+ alias_name: str = typer.Argument(..., help="Alias (command name) to map"),
266
+ command_name: str = typer.Argument(..., help="Command name to invoke (e.g. backlog, module)"),
267
+ force: bool = typer.Option(False, "--force", help="Allow alias to shadow built-in command"),
268
+ ) -> None:
269
+ """Create an alias mapping a custom name to a registered command."""
270
+ try:
271
+ create_alias(alias_name.strip(), command_name.strip(), force=force)
272
+ except ValueError as exc:
273
+ console.print(f"[red]{exc}[/red]")
274
+ raise typer.Exit(1) from exc
275
+ console.print(f"[green]Alias[/green] {alias_name!r} -> {command_name!r}")
276
+
277
+
278
+ @alias_app.command(name="list")
279
+ @beartype
280
+ def alias_list() -> None:
281
+ """List all configured aliases."""
282
+ aliases = list_aliases()
283
+ if not aliases:
284
+ console.print("[dim]No aliases configured.[/dim]")
285
+ return
286
+ table = Table(title="Aliases")
287
+ table.add_column("Alias", style="cyan")
288
+ table.add_column("Command", style="green")
289
+ for alias, mod in sorted(aliases.items()):
290
+ table.add_row(alias, mod)
291
+ console.print(table)
292
+
293
+
294
+ @alias_app.command(name="remove")
295
+ @beartype
296
+ def alias_remove(
297
+ alias_name: str = typer.Argument(..., help="Alias to remove"),
298
+ ) -> None:
299
+ """Remove an alias."""
300
+ remove_alias(alias_name.strip())
301
+ console.print(f"[green]Removed alias[/green] {alias_name!r}")
302
+
303
+
304
+ if app.add_typer is not None:
305
+ app.add_typer(alias_app, name="alias")
306
+
307
+
308
+ @app.command(name="add-registry")
309
+ @beartype
310
+ def add_registry_cmd(
311
+ url: str = typer.Argument(..., help="Registry index URL (e.g. https://company.com/index.json)"),
312
+ id: str | None = typer.Option(None, "--id", help="Registry id (default: derived from URL)"),
313
+ priority: int | None = typer.Option(None, "--priority", help="Priority (default: next available)"),
314
+ trust: str = typer.Option("prompt", "--trust", help="Trust level: always, prompt, or never"),
315
+ ) -> None:
316
+ """Add a custom registry to the config."""
317
+ if trust not in ("always", "prompt", "never"):
318
+ console.print("[red]trust must be one of: always, prompt, never.[/red]")
319
+ raise typer.Exit(1)
320
+ reg_id = (id or url.strip().rstrip("/").split("/")[-2] or "custom").strip() or "custom"
321
+ try:
322
+ add_registry(reg_id, url.strip(), priority=priority, trust=trust)
323
+ except Exception as exc:
324
+ console.print(f"[red]{exc}[/red]")
325
+ raise typer.Exit(1) from exc
326
+ console.print(f"[green]Added registry[/green] {reg_id!r} -> {url}")
327
+
328
+
329
+ @app.command(name="list-registries")
330
+ @beartype
331
+ def list_registries_cmd() -> None:
332
+ """List all configured registries (official + custom)."""
333
+ registries = list_registries()
334
+ if not registries:
335
+ console.print("[dim]No registries configured.[/dim]")
336
+ return
337
+ table = Table(title="Registries")
338
+ table.add_column("Id", style="cyan")
339
+ table.add_column("URL", style="green")
340
+ table.add_column("Priority", style="dim")
341
+ table.add_column("Trust", style="yellow")
342
+ for r in registries:
343
+ table.add_row(
344
+ str(r.get("id", "")),
345
+ str(r.get("url", "")),
346
+ str(r.get("priority", "")),
347
+ str(r.get("trust", "")),
348
+ )
349
+ console.print(table)
350
+
351
+
352
+ @app.command(name="remove-registry")
353
+ @beartype
354
+ def remove_registry_cmd(
355
+ registry_id: str = typer.Argument(..., help="Registry id to remove"),
356
+ ) -> None:
357
+ """Remove a custom registry from the config."""
358
+ remove_registry(registry_id.strip())
359
+ console.print(f"[green]Removed registry[/green] {registry_id!r}")
360
+
361
+
246
362
  @app.command()
247
363
  @beartype
248
364
  def enable(
@@ -320,25 +436,26 @@ def search(query: str = typer.Argument(..., help="Search query")) -> None:
320
436
  seen_ids: set[str] = set()
321
437
  rows: list[dict[str, str]] = []
322
438
 
323
- index = fetch_registry_index() or {}
324
- for entry in index.get("modules", []):
325
- if not isinstance(entry, dict):
326
- continue
327
- module_id = str(entry.get("id", ""))
328
- description = str(entry.get("description", ""))
329
- tags = entry.get("tags", [])
330
- tags_text = " ".join(str(t) for t in tags) if isinstance(tags, list) else ""
331
- haystack = f"{module_id} {description} {tags_text}".lower()
332
- if query_l in haystack and module_id not in seen_ids:
333
- seen_ids.add(module_id)
334
- rows.append(
335
- {
336
- "id": module_id,
337
- "version": str(entry.get("latest_version", "")),
338
- "description": description,
339
- "scope": "marketplace",
340
- }
341
- )
439
+ for reg_id, index in fetch_all_indexes():
440
+ for entry in index.get("modules", []):
441
+ if not isinstance(entry, dict):
442
+ continue
443
+ module_id = str(entry.get("id", ""))
444
+ description = str(entry.get("description", ""))
445
+ tags = entry.get("tags", [])
446
+ tags_text = " ".join(str(t) for t in tags) if isinstance(tags, list) else ""
447
+ haystack = f"{module_id} {description} {tags_text}".lower()
448
+ if query_l in haystack and module_id not in seen_ids:
449
+ seen_ids.add(module_id)
450
+ rows.append(
451
+ {
452
+ "id": module_id,
453
+ "version": str(entry.get("latest_version", "")),
454
+ "description": description,
455
+ "scope": "marketplace",
456
+ "registry": reg_id,
457
+ }
458
+ )
342
459
 
343
460
  for discovered in discover_all_modules():
344
461
  meta = discovered.metadata
@@ -372,9 +489,11 @@ def search(query: str = typer.Argument(..., help="Search query")) -> None:
372
489
  table.add_column("ID", style="cyan")
373
490
  table.add_column("Version", style="magenta")
374
491
  table.add_column("Scope", style="yellow")
492
+ table.add_column("Registry", style="dim")
375
493
  table.add_column("Description")
376
494
  for row in rows:
377
- table.add_row(row["id"], row["version"], row["scope"], row["description"])
495
+ reg = row.get("registry", "")
496
+ table.add_row(row["id"], row["version"], row["scope"], reg, row["description"])
378
497
  console.print(table)
379
498
 
380
499
 
@@ -0,0 +1,94 @@
1
+ """Alias storage and resolution: alias -> command name."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ from beartype import beartype
9
+ from icontract import ensure, require
10
+
11
+ from specfact_cli.common import get_bridge_logger
12
+
13
+
14
+ logger = get_bridge_logger(__name__)
15
+
16
+ _ALIASES_FILENAME = "aliases.json"
17
+
18
+
19
+ def get_aliases_path() -> Path:
20
+ """Return path to aliases.json under ~/.specfact/registry/."""
21
+ return Path.home() / ".specfact" / "registry" / _ALIASES_FILENAME
22
+
23
+
24
+ def _builtin_command_names() -> set[str]:
25
+ """Return set of built-in (default) command names for shadowing check."""
26
+ from specfact_cli.registry.module_packages import CORE_MODULE_ORDER
27
+
28
+ return set(CORE_MODULE_ORDER)
29
+
30
+
31
+ @beartype
32
+ @require(lambda alias: alias.strip() != "", "alias must be non-empty")
33
+ @require(lambda command_name: command_name.strip() != "", "command_name must be non-empty")
34
+ @ensure(lambda: True, "no postcondition on void")
35
+ def create_alias(alias: str, command_name: str, force: bool = False) -> None:
36
+ """Store alias -> command_name in aliases.json. Warn or raise if alias shadows built-in."""
37
+ alias = alias.strip()
38
+ command_name = command_name.strip()
39
+ path = get_aliases_path()
40
+ path.parent.mkdir(parents=True, exist_ok=True)
41
+ builtin = _builtin_command_names()
42
+ if alias in builtin and not force:
43
+ logger.warning("Alias will shadow built-in module: %s", alias)
44
+ raise ValueError(f'Alias "{alias}" would shadow built-in module. Use --force to proceed.')
45
+ data = {}
46
+ if path.exists():
47
+ data = json.loads(path.read_text(encoding="utf-8"))
48
+ data[alias] = command_name
49
+ path.write_text(json.dumps(data, indent=2), encoding="utf-8")
50
+ if alias in builtin:
51
+ logger.warning("Alias will shadow built-in module: %s", alias)
52
+
53
+
54
+ @beartype
55
+ @ensure(lambda result: isinstance(result, dict), "returns dict")
56
+ def list_aliases() -> dict[str, str]:
57
+ """Return all alias -> command name mappings. Empty dict if file missing."""
58
+ path = get_aliases_path()
59
+ if not path.exists():
60
+ return {}
61
+ raw = path.read_text(encoding="utf-8")
62
+ data = json.loads(raw)
63
+ if not isinstance(data, dict):
64
+ return {}
65
+ return {str(k): str(v) for k, v in data.items()}
66
+
67
+
68
+ @beartype
69
+ @require(lambda alias: alias.strip() != "", "alias must be non-empty")
70
+ @ensure(lambda: True, "no postcondition on void")
71
+ def remove_alias(alias: str) -> None:
72
+ """Remove alias from aliases.json."""
73
+ alias = alias.strip()
74
+ path = get_aliases_path()
75
+ if not path.exists():
76
+ return
77
+ data = json.loads(path.read_text(encoding="utf-8"))
78
+ if alias in data:
79
+ del data[alias]
80
+ if data:
81
+ path.write_text(json.dumps(data, indent=2), encoding="utf-8")
82
+ else:
83
+ path.unlink()
84
+
85
+
86
+ @beartype
87
+ @require(lambda invoked_name: isinstance(invoked_name, str), "invoked_name must be str")
88
+ @ensure(lambda result: isinstance(result, str) and len(result) > 0, "returns non-empty string")
89
+ def resolve_command(invoked_name: str) -> str:
90
+ """If invoked_name is an alias, return the stored command name; else return invoked_name."""
91
+ aliases = list_aliases()
92
+ if invoked_name in aliases:
93
+ return aliases[invoked_name]
94
+ return invoked_name
@@ -0,0 +1,139 @@
1
+ """Custom registry management: YAML config and multi-registry index fetching."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import requests
9
+ import yaml
10
+ from beartype import beartype
11
+ from icontract import ensure, require
12
+
13
+ from specfact_cli.common import get_bridge_logger
14
+ from specfact_cli.registry.marketplace_client import REGISTRY_INDEX_URL
15
+
16
+
17
+ logger = get_bridge_logger(__name__)
18
+
19
+ _REGISTRIES_FILENAME = "registries.yaml"
20
+ OFFICIAL_REGISTRY_ID = "official"
21
+ TRUST_LEVELS = frozenset({"always", "prompt", "never"})
22
+
23
+
24
+ def get_registries_config_path() -> Path:
25
+ """Return path to registries.yaml under ~/.specfact/config/."""
26
+ return Path.home() / ".specfact" / "config" / _REGISTRIES_FILENAME
27
+
28
+
29
+ def _default_official_entry() -> dict[str, Any]:
30
+ """Return the built-in official registry entry."""
31
+ return {
32
+ "id": OFFICIAL_REGISTRY_ID,
33
+ "url": REGISTRY_INDEX_URL,
34
+ "priority": 1,
35
+ "trust": "always",
36
+ }
37
+
38
+
39
+ @beartype
40
+ @require(lambda id: id.strip() != "", "id must be non-empty")
41
+ @require(lambda url: url.strip().startswith("http"), "url must be http(s)")
42
+ @require(lambda trust: trust in TRUST_LEVELS, "trust must be always, prompt, or never")
43
+ @ensure(lambda: True, "no postcondition on void")
44
+ def add_registry(
45
+ id: str,
46
+ url: str,
47
+ priority: int | None = None,
48
+ trust: str = "prompt",
49
+ ) -> None:
50
+ """Add a registry to config. Assigns next priority if priority is None."""
51
+ id = id.strip()
52
+ url = url.strip()
53
+ path = get_registries_config_path()
54
+ path.parent.mkdir(parents=True, exist_ok=True)
55
+ registries: list[dict[str, Any]] = []
56
+ if path.exists():
57
+ data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
58
+ registries = list(data.get("registries") or [])
59
+ existing_ids = {r.get("id") for r in registries if isinstance(r, dict) and r.get("id")}
60
+ if id in existing_ids:
61
+ registries = [r for r in registries if isinstance(r, dict) and r.get("id") != id]
62
+ if priority is None:
63
+ priorities = [
64
+ p
65
+ for r in registries
66
+ if isinstance(r, dict) and (p := r.get("priority")) is not None and isinstance(p, (int, float))
67
+ ]
68
+ priority = int(max(priorities, default=0)) + 1
69
+ registries.append({"id": id, "url": url, "priority": int(priority), "trust": trust})
70
+ registries.sort(key=lambda r: (r.get("priority", 999), r.get("id", "")))
71
+ path.write_text(yaml.dump({"registries": registries}, default_flow_style=False, sort_keys=False), encoding="utf-8")
72
+
73
+
74
+ @beartype
75
+ @ensure(lambda result: isinstance(result, list), "returns list")
76
+ def list_registries() -> list[dict[str, Any]]:
77
+ """Return all registries: official first, then custom from config, sorted by priority."""
78
+ result: list[dict[str, Any]] = []
79
+ path = get_registries_config_path()
80
+ if path.exists():
81
+ try:
82
+ data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
83
+ custom = [r for r in (data.get("registries") or []) if isinstance(r, dict) and r.get("id")]
84
+ has_official = any(r.get("id") == OFFICIAL_REGISTRY_ID for r in custom)
85
+ if not has_official:
86
+ result.append(_default_official_entry())
87
+ for r in custom:
88
+ result.append({k: v for k, v in r.items() if k in ("id", "url", "priority", "trust")})
89
+ result.sort(key=lambda r: (r.get("priority", 999), r.get("id", "")))
90
+ except Exception as exc:
91
+ logger.warning("Failed to load registries config: %s", exc)
92
+ result = [_default_official_entry()]
93
+ else:
94
+ result = [_default_official_entry()]
95
+ return result
96
+
97
+
98
+ @beartype
99
+ @require(lambda id: id.strip() != "", "id must be non-empty")
100
+ @ensure(lambda: True, "no postcondition on void")
101
+ def remove_registry(id: str) -> None:
102
+ """Remove a registry by id from config. Cannot remove official (no-op if official)."""
103
+ id = id.strip()
104
+ if id == OFFICIAL_REGISTRY_ID:
105
+ logger.debug("Cannot remove built-in official registry")
106
+ return
107
+ path = get_registries_config_path()
108
+ if not path.exists():
109
+ return
110
+ data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
111
+ registries = [r for r in (data.get("registries") or []) if isinstance(r, dict) and r.get("id") != id]
112
+ if not registries:
113
+ path.unlink()
114
+ return
115
+ path.write_text(yaml.dump({"registries": registries}, default_flow_style=False, sort_keys=False), encoding="utf-8")
116
+
117
+
118
+ @beartype
119
+ @ensure(lambda result: isinstance(result, list), "returns list")
120
+ def fetch_all_indexes(timeout: float = 10.0) -> list[tuple[str, dict[str, Any]]]:
121
+ """Fetch index from each registry in priority order. Returns list of (registry_id, index_dict)."""
122
+ registries = list_registries()
123
+ result: list[tuple[str, dict[str, Any]]] = []
124
+ for reg in registries:
125
+ reg_id = str(reg.get("id", ""))
126
+ url = str(reg.get("url", "")).strip()
127
+ if not url:
128
+ continue
129
+ try:
130
+ response = requests.get(url, timeout=timeout)
131
+ response.raise_for_status()
132
+ payload = response.json()
133
+ if isinstance(payload, dict):
134
+ result.append((reg_id, payload))
135
+ else:
136
+ logger.warning("Registry %s returned non-dict index", reg_id)
137
+ except Exception as exc:
138
+ logger.warning("Registry %s unavailable: %s", reg_id, exc)
139
+ return result