specfact-cli 0.46.25__tar.gz → 0.47.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 (279) hide show
  1. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/PKG-INFO +8 -1
  2. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/README.md +7 -0
  3. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/pyproject.toml +5 -2
  4. specfact_cli-0.47.3/resources/bundled-module-registry/index.json +26 -0
  5. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/templates/pr-template.md.j2 +1 -2
  6. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/__init__.py +1 -1
  7. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/__init__.py +31 -1
  8. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/cli.py +176 -30
  9. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modules/init/module-package.yaml +3 -3
  10. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modules/init/src/commands.py +3 -4
  11. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modules/upgrade/module-package.yaml +3 -3
  12. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modules/upgrade/src/commands.py +139 -2
  13. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/bundle_loader.py +1 -1
  14. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/env_manager.py +18 -4
  15. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/github_annotations.py +2 -2
  16. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/ide_setup.py +159 -2
  17. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/progressive_disclosure.py +144 -14
  18. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/structure.py +4 -4
  19. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/suggestions.py +9 -9
  20. specfact_cli-0.46.25/resources/bundled-module-registry/index.json +0 -20
  21. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/.gitignore +0 -0
  22. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/LICENSE +0 -0
  23. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/keys/README.md +0 -0
  24. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/keys/module-signing-public.pem +0 -0
  25. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/mappings/node-async.yaml +0 -0
  26. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/mappings/python-async.yaml +0 -0
  27. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/mappings/speckit-default.yaml +0 -0
  28. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/schemas/deviation.schema.json +0 -0
  29. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/schemas/plan.schema.json +0 -0
  30. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/schemas/protocol.schema.json +0 -0
  31. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/templates/github-action.yml.j2 +0 -0
  32. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/templates/persona/architect.md.j2 +0 -0
  33. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/templates/persona/developer.md.j2 +0 -0
  34. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/templates/persona/product-owner.md.j2 +0 -0
  35. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/templates/plan.bundle.yaml.j2 +0 -0
  36. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/templates/policies/kanban.yaml +0 -0
  37. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/templates/policies/mixed.yaml +0 -0
  38. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/templates/policies/safe.yaml +0 -0
  39. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/templates/policies/scrum.yaml +0 -0
  40. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/templates/protocol.yaml.j2 +0 -0
  41. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/resources/templates/telemetry.yaml.example +0 -0
  42. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/__main__.py +0 -0
  43. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/adapters/__init__.py +0 -0
  44. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/adapters/ado.py +0 -0
  45. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/adapters/backlog_base.py +0 -0
  46. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/adapters/base.py +0 -0
  47. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/adapters/github.py +0 -0
  48. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/adapters/openspec.py +0 -0
  49. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/adapters/openspec_parser.py +0 -0
  50. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/adapters/registry.py +0 -0
  51. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/adapters/speckit.py +0 -0
  52. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/agents/__init__.py +0 -0
  53. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/agents/analyze_agent.py +0 -0
  54. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/agents/base.py +0 -0
  55. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/agents/plan_agent.py +0 -0
  56. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/agents/registry.py +0 -0
  57. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/agents/sync_agent.py +0 -0
  58. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/__init__.py +0 -0
  59. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
  60. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
  61. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
  62. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
  63. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
  64. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
  65. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
  66. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
  67. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
  68. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/backlog/__init__.py +0 -0
  69. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
  70. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/backlog/adapters/base.py +0 -0
  71. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/backlog/converter.py +0 -0
  72. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/backlog/filters.py +0 -0
  73. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
  74. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
  75. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/backlog/mappers/base.py +0 -0
  76. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
  77. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
  78. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/__init__.py +0 -0
  79. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/_bundle_shim.py +0 -0
  80. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/analyze.py +0 -0
  81. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/contract_cmd.py +0 -0
  82. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/drift.py +0 -0
  83. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/enforce.py +0 -0
  84. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/generate.py +0 -0
  85. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/import_cmd.py +0 -0
  86. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/init.py +0 -0
  87. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/migrate.py +0 -0
  88. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/plan.py +0 -0
  89. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/project_cmd.py +0 -0
  90. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/repro.py +0 -0
  91. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/sdd.py +0 -0
  92. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/spec.py +0 -0
  93. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/sync.py +0 -0
  94. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/update.py +0 -0
  95. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/commands/validate.py +0 -0
  96. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/common/__init__.py +0 -0
  97. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/common/bundle_factory.py +0 -0
  98. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/common/logger_setup.py +0 -0
  99. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/common/logging_utils.py +0 -0
  100. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/common/text_utils.py +0 -0
  101. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/common/utils.py +0 -0
  102. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/comparators/__init__.py +0 -0
  103. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/comparators/plan_comparator.py +0 -0
  104. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/contracts/__init__.py +0 -0
  105. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/contracts/crosshair_props.py +0 -0
  106. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/contracts/module_interface.py +0 -0
  107. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
  108. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
  109. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/generators/__init__.py +0 -0
  110. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/generators/contract_generator.py +0 -0
  111. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/generators/openapi_extractor.py +0 -0
  112. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/generators/persona_exporter.py +0 -0
  113. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/generators/plan_generator.py +0 -0
  114. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/generators/protocol_generator.py +0 -0
  115. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/generators/report_generator.py +0 -0
  116. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/generators/task_generator.py +0 -0
  117. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/generators/test_to_openapi.py +0 -0
  118. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/generators/workflow_generator.py +0 -0
  119. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/groups/__init__.py +0 -0
  120. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/groups/codebase_group.py +0 -0
  121. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/groups/govern_group.py +0 -0
  122. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/groups/member_group.py +0 -0
  123. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/groups/project_group.py +0 -0
  124. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/groups/spec_group.py +0 -0
  125. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/importers/__init__.py +0 -0
  126. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/importers/speckit_converter.py +0 -0
  127. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/importers/speckit_scanner.py +0 -0
  128. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/integrations/__init__.py +0 -0
  129. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/integrations/specmatic.py +0 -0
  130. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/merge/__init__.py +0 -0
  131. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/merge/resolver.py +0 -0
  132. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/migrations/__init__.py +0 -0
  133. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/migrations/plan_migrator.py +0 -0
  134. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/__init__.py +0 -0
  135. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/backlog_item.py +0 -0
  136. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/bridge.py +0 -0
  137. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/capabilities.py +0 -0
  138. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/change.py +0 -0
  139. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/contract.py +0 -0
  140. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/deviation.py +0 -0
  141. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/dor_config.py +0 -0
  142. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/enforcement.py +0 -0
  143. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/module_package.py +0 -0
  144. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/persona_template.py +0 -0
  145. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/plan.py +0 -0
  146. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/project.py +0 -0
  147. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/protocol.py +0 -0
  148. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/quality.py +0 -0
  149. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/sdd.py +0 -0
  150. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/source_tracking.py +0 -0
  151. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/task.py +0 -0
  152. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/models/validation.py +0 -0
  153. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modes/__init__.py +0 -0
  154. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modes/detector.py +0 -0
  155. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modes/router.py +0 -0
  156. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modules/__init__.py +0 -0
  157. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modules/_bundle_import.py +0 -0
  158. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modules/init/src/__init__.py +0 -0
  159. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modules/init/src/app.py +0 -0
  160. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modules/init/src/first_run_selection.py +0 -0
  161. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modules/module_io_shim.py +0 -0
  162. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modules/module_registry/module-package.yaml +0 -0
  163. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modules/module_registry/src/__init__.py +0 -0
  164. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modules/module_registry/src/app.py +0 -0
  165. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modules/module_registry/src/commands.py +0 -0
  166. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modules/upgrade/src/__init__.py +0 -0
  167. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/modules/upgrade/src/app.py +0 -0
  168. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/parsers/__init__.py +0 -0
  169. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/parsers/persona_importer.py +0 -0
  170. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/__init__.py +0 -0
  171. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/alias_manager.py +0 -0
  172. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/bootstrap.py +0 -0
  173. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/bridge_registry.py +0 -0
  174. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/crypto_validator.py +0 -0
  175. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/custom_registries.py +0 -0
  176. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/dependency_resolver.py +0 -0
  177. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/extension_registry.py +0 -0
  178. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/help_cache.py +0 -0
  179. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/marketplace_client.py +0 -0
  180. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/metadata.py +0 -0
  181. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/module_availability.py +0 -0
  182. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/module_discovery.py +0 -0
  183. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/module_grouping.py +0 -0
  184. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/module_installer.py +0 -0
  185. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/module_lifecycle.py +0 -0
  186. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/module_packages.py +0 -0
  187. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/module_security.py +0 -0
  188. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/module_state.py +0 -0
  189. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/registry/registry.py +0 -0
  190. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/resources/semgrep/async.yml +0 -0
  191. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
  192. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
  193. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/runtime.py +0 -0
  194. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/__init__.py +0 -0
  195. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_probe.py +0 -0
  196. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_sync.py +0 -0
  197. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_sync_openspec_md_parse.py +0 -0
  198. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_sync_requirement_from_proposal.py +0 -0
  199. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_sync_requirement_helpers.py +0 -0
  200. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_sync_tasks_from_proposal.py +0 -0
  201. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_sync_what_changes_format.py +0 -0
  202. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_sync_write_openspec_from_proposal.py +0 -0
  203. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_watch.py +0 -0
  204. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/change_detector.py +0 -0
  205. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/code_to_spec.py +0 -0
  206. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/drift_detector.py +0 -0
  207. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/repository_sync.py +0 -0
  208. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/spec_to_code.py +0 -0
  209. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/spec_to_tests.py +0 -0
  210. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/watcher.py +0 -0
  211. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
  212. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/telemetry.py +0 -0
  213. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/templates/__init__.py +0 -0
  214. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
  215. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
  216. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
  217. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
  218. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
  219. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
  220. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
  221. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/templates/registry.py +0 -0
  222. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/templates/specification_templates.py +0 -0
  223. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/__init__.py +0 -0
  224. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
  225. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/auth_tokens.py +0 -0
  226. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/bundle_converters.py +0 -0
  227. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/code_change_detector.py +0 -0
  228. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/console.py +0 -0
  229. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/content_sanitizer.py +0 -0
  230. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/context_detection.py +0 -0
  231. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/contract_predicates.py +0 -0
  232. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/enrichment_context.py +0 -0
  233. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/enrichment_parser.py +0 -0
  234. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/feature_keys.py +0 -0
  235. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/git.py +0 -0
  236. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/icontract_helpers.py +0 -0
  237. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/incremental_check.py +0 -0
  238. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/metadata.py +0 -0
  239. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/optional_deps.py +0 -0
  240. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/performance.py +0 -0
  241. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/persona_ownership.py +0 -0
  242. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/progress.py +0 -0
  243. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/project_artifact_write.py +0 -0
  244. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/prompts.py +0 -0
  245. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/sdd_discovery.py +0 -0
  246. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/source_scanner.py +0 -0
  247. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/startup_checks.py +0 -0
  248. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/structured_io.py +0 -0
  249. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/terminal.py +0 -0
  250. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/utils/yaml_utils.py +0 -0
  251. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validation/__init__.py +0 -0
  252. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validation/command_audit.py +0 -0
  253. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/__init__.py +0 -0
  254. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/agile_validation.py +0 -0
  255. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
  256. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/cli_first_validator.py +0 -0
  257. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/contract_validator.py +0 -0
  258. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/fsm.py +0 -0
  259. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/repro_checker.py +0 -0
  260. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/schema.py +0 -0
  261. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
  262. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
  263. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
  264. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
  265. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
  266. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
  267. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
  268. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
  269. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
  270. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
  271. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
  272. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
  273. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
  274. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/models.py +0 -0
  275. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
  276. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
  277. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
  278. {specfact_cli-0.46.25 → specfact_cli-0.47.3}/src/specfact_cli/versioning/__init__.py +0 -0
  279. {specfact_cli-0.46.25 → specfact_cli-0.47.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.46.25
3
+ Version: 0.47.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
@@ -299,6 +299,11 @@ Description-Content-Type: text/markdown
299
299
 
300
300
  </div>
301
301
 
302
+ ## Command Overview
303
+
304
+ - [Generated command overview for humans](docs/reference/commands.generated.md)
305
+ - [AI-agent command overview](llms.txt)
306
+
302
307
  ## Try it in 60 seconds
303
308
 
304
309
  ```bash
@@ -342,6 +347,8 @@ specfact code review run --path . --scope full
342
347
 
343
348
  The sample output comes from a pinned capture against `nold-ai/specfact-demo-repo`. Reproduce it with `docs/_support/readme-first-contact/capture-readme-output.sh`; capture metadata lives alongside the raw logs in `docs/_support/readme-first-contact/sample-output/`.
344
349
 
350
+ The Code Review bundle also reports `ai_bloat` advisories for code shapes that AI-assisted coding often amplifies, such as redundant wrappers, passthrough lambdas, identity `try`/`except` blocks, and avoidable intermediate lists. These findings are advisory, score-neutral, and not AI-authorship detection. Use the generated `.specfact/code-review.json` report with the Project bundle's `/specfact.08-simplify` IDE prompt to review each cleanup before accepting it. See the [AI bloat quickstart](https://modules.specfact.io/quickstart-ai-bloat/) on the modules docs site.
351
+
345
352
  ## What SpecFact does
346
353
 
347
354
  - **Reviews AI-assisted changes deterministically** — compare code against contracts, clean-code rules, and policy gates
@@ -16,6 +16,11 @@
16
16
 
17
17
  </div>
18
18
 
19
+ ## Command Overview
20
+
21
+ - [Generated command overview for humans](docs/reference/commands.generated.md)
22
+ - [AI-agent command overview](llms.txt)
23
+
19
24
  ## Try it in 60 seconds
20
25
 
21
26
  ```bash
@@ -59,6 +64,8 @@ specfact code review run --path . --scope full
59
64
 
60
65
  The sample output comes from a pinned capture against `nold-ai/specfact-demo-repo`. Reproduce it with `docs/_support/readme-first-contact/capture-readme-output.sh`; capture metadata lives alongside the raw logs in `docs/_support/readme-first-contact/sample-output/`.
61
66
 
67
+ The Code Review bundle also reports `ai_bloat` advisories for code shapes that AI-assisted coding often amplifies, such as redundant wrappers, passthrough lambdas, identity `try`/`except` blocks, and avoidable intermediate lists. These findings are advisory, score-neutral, and not AI-authorship detection. Use the generated `.specfact/code-review.json` report with the Project bundle's `/specfact.08-simplify` IDE prompt to review each cleanup before accepting it. See the [AI bloat quickstart](https://modules.specfact.io/quickstart-ai-bloat/) on the modules docs site.
68
+
62
69
  ## What SpecFact does
63
70
 
64
71
  - **Reviews AI-assisted changes deterministically** — compare code against contracts, clean-code rules, and policy gates
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "specfact-cli"
7
- version = "0.46.25"
7
+ version = "0.47.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"
@@ -247,12 +247,15 @@ yaml-check-all = "bash scripts/yaml-tools.sh check-all {args}"
247
247
  # Docs validation (docs-12): command examples vs CLI; modules.specfact.io URLs in docs
248
248
  check-docs-commands = "python scripts/check-docs-commands.py"
249
249
  check-cross-site-links = "python scripts/check-cross-site-links.py"
250
+ generate-command-overview = "python scripts/generate-command-overview.py --write"
251
+ check-command-overview = "python scripts/generate-command-overview.py --check"
252
+ check-command-contract = "python scripts/check-command-contract.py"
250
253
  doc-frontmatter-check = "python scripts/check_doc_frontmatter.py"
251
254
  validate-agent-rule-signals = "python scripts/validate_agent_rule_applies_when.py"
252
255
  check-version-sources = "python scripts/check_version_sources.py"
253
256
  check-pypi-ahead = "python scripts/check_local_version_ahead_of_pypi.py"
254
257
  release = "python scripts/check_local_version_ahead_of_pypi.py && python scripts/check_version_sources.py"
255
- docs-validate = "python scripts/check-docs-commands.py && python scripts/check-cross-site-links.py --warn-only && python scripts/check_doc_frontmatter.py && python scripts/validate_agent_rule_applies_when.py"
258
+ docs-validate = "python scripts/generate-command-overview.py --check && python scripts/check-command-contract.py && python scripts/check-docs-commands.py && python scripts/check-cross-site-links.py --warn-only && python scripts/check_doc_frontmatter.py && python scripts/validate_agent_rule_applies_when.py"
256
259
 
257
260
  # Legacy entry (kept for compatibility); prefer `workflows-lint` above
258
261
  lint-workflows = "bash scripts/run_actionlint.sh {args}"
@@ -0,0 +1,26 @@
1
+ {
2
+ "modules": [
3
+ {
4
+ "checksum_sha256": "3a563335d7a5e16e6e9ec975d6e5c77ff550d8880474eca03713b43e97d39a4a",
5
+ "download_url": "https://github.com/nold-ai/specfact-cli/releases/download/init-0.1.37.tar.gz",
6
+ "id": "init",
7
+ "latest_version": "0.1.37"
8
+ },
9
+ {
10
+ "checksum_sha256": "35c592374b209224af9a826c21efa66947cc16bf680b20ee0463f44bee4f9b59",
11
+ "download_url": "https://github.com/nold-ai/specfact-cli/releases/download/upgrade-0.1.21.tar.gz",
12
+ "id": "upgrade",
13
+ "latest_version": "0.1.21"
14
+ },
15
+ {
16
+ "checksum_sha256": "79359da27cb1c734a928a453a786b626e7b06654f515f32ead0cf96fa8e70bc2",
17
+ "download_url": "https://github.com/nold-ai/specfact-cli/releases/download/module-registry-0.1.26.tar.gz",
18
+ "id": "module-registry",
19
+ "latest_version": "0.1.26"
20
+ },
21
+ {
22
+ "id": "bundle-mapper",
23
+ "latest_version": "0.1.9"
24
+ }
25
+ ]
26
+ }
@@ -20,7 +20,7 @@
20
20
  - **Non-interactive / CI** must bootstrap workflow bundles before `specfact code …` or `specfact project …`:
21
21
  - `specfact init --profile solo-developer --repo .` (or another profile / `specfact init --install …`), **or**
22
22
  - `specfact module install nold-ai/specfact-codebase` (and other bundles as needed).
23
- - Contract repro in CI uses **`specfact code repro`** (not `specfact repro`). Optional: `specfact code repro setup` for CrossHair config.
23
+ - Contract repro in CI uses **`specfact code repro`**. Optional: `specfact code repro setup` for CrossHair config.
24
24
  - Optional `specfact project version check` needs a project under `.specfact/projects/<bundle>/` and `--bundle <name>`.
25
25
 
26
26
  **SpecFact CLI Validation Results:**
@@ -64,4 +64,3 @@
64
64
  ## 📝 Notes
65
65
 
66
66
  {{ notes | default("Add any additional notes here") }}
67
-
@@ -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.46.25"
6
+ __version__ = "0.47.3"
@@ -13,6 +13,7 @@ to ``sys.path`` so local development can load marketplace packages without insta
13
13
 
14
14
  from __future__ import annotations
15
15
 
16
+ import importlib.util
16
17
  import os
17
18
  import sys
18
19
  from pathlib import Path
@@ -25,6 +26,12 @@ def _candidate_modules_repo_roots() -> list[Path]:
25
26
  roots.append(Path(configured).expanduser())
26
27
 
27
28
  this_file = Path(__file__).resolve()
29
+ parts = this_file.parts
30
+ if "specfact-cli-worktrees" in parts:
31
+ marker_index = parts.index("specfact-cli-worktrees")
32
+ base = Path(*parts[:marker_index])
33
+ suffix = Path(*parts[marker_index + 1 : -3])
34
+ roots.append(base / "specfact-cli-modules-worktrees" / suffix)
28
35
  for base in (this_file.parent.parent.parent, *this_file.parents):
29
36
  roots.append(base / "specfact-cli-modules")
30
37
  roots.append(base.parent / "specfact-cli-modules")
@@ -45,6 +52,29 @@ def _bootstrap_bundle_paths() -> None:
45
52
 
46
53
  _bootstrap_bundle_paths()
47
54
 
48
- __version__ = "0.46.25"
55
+
56
+ def _install_progressive_disclosure() -> None:
57
+ module_name = "_specfact_progressive_disclosure_bootstrap"
58
+ if module_name in sys.modules:
59
+ return
60
+ module_path = Path(__file__).resolve().parent / "utils" / "progressive_disclosure.py"
61
+ spec = importlib.util.spec_from_file_location(module_name, module_path)
62
+ if spec is None or spec.loader is None:
63
+ return
64
+ module = importlib.util.module_from_spec(spec)
65
+ sys.modules[module_name] = module
66
+ try:
67
+ spec.loader.exec_module(module)
68
+ except Exception:
69
+ sys.modules.pop(module_name, None)
70
+ raise
71
+
72
+
73
+ # Install the shared Click/Typer usage-error contract as soon as core is imported.
74
+ # Module packages import specfact_cli before constructing direct Typer apps, so this
75
+ # keeps missing-command and missing-parameter UX consistent outside the root CLI too.
76
+ _install_progressive_disclosure()
77
+
78
+ __version__ = "0.47.3"
49
79
 
50
80
  __all__ = ["__version__"]
@@ -11,6 +11,7 @@ import inspect
11
11
  import os
12
12
  import sys
13
13
  from collections.abc import Callable, Mapping
14
+ from contextlib import suppress
14
15
  from dataclasses import dataclass
15
16
  from datetime import datetime
16
17
  from pathlib import Path
@@ -72,6 +73,12 @@ from specfact_cli.utils.progressive_disclosure import ProgressiveDisclosureGroup
72
73
  from specfact_cli.utils.structured_io import StructuredFormat
73
74
 
74
75
 
76
+ try:
77
+ from typer._click.exceptions import UsageError as TyperUsageError
78
+ except ImportError: # pragma: no cover - older Typer delegates directly to Click
79
+ TyperUsageError = click.UsageError # type: ignore[assignment,misc]
80
+
81
+
75
82
  # Names of commands that come from installable bundles; when not registered, show actionable error.
76
83
  KNOWN_BUNDLE_GROUP_OR_SHIM_NAMES: frozenset[str] = frozenset(
77
84
  {
@@ -165,9 +172,7 @@ class _RootCLIGroup(ProgressiveDisclosureGroup):
165
172
  """Root group that shows actionable error when an unknown command is a known bundle group/shim."""
166
173
 
167
174
  @ensure(lambda result: isinstance(result, tuple) and len(result) == 3, "result must be a 3-tuple")
168
- def resolve_command(
169
- self, ctx: click.Context, args: list[str]
170
- ) -> tuple[str | None, click.Command | None, list[str]]:
175
+ def resolve_command(self, ctx: Any, args: list[str]) -> Any:
171
176
  if not args:
172
177
  return super().resolve_command(ctx, args)
173
178
  invoked = args[0]
@@ -567,6 +572,10 @@ def _raise_lazy_delegate_click_exception(exc: Exception) -> NoReturn:
567
572
  raise click.ClickException(str(exc)) from exc
568
573
 
569
574
 
575
+ def _is_group_like(command: object) -> bool:
576
+ return hasattr(command, "list_commands") and hasattr(command, "get_command")
577
+
578
+
570
579
  def _load_lazy_delegate_typer(cmd_name: str) -> typer.Typer:
571
580
  resolved_name = resolve_command(cmd_name)
572
581
  try:
@@ -583,7 +592,7 @@ def _build_lazy_delegate_click_command(cmd_name: str, args: tuple[str, ...], rea
583
592
  from typer.main import get_command
584
593
 
585
594
  try:
586
- return get_command(real_typer)
595
+ return cast(click.Command, get_command(real_typer))
587
596
  except (RuntimeError, ValueError) as exc:
588
597
  if _args_request_help(args):
589
598
  _print_lazy_help_fallback(cmd_name, args)
@@ -593,6 +602,15 @@ def _build_lazy_delegate_click_command(cmd_name: str, args: tuple[str, ...], rea
593
602
 
594
603
 
595
604
  def _lazy_delegate_prog_name(ctx: click.Context, cmd_name: str) -> str:
605
+ original_prog_name = ctx.meta.get("original_prog_name")
606
+ if not isinstance(original_prog_name, str) and ctx.parent is not None:
607
+ original_prog_name = ctx.parent.meta.get("original_prog_name")
608
+ if isinstance(original_prog_name, str) and original_prog_name:
609
+ return original_prog_name
610
+ if isinstance(ctx.info_name, str) and " " in ctx.info_name:
611
+ return ctx.info_name
612
+ if isinstance(ctx.command_path, str) and " " in ctx.command_path:
613
+ return ctx.command_path
596
614
  parts: list[str] = []
597
615
  parent = ctx.parent
598
616
  while parent and getattr(parent, "command", None):
@@ -601,20 +619,46 @@ def _lazy_delegate_prog_name(ctx: click.Context, cmd_name: str) -> str:
601
619
  parts.append(name)
602
620
  parent = getattr(parent, "parent", None)
603
621
  if parts:
604
- return " ".join(reversed(parts))
605
- original_prog_name = ctx.meta.get("original_prog_name")
606
- if isinstance(original_prog_name, str) and original_prog_name:
607
- return original_prog_name
622
+ prog_name = " ".join(reversed(parts))
623
+ if prog_name == cmd_name:
624
+ return f"specfact {cmd_name}"
625
+ return prog_name
608
626
  return cmd_name
609
627
 
610
628
 
611
629
  def _strip_redundant_single_command_arg(click_cmd: click.Command, args: tuple[str, ...]) -> list[str]:
612
630
  args_list = list(args)
613
- if not isinstance(click_cmd, click.Group) and args_list and args_list[0] == getattr(click_cmd, "name", None):
631
+ if not _is_group_like(click_cmd) and args_list and args_list[0] == getattr(click_cmd, "name", None):
614
632
  return args_list[1:]
615
633
  return args_list
616
634
 
617
635
 
636
+ def _help_args_before_first_option(args: list[str]) -> list[str]:
637
+ help_args: list[str] = []
638
+ for arg in args:
639
+ if arg.startswith("-"):
640
+ break
641
+ help_args.append(arg)
642
+ return help_args
643
+
644
+
645
+ def _lazy_usage_error_message(exc: Exception, command: click.Command, ctx: click.Context | None) -> str:
646
+ message = str(getattr(exc, "format_message", lambda: str(exc))())
647
+ if not message.strip().lower().startswith("missing command"):
648
+ return message
649
+ names: list[str] = []
650
+ group = ctx.command if ctx is not None else command
651
+ if _is_group_like(group):
652
+ try:
653
+ command_ctx = ctx or click.Context(cast(click.Command, group))
654
+ names = [str(name) for name in group.list_commands(command_ctx)]
655
+ except Exception:
656
+ names = []
657
+ if names:
658
+ return f"Missing subcommand. Choose one of: {', '.join(names)}."
659
+ return "Missing subcommand."
660
+
661
+
618
662
  def _lazy_delegate_remaining_args(ctx: click.Context) -> list[str]:
619
663
  ctx_state = vars(ctx)
620
664
  protected_args = ctx_state.get("_protected_args") or ctx_state.get("protected_args") or ()
@@ -632,7 +676,11 @@ class _LazyDelegateGroup(click.Group):
632
676
  super().__init__(
633
677
  name=name or cmd_name,
634
678
  help=help or help_str,
635
- context_settings={"ignore_unknown_options": True},
679
+ context_settings={
680
+ "ignore_unknown_options": True,
681
+ "allow_extra_args": True,
682
+ "allow_interspersed_args": False,
683
+ },
636
684
  invoke_without_command=True,
637
685
  no_args_is_help=False,
638
686
  )
@@ -641,37 +689,75 @@ class _LazyDelegateGroup(click.Group):
641
689
  self._delegate_cmd = self._make_delegate_command()
642
690
 
643
691
  def _make_delegate_command(self) -> click.Command:
644
- cmd_name = self._lazy_cmd_name
645
-
646
692
  def _invoke(args: tuple[str, ...]) -> None:
647
- ctx = click.get_current_context()
648
- real_typer = _load_lazy_delegate_typer(cmd_name)
649
- click_cmd = _build_lazy_delegate_click_command(cmd_name, args, real_typer)
650
- # Build full prog name from root (e.g. "specfact sync") so usage shows "specfact sync bridge", not "sync sync bridge"
651
- prog_name = _lazy_delegate_prog_name(ctx, cmd_name)
652
- # When the real app is a single command (e.g. drift has only "detect"), Typer
653
- # builds a TyperCommand, not a Group. Then args are ["detect", "bundle", "--repo", ...]
654
- # and the command expects ["bundle", "--repo", ...] (no leading "detect").
655
- args_list = _strip_redundant_single_command_arg(click_cmd, args)
656
- exit_code = click_cmd.main(args=args_list, prog_name=prog_name, standalone_mode=False)
657
- if exit_code and exit_code != 0:
658
- raise SystemExit(exit_code)
693
+ self._invoke_real_command(click.get_current_context(), args)
659
694
 
660
695
  return click.Command(
661
696
  "__delegate__",
662
697
  callback=_invoke,
663
698
  params=[click.Argument(["args"], nargs=-1, type=click.UNPROCESSED)],
664
- context_settings={"ignore_unknown_options": True},
699
+ context_settings={
700
+ "ignore_unknown_options": True,
701
+ "allow_extra_args": True,
702
+ "allow_interspersed_args": False,
703
+ },
665
704
  add_help_option=False, # Pass --help through to real Typer so "specfact backlog daily ado --help" shows correct usage
666
705
  )
667
706
 
707
+ def _invoke_real_command(self, ctx: click.Context, args: tuple[str, ...] | list[str]) -> None:
708
+ cmd_name = self._lazy_cmd_name
709
+ real_typer = _load_lazy_delegate_typer(cmd_name)
710
+ args_tuple = tuple(args)
711
+ click_cmd = _build_lazy_delegate_click_command(cmd_name, args_tuple, real_typer)
712
+ prog_name = _lazy_delegate_prog_name(ctx, cmd_name)
713
+ args_list = _strip_redundant_single_command_arg(click_cmd, args_tuple)
714
+ try:
715
+ exit_code = click_cmd.main(args=args_list, prog_name=prog_name, standalone_mode=False)
716
+ except (click.UsageError, TyperUsageError) as exc:
717
+ if exc.ctx is None:
718
+ help_args = _help_args_before_first_option(args_list)
719
+ try:
720
+ click_cmd.main(args=[*help_args, "--help"], prog_name=prog_name, standalone_mode=False)
721
+ except BaseException as help_exit:
722
+ if not help_exit.__class__.__name__.endswith("Exit"):
723
+ raise
724
+ click.echo(f"Error: {_lazy_usage_error_message(exc, click_cmd, None)}", file=sys.stderr)
725
+ else:
726
+ # Help rendering is best-effort; the primary usage error is emitted below.
727
+ with suppress(Exception):
728
+ click.echo(exc.ctx.get_help(), file=sys.stderr)
729
+ click.echo("", file=sys.stderr)
730
+ click.echo(f"Error: {_lazy_usage_error_message(exc, click_cmd, exc.ctx)}", file=sys.stderr)
731
+ raise SystemExit(exc.exit_code) from None
732
+ except SystemExit as exc:
733
+ code = exc.code if isinstance(exc.code, int) else 1 if exc.code else 0
734
+ raise SystemExit(code) from None
735
+ except BaseException as exc:
736
+ if exc.__class__.__name__.endswith("Exit"):
737
+ raise SystemExit(getattr(exc, "exit_code", getattr(exc, "code", 0))) from None
738
+ raise
739
+ if exit_code and exit_code != 0:
740
+ raise SystemExit(exit_code)
741
+
742
+ def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
743
+ if _args_request_help(args):
744
+ try:
745
+ return super().parse_args(ctx, args)
746
+ except click.exceptions.Exit as exc:
747
+ raise SystemExit(exc.exit_code) from None
748
+ ctx.args = list(args)
749
+ return []
750
+
668
751
  @require(lambda ctx: ctx is not None, "ctx must not be None")
669
752
  @ensure(lambda result: result is None or isinstance(result, int), "result must be None or an exit code")
670
753
  def invoke(self, ctx: click.Context) -> Any:
671
754
  if ctx.invoked_subcommand is None:
672
755
  args = _lazy_delegate_remaining_args(ctx)
673
- ctx.meta["original_prog_name"] = ctx.command_path
674
- return self._delegate_cmd.main(args=args, prog_name=ctx.command_path, standalone_mode=False)
756
+ command_path = ctx.command_path
757
+ if command_path == self._lazy_cmd_name:
758
+ command_path = f"specfact {self._lazy_cmd_name}"
759
+ ctx.meta["original_prog_name"] = command_path
760
+ return self._invoke_real_command(ctx, args)
675
761
  return super().invoke(ctx)
676
762
 
677
763
  @require(_lazy_delegate_cmd_name_ready, "lazy command name must be set")
@@ -712,8 +798,8 @@ class _LazyDelegateGroup(click.Group):
712
798
  click_cmd = get_command(real_typer)
713
799
  except (RuntimeError, ValueError):
714
800
  return None
715
- if isinstance(click_cmd, click.Group):
716
- return click_cmd
801
+ if _is_group_like(click_cmd):
802
+ return cast(click.Group, click_cmd)
717
803
  return None
718
804
 
719
805
  @require(_lazy_delegate_cmd_name_ready, "lazy command name must be set before formatting help")
@@ -736,6 +822,10 @@ class _LazyDelegateGroup(click.Group):
736
822
  click_cmd.main(args=["-h"], prog_name=prog_name, standalone_mode=False)
737
823
  except SystemExit:
738
824
  raise # Re-raise so process exits (help was already printed with Rich)
825
+ except BaseException as exc:
826
+ if exc.__class__.__name__.endswith("Exit"):
827
+ raise SystemExit(getattr(exc, "exit_code", 0)) from None
828
+ raise
739
829
  # main() returned without exiting; Rich help was already printed, skip default formatter
740
830
  return
741
831
 
@@ -748,7 +838,7 @@ def _build_lazy_delegate_group(cmd_name: str, help_str: str) -> click.Group:
748
838
  def _flatten_specfact_nested_subgroup(result: click.Group, flatten_name: str) -> None:
749
839
  """Merge a nested subgroup named `flatten_name` into its parent and re-sort command order."""
750
840
  redundant = result.commands.pop(flatten_name)
751
- if isinstance(redundant, click.Group):
841
+ if _is_group_like(redundant):
752
842
  for cmd_name, cmd in redundant.commands.items():
753
843
  result.add_command(cmd, name=cmd_name)
754
844
  if result.commands:
@@ -814,6 +904,7 @@ def _get_group_from_info_wrapper(
814
904
  _typer_get_group_from_info_original: Callable[..., click.Group] | None = None
815
905
  _typer_get_command_original: Callable[[typer.Typer], click.Command] | None = None
816
906
  _typer_get_params_original: Callable[..., Any] | None = None
907
+ _typer_get_params_convertors_original: Callable[..., Any] | None = None
817
908
 
818
909
 
819
910
  def _specfact_get_params_from_function(func: Callable[..., Any]) -> Any:
@@ -838,6 +929,55 @@ def _specfact_get_params_from_function(func: Callable[..., Any]) -> Any:
838
929
  return orig(func)
839
930
 
840
931
 
932
+ def _is_context_annotation(annotation: object) -> bool:
933
+ if annotation in (click.Context, typer.Context):
934
+ return True
935
+ return getattr(annotation, "__name__", "") == "Context" and "click" in getattr(annotation, "__module__", "")
936
+
937
+
938
+ def _specfact_get_params_convertors_ctx_param_name_from_function(callback: Callable[..., Any] | None) -> Any:
939
+ """Treat both Click context classes as Typer context parameters across Typer releases."""
940
+ assert _typer_get_params_convertors_original is not None
941
+ original_error: RuntimeError | None = None
942
+ try:
943
+ return _typer_get_params_convertors_original(callback)
944
+ except RuntimeError as exc:
945
+ if callback is None or "click.core.Context" not in str(exc):
946
+ raise
947
+ original_error = exc
948
+ import typer.utils as typer_utils
949
+
950
+ params = _specfact_get_params_from_function(callback)
951
+ ctx_param_name: str | None = None
952
+ filtered_params: dict[str, Any] = {}
953
+ for name, param in params.items():
954
+ if ctx_param_name is None and _is_context_annotation(getattr(param, "annotation", None)):
955
+ ctx_param_name = name
956
+ continue
957
+ filtered_params[name] = param
958
+
959
+ if ctx_param_name is None:
960
+ raise RuntimeError("Unable to identify Click context parameter") from original_error
961
+
962
+ typer_main = cast(Any, importlib.import_module("typer.main"))
963
+ previous_main_get_params = typer_main.get_params_from_function
964
+ previous_utils_get_params = typer_utils.get_params_from_function
965
+
966
+ def _filtered_get_params(func: Callable[..., Any]) -> Any:
967
+ if func is callback:
968
+ return filtered_params
969
+ return previous_main_get_params(func)
970
+
971
+ try:
972
+ typer_main.get_params_from_function = _filtered_get_params
973
+ typer_utils.get_params_from_function = _filtered_get_params
974
+ click_params, convertors, _ignored_ctx = _typer_get_params_convertors_original(callback)
975
+ finally:
976
+ typer_main.get_params_from_function = previous_main_get_params
977
+ typer_utils.get_params_from_function = previous_utils_get_params
978
+ return click_params, convertors, ctx_param_name
979
+
980
+
841
981
  # Patch so root app build uses our delegate group for lazy typers (built via get_group_from_info).
842
982
  def _patch_typer_build() -> None:
843
983
  import typer.utils as typer_utils
@@ -845,6 +985,7 @@ def _patch_typer_build() -> None:
845
985
  typer_main = cast(Any, importlib.import_module("typer.main"))
846
986
 
847
987
  global _typer_get_group_from_info_original, _typer_get_command_original, _typer_get_params_original
988
+ global _typer_get_params_convertors_original
848
989
  # Save originals only on first patch; avoid overwriting with our wrapper when cli is re-imported (e.g. by plan module).
849
990
  if _typer_get_group_from_info_original is None:
850
991
  _typer_get_group_from_info_original = typer_main.get_group_from_info
@@ -852,9 +993,14 @@ def _patch_typer_build() -> None:
852
993
  _typer_get_command_original = typer_main.get_command
853
994
  if _typer_get_params_original is None:
854
995
  _typer_get_params_original = typer_utils.get_params_from_function
996
+ if _typer_get_params_convertors_original is None:
997
+ _typer_get_params_convertors_original = typer_main.get_params_convertors_ctx_param_name_from_function
855
998
  typer_utils.get_params_from_function = _specfact_get_params_from_function
856
999
  # typer.main may have bound get_params_from_function at import time; keep in sync.
857
1000
  typer_main.get_params_from_function = _specfact_get_params_from_function
1001
+ typer_main.get_params_convertors_ctx_param_name_from_function = (
1002
+ _specfact_get_params_convertors_ctx_param_name_from_function
1003
+ )
858
1004
  typer_main.get_command = _get_command
859
1005
  typer_main.get_group_from_info = _get_group_from_info_wrapper
860
1006
 
@@ -1,5 +1,5 @@
1
1
  name: init
2
- version: 0.1.33
2
+ version: 0.1.37
3
3
  commands:
4
4
  - init
5
5
  category: core
@@ -17,5 +17,5 @@ publisher:
17
17
  description: Initialize SpecFact workspace and bootstrap local configuration.
18
18
  license: Apache-2.0
19
19
  integrity:
20
- checksum: sha256:0086a4fa3e1f8744deee21640e73e6e2f24daf2b4a680b9157feed5487afc208
21
- signature: L7DtVqvg+zqc/CgJu/mOkvxYpfH9HuddbLS3EK5Uvnf6K/j/BofqopamZDdHVlevOGAb+dbsk06kskNOIJfkDQ==
20
+ checksum: sha256:8741e8fa3408bd38e7d65d0784a25466369955dd9f16f1981a52c0e1550afbe0
21
+ signature: kPAMdqpcAr92gr+DwxWibXfD0mgCLSb78EwGzoXH3fHct7xss8zt/SFvJLVZ0qkYHA8/4Nu6ls0iihRN9DXhCw==
@@ -7,7 +7,6 @@ import subprocess
7
7
  from pathlib import Path
8
8
  from typing import Any, cast
9
9
 
10
- import click
11
10
  import typer
12
11
  from beartype import beartype
13
12
  from icontract import ensure, require
@@ -189,6 +188,7 @@ import_to_bundle = module_io_shim.import_to_bundle
189
188
  export_from_bundle = module_io_shim.export_from_bundle
190
189
  sync_with_bundle = module_io_shim.sync_with_bundle
191
190
  validate_bundle = module_io_shim.validate_bundle
191
+ IDE_TARGETS_HELP = ", ".join([*sorted(IDE_CONFIG.keys()), "auto"])
192
192
 
193
193
 
194
194
  def _install_contract_enhancement_dependencies(repo_path: Path, env_info: EnvManagerInfo) -> None:
@@ -614,7 +614,7 @@ def init_ide(
614
614
  ide: str | None = typer.Option(
615
615
  None,
616
616
  "--ide",
617
- help="IDE type (cursor, vscode, copilot, claude, gemini, qwen, opencode, windsurf, kilocode, auggie, roo, codebuddy, amp, q, auto)",
617
+ help=f"IDE/agent target ({IDE_TARGETS_HELP})",
618
618
  ),
619
619
  env_manager: EnvManager = typer.Option(
620
620
  EnvManager.AUTO,
@@ -712,9 +712,8 @@ def init_ide(
712
712
  @app.callback(invoke_without_command=True)
713
713
  @require(lambda repo: _is_valid_repo_path(repo), "Repo path must exist and be directory")
714
714
  @ensure(lambda result: result is None, "Command should return None")
715
- @beartype
716
715
  def init(
717
- ctx: click.Context,
716
+ ctx: typer.Context,
718
717
  repo: Path = typer.Option(
719
718
  Path("."),
720
719
  "--repo",
@@ -1,5 +1,5 @@
1
1
  name: upgrade
2
- version: 0.1.15
2
+ version: 0.1.21
3
3
  commands:
4
4
  - upgrade
5
5
  category: core
@@ -17,5 +17,5 @@ publisher:
17
17
  description: Check and apply SpecFact CLI version upgrades.
18
18
  license: Apache-2.0
19
19
  integrity:
20
- checksum: sha256:134da8c48f8be96e9a4337a7cc2076bbf8b19e0d3f69161636bcec050f97e4c1
21
- signature: CK8Gq+/d0zWlU1Lo1I6EcgPiT3FzVsXj+CXFn7+zUnpXRdpYZxccLLZqh1Hvl2IPv3Y4yecVlOEcWZKtn0tJBg==
20
+ checksum: sha256:eae81fc948d038dc857827de5c9c5363c963dea13af9e78395b71aeb5411742b
21
+ signature: H8A/ySECBGR+f1iCNepv434cQ7MazJr1RMRKoDCPS9IuIoMmL3nm4HNOSBFWPTqeFVgTbMWlISPUSjFaF4AoDg==