specfact-cli 0.42.3__tar.gz → 0.42.4__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 (285) hide show
  1. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/PKG-INFO +3 -1
  2. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/README.md +2 -0
  3. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/pyproject.toml +1 -1
  4. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/__init__.py +1 -1
  5. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/__init__.py +1 -1
  6. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/adapters/ado.py +16 -3
  7. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/analyzers/code_analyzer.py +37 -24
  8. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modules/init/module-package.yaml +3 -3
  9. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modules/init/src/commands.py +96 -59
  10. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/module_packages.py +8 -1
  11. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/runtime.py +2 -1
  12. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/ide_setup.py +210 -51
  13. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/terminal.py +40 -0
  14. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/.gitignore +0 -0
  15. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/LICENSE +0 -0
  16. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/keys/README.md +0 -0
  17. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/keys/module-signing-public.pem +0 -0
  18. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/mappings/node-async.yaml +0 -0
  19. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/mappings/python-async.yaml +0 -0
  20. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/mappings/speckit-default.yaml +0 -0
  21. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/prompts/shared/cli-enforcement.md +0 -0
  22. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/prompts/specfact.01-import.md +0 -0
  23. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/prompts/specfact.02-plan.md +0 -0
  24. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/prompts/specfact.03-review.md +0 -0
  25. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/prompts/specfact.04-sdd.md +0 -0
  26. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/prompts/specfact.05-enforce.md +0 -0
  27. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/prompts/specfact.06-sync.md +0 -0
  28. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/prompts/specfact.07-contracts.md +0 -0
  29. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/prompts/specfact.compare.md +0 -0
  30. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/prompts/specfact.validate.md +0 -0
  31. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/schemas/deviation.schema.json +0 -0
  32. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/schemas/plan.schema.json +0 -0
  33. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/schemas/protocol.schema.json +0 -0
  34. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/templates/github-action.yml.j2 +0 -0
  35. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/templates/persona/architect.md.j2 +0 -0
  36. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/templates/persona/developer.md.j2 +0 -0
  37. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/templates/persona/product-owner.md.j2 +0 -0
  38. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/templates/plan.bundle.yaml.j2 +0 -0
  39. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/templates/policies/kanban.yaml +0 -0
  40. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/templates/policies/mixed.yaml +0 -0
  41. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/templates/policies/safe.yaml +0 -0
  42. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/templates/policies/scrum.yaml +0 -0
  43. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/templates/pr-template.md.j2 +0 -0
  44. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/templates/protocol.yaml.j2 +0 -0
  45. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/resources/templates/telemetry.yaml.example +0 -0
  46. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/__main__.py +0 -0
  47. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/adapters/__init__.py +0 -0
  48. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/adapters/backlog_base.py +0 -0
  49. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/adapters/base.py +0 -0
  50. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/adapters/github.py +0 -0
  51. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/adapters/openspec.py +0 -0
  52. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/adapters/openspec_parser.py +0 -0
  53. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/adapters/registry.py +0 -0
  54. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/adapters/speckit.py +0 -0
  55. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/agents/__init__.py +0 -0
  56. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/agents/analyze_agent.py +0 -0
  57. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/agents/base.py +0 -0
  58. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/agents/plan_agent.py +0 -0
  59. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/agents/registry.py +0 -0
  60. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/agents/sync_agent.py +0 -0
  61. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/analyzers/__init__.py +0 -0
  62. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
  63. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
  64. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
  65. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
  66. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
  67. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
  68. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
  69. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
  70. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/backlog/__init__.py +0 -0
  71. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
  72. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/backlog/adapters/base.py +0 -0
  73. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/backlog/converter.py +0 -0
  74. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/backlog/filters.py +0 -0
  75. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
  76. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
  77. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/backlog/mappers/base.py +0 -0
  78. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
  79. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
  80. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/cli.py +0 -0
  81. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/__init__.py +0 -0
  82. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/_bundle_shim.py +0 -0
  83. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/analyze.py +0 -0
  84. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/contract_cmd.py +0 -0
  85. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/drift.py +0 -0
  86. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/enforce.py +0 -0
  87. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/generate.py +0 -0
  88. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/import_cmd.py +0 -0
  89. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/init.py +0 -0
  90. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/migrate.py +0 -0
  91. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/plan.py +0 -0
  92. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/project_cmd.py +0 -0
  93. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/repro.py +0 -0
  94. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/sdd.py +0 -0
  95. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/spec.py +0 -0
  96. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/sync.py +0 -0
  97. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/update.py +0 -0
  98. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/commands/validate.py +0 -0
  99. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/common/__init__.py +0 -0
  100. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/common/bundle_factory.py +0 -0
  101. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/common/logger_setup.py +0 -0
  102. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/common/logging_utils.py +0 -0
  103. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/common/text_utils.py +0 -0
  104. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/common/utils.py +0 -0
  105. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/comparators/__init__.py +0 -0
  106. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/comparators/plan_comparator.py +0 -0
  107. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/contracts/__init__.py +0 -0
  108. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/contracts/crosshair_props.py +0 -0
  109. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/contracts/module_interface.py +0 -0
  110. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
  111. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
  112. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/generators/__init__.py +0 -0
  113. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/generators/contract_generator.py +0 -0
  114. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/generators/openapi_extractor.py +0 -0
  115. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/generators/persona_exporter.py +0 -0
  116. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/generators/plan_generator.py +0 -0
  117. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/generators/protocol_generator.py +0 -0
  118. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/generators/report_generator.py +0 -0
  119. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/generators/task_generator.py +0 -0
  120. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/generators/test_to_openapi.py +0 -0
  121. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/generators/workflow_generator.py +0 -0
  122. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/groups/__init__.py +0 -0
  123. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/groups/codebase_group.py +0 -0
  124. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/groups/govern_group.py +0 -0
  125. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/groups/member_group.py +0 -0
  126. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/groups/project_group.py +0 -0
  127. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/groups/spec_group.py +0 -0
  128. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/importers/__init__.py +0 -0
  129. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/importers/speckit_converter.py +0 -0
  130. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/importers/speckit_scanner.py +0 -0
  131. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/integrations/__init__.py +0 -0
  132. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/integrations/specmatic.py +0 -0
  133. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/merge/__init__.py +0 -0
  134. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/merge/resolver.py +0 -0
  135. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/migrations/__init__.py +0 -0
  136. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/migrations/plan_migrator.py +0 -0
  137. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/__init__.py +0 -0
  138. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/backlog_item.py +0 -0
  139. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/bridge.py +0 -0
  140. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/capabilities.py +0 -0
  141. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/change.py +0 -0
  142. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/contract.py +0 -0
  143. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/deviation.py +0 -0
  144. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/dor_config.py +0 -0
  145. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/enforcement.py +0 -0
  146. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/module_package.py +0 -0
  147. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/persona_template.py +0 -0
  148. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/plan.py +0 -0
  149. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/project.py +0 -0
  150. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/protocol.py +0 -0
  151. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/quality.py +0 -0
  152. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/sdd.py +0 -0
  153. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/source_tracking.py +0 -0
  154. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/task.py +0 -0
  155. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/models/validation.py +0 -0
  156. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modes/__init__.py +0 -0
  157. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modes/detector.py +0 -0
  158. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modes/router.py +0 -0
  159. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modules/__init__.py +0 -0
  160. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modules/_bundle_import.py +0 -0
  161. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modules/init/src/__init__.py +0 -0
  162. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modules/init/src/app.py +0 -0
  163. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modules/init/src/first_run_selection.py +0 -0
  164. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modules/module_io_shim.py +0 -0
  165. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modules/module_registry/module-package.yaml +0 -0
  166. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modules/module_registry/src/__init__.py +0 -0
  167. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modules/module_registry/src/app.py +0 -0
  168. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modules/module_registry/src/commands.py +0 -0
  169. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modules/upgrade/module-package.yaml +0 -0
  170. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modules/upgrade/src/__init__.py +0 -0
  171. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modules/upgrade/src/app.py +0 -0
  172. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/modules/upgrade/src/commands.py +0 -0
  173. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/parsers/__init__.py +0 -0
  174. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/parsers/persona_importer.py +0 -0
  175. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/__init__.py +0 -0
  176. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/alias_manager.py +0 -0
  177. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/bootstrap.py +0 -0
  178. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/bridge_registry.py +0 -0
  179. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/crypto_validator.py +0 -0
  180. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/custom_registries.py +0 -0
  181. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/dependency_resolver.py +0 -0
  182. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/extension_registry.py +0 -0
  183. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/help_cache.py +0 -0
  184. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/marketplace_client.py +0 -0
  185. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/metadata.py +0 -0
  186. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/module_discovery.py +0 -0
  187. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/module_grouping.py +0 -0
  188. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/module_installer.py +0 -0
  189. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/module_lifecycle.py +0 -0
  190. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/module_security.py +0 -0
  191. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/module_state.py +0 -0
  192. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/registry/registry.py +0 -0
  193. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/resources/semgrep/async.yml +0 -0
  194. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
  195. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
  196. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/__init__.py +0 -0
  197. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/bridge_probe.py +0 -0
  198. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/bridge_sync.py +0 -0
  199. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/bridge_sync_openspec_md_parse.py +0 -0
  200. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/bridge_sync_requirement_from_proposal.py +0 -0
  201. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/bridge_sync_requirement_helpers.py +0 -0
  202. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/bridge_sync_tasks_from_proposal.py +0 -0
  203. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/bridge_sync_what_changes_format.py +0 -0
  204. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/bridge_sync_write_openspec_from_proposal.py +0 -0
  205. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/bridge_watch.py +0 -0
  206. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/change_detector.py +0 -0
  207. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/code_to_spec.py +0 -0
  208. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/drift_detector.py +0 -0
  209. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/repository_sync.py +0 -0
  210. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/spec_to_code.py +0 -0
  211. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/spec_to_tests.py +0 -0
  212. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/watcher.py +0 -0
  213. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
  214. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/telemetry.py +0 -0
  215. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/templates/__init__.py +0 -0
  216. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
  217. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
  218. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
  219. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
  220. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
  221. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
  222. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
  223. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/templates/registry.py +0 -0
  224. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/templates/specification_templates.py +0 -0
  225. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/__init__.py +0 -0
  226. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
  227. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/auth_tokens.py +0 -0
  228. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/bundle_converters.py +0 -0
  229. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/bundle_loader.py +0 -0
  230. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/code_change_detector.py +0 -0
  231. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/console.py +0 -0
  232. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/content_sanitizer.py +0 -0
  233. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/context_detection.py +0 -0
  234. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/contract_predicates.py +0 -0
  235. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/enrichment_context.py +0 -0
  236. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/enrichment_parser.py +0 -0
  237. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/env_manager.py +0 -0
  238. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/feature_keys.py +0 -0
  239. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/git.py +0 -0
  240. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/github_annotations.py +0 -0
  241. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/icontract_helpers.py +0 -0
  242. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/incremental_check.py +0 -0
  243. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/metadata.py +0 -0
  244. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/optional_deps.py +0 -0
  245. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/performance.py +0 -0
  246. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/persona_ownership.py +0 -0
  247. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/progress.py +0 -0
  248. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
  249. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/prompts.py +0 -0
  250. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/sdd_discovery.py +0 -0
  251. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/source_scanner.py +0 -0
  252. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/startup_checks.py +0 -0
  253. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/structure.py +0 -0
  254. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/structured_io.py +0 -0
  255. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/suggestions.py +0 -0
  256. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/utils/yaml_utils.py +0 -0
  257. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validation/__init__.py +0 -0
  258. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validation/command_audit.py +0 -0
  259. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/__init__.py +0 -0
  260. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/agile_validation.py +0 -0
  261. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
  262. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/cli_first_validator.py +0 -0
  263. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/contract_validator.py +0 -0
  264. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/fsm.py +0 -0
  265. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/repro_checker.py +0 -0
  266. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/schema.py +0 -0
  267. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
  268. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
  269. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
  270. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
  271. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
  272. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
  273. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
  274. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
  275. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
  276. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
  277. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
  278. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
  279. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
  280. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/models.py +0 -0
  281. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
  282. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
  283. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
  284. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/src/specfact_cli/versioning/__init__.py +0 -0
  285. {specfact_cli-0.42.3 → specfact_cli-0.42.4}/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.42.3
3
+ Version: 0.42.4
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
@@ -342,6 +342,8 @@ specfact init ide --ide cursor
342
342
  specfact init ide --ide vscode
343
343
  ```
344
344
 
345
+ `specfact init ide` discovers prompt resources from installed workflow modules and exports them to your IDE. If module prompt payloads are not installed yet, the CLI uses packaged fallback resources.
346
+
345
347
  ### Run Your First Flow
346
348
 
347
349
  ```bash
@@ -61,6 +61,8 @@ specfact init ide --ide cursor
61
61
  specfact init ide --ide vscode
62
62
  ```
63
63
 
64
+ `specfact init ide` discovers prompt resources from installed workflow modules and exports them to your IDE. If module prompt payloads are not installed yet, the CLI uses packaged fallback resources.
65
+
64
66
  ### Run Your First Flow
65
67
 
66
68
  ```bash
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "specfact-cli"
7
- version = "0.42.3"
7
+ version = "0.42.4"
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.42.3"
6
+ __version__ = "0.42.4"
@@ -42,6 +42,6 @@ def _bootstrap_bundle_paths() -> None:
42
42
 
43
43
  _bootstrap_bundle_paths()
44
44
 
45
- __version__ = "0.42.3"
45
+ __version__ = "0.42.4"
46
46
 
47
47
  __all__ = ["__version__"]
@@ -12,10 +12,10 @@ from __future__ import annotations
12
12
 
13
13
  import os
14
14
  import re
15
- from collections.abc import Mapping
15
+ from collections.abc import Callable, Mapping
16
16
  from datetime import UTC, datetime
17
17
  from pathlib import Path
18
- from typing import Any, NoReturn, cast
18
+ from typing import Any, NoReturn, Protocol, cast
19
19
  from urllib.parse import urlparse
20
20
 
21
21
  import requests
@@ -51,6 +51,19 @@ _ADO_COMMENTS_API_VERSION = "7.1-preview.4"
51
51
  console = Console()
52
52
 
53
53
 
54
+ class _AccessTokenLike(Protocol):
55
+ """Typed subset of Azure access token fields used by refresh logic."""
56
+
57
+ token: str
58
+ expires_on: int
59
+
60
+
61
+ def _get_access_token(credential: Any, scopes: list[str]) -> _AccessTokenLike:
62
+ """Return a typed Azure access token from a credential object."""
63
+ get_token_fn = cast(Callable[..., _AccessTokenLike], credential.get_token)
64
+ return get_token_fn(*scopes)
65
+
66
+
54
67
  def _as_str_dict(obj: dict[Any, Any]) -> dict[str, Any]:
55
68
  """Narrow a runtime ``dict`` to ``dict[str, Any]`` for static analysis."""
56
69
  return cast(dict[str, Any], obj)
@@ -1850,7 +1863,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
1850
1863
  # offline_access is a reserved scope and cannot be explicitly requested
1851
1864
  azure_devops_resource = "499b84ac-1321-427f-aa17-267ca6975798/.default"
1852
1865
  azure_devops_scopes = [azure_devops_resource]
1853
- token = credential.get_token(*azure_devops_scopes)
1866
+ token = _get_access_token(credential, azure_devops_scopes)
1854
1867
 
1855
1868
  # Return refreshed token data
1856
1869
  from datetime import UTC, datetime
@@ -644,26 +644,8 @@ class CodeAnalyzer:
644
644
  # Extract classes as features
645
645
  for node in ast.walk(tree):
646
646
  if isinstance(node, ast.ClassDef):
647
- # For sequential keys, use placeholder (will be fixed after all features collected)
648
- # For classname keys, we can generate immediately
649
- current_count = 0 if self.key_format == "sequential" else len(self.features)
650
-
651
- # Extract Semgrep evidence for confidence scoring
652
- class_start_line = node.lineno if hasattr(node, "lineno") else None
653
- class_end_line = node.end_lineno if hasattr(node, "end_lineno") else None
654
- semgrep_evidence = self._extract_semgrep_evidence(
655
- semgrep_findings, node.name, class_start_line, class_end_line
656
- )
657
-
658
- # Create feature with Semgrep evidence included in confidence calculation
659
- feature = self._extract_feature_from_class_parallel(
660
- node, file_path, current_count, semgrep_evidence
661
- )
662
- if feature:
663
- # Enhance feature with detailed Semgrep findings (outcomes, constraints, themes)
664
- self._enhance_feature_with_semgrep(
665
- feature, semgrep_findings, file_path, node.name, class_start_line, class_end_line
666
- )
647
+ feature = self._extract_feature_for_results(node, file_path, semgrep_findings)
648
+ if feature is not None:
667
649
  results["features"].append(feature)
668
650
 
669
651
  except (SyntaxError, UnicodeDecodeError):
@@ -672,6 +654,31 @@ class CodeAnalyzer:
672
654
 
673
655
  return results
674
656
 
657
+ def _extract_feature_for_results(
658
+ self,
659
+ node: ast.ClassDef,
660
+ file_path: Path,
661
+ semgrep_findings: list[dict[str, Any]],
662
+ ) -> Feature | None:
663
+ """Extract one feature while isolating per-class enhancement failures."""
664
+ current_count = 0 if self.key_format == "sequential" else len(self.features)
665
+ class_start_line = node.lineno if hasattr(node, "lineno") else None
666
+ class_end_line = node.end_lineno if hasattr(node, "end_lineno") else None
667
+ semgrep_evidence = self._extract_semgrep_evidence(semgrep_findings, node.name, class_start_line, class_end_line)
668
+
669
+ feature = self._extract_feature_from_class_parallel(node, file_path, current_count, semgrep_evidence)
670
+ if feature is None:
671
+ return None
672
+
673
+ try:
674
+ self._enhance_feature_with_semgrep(
675
+ feature, semgrep_findings, file_path, node.name, class_start_line, class_end_line
676
+ )
677
+ except Exception as exc:
678
+ console.print(f"[dim]⚠ Warning: Skipped Semgrep enhancement for {file_path}:{node.name}: {exc}[/dim]")
679
+
680
+ return feature
681
+
675
682
  def _merge_analysis_results(self, results: dict[str, Any]) -> None:
676
683
  """Merge parallel analysis results into instance variables."""
677
684
  # Merge themes
@@ -1327,10 +1334,16 @@ class CodeAnalyzer:
1327
1334
  if not methods:
1328
1335
  return None, None
1329
1336
  primary_method = methods[0]
1330
- scenarios = self.control_flow_analyzer.extract_scenarios_from_method(
1331
- primary_method, class_name, primary_method.name
1332
- )
1333
- contracts = self.contract_extractor.extract_function_contracts(primary_method)
1337
+ try:
1338
+ scenarios = self.control_flow_analyzer.extract_scenarios_from_method(
1339
+ primary_method, class_name, primary_method.name
1340
+ )
1341
+ except Exception:
1342
+ scenarios = None
1343
+ try:
1344
+ contracts = self.contract_extractor.extract_function_contracts(primary_method)
1345
+ except Exception:
1346
+ contracts = None
1334
1347
  return scenarios, contracts
1335
1348
 
1336
1349
  def _generate_story_title(self, group_name: str, class_name: str) -> str:
@@ -1,5 +1,5 @@
1
1
  name: init
2
- version: 0.1.10
2
+ version: 0.1.13
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:e95ce8c81cc16aac931b977f78c0e4652a68f3d2e81aa09ce496d4753698d231
21
- signature: UaFkWSDeevp4et+OM5aKrEk2E+lnTP3idTJg1K0tmFn8bF9tgU1fnnswKSQtn1wL6VEP8lv7XIDxeKxVZP2SDg==
20
+ checksum: sha256:4f929d90edc481d21cf1aa9c2782f569a34baf560e25d3317067f1c5951509e9
21
+ signature: Sa60QWb6UaZRp40885CzkWMCGwT809/qQj1dnWIJcocwfLwT7Je+BR2STbGQv2RbTbTBBXmQ0Qwj/+AM3U0qAA==
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import subprocess
6
6
  from pathlib import Path
7
- from typing import Any
7
+ from typing import Any, cast
8
8
 
9
9
  import click
10
10
  import typer
@@ -23,14 +23,16 @@ from specfact_cli.registry.help_cache import run_discovery_and_write_cache
23
23
  from specfact_cli.registry.module_installer import USER_MODULES_ROOT as INIT_USER_MODULES_ROOT
24
24
  from specfact_cli.registry.module_packages import get_discovered_modules_for_state
25
25
  from specfact_cli.registry.module_state import write_modules_state
26
- from specfact_cli.runtime import debug_log_operation, debug_print, is_debug_mode, is_non_interactive
26
+ from specfact_cli.runtime import debug_print, is_non_interactive
27
27
  from specfact_cli.telemetry import telemetry
28
+ from specfact_cli.utils.contract_predicates import repo_path_exists, repo_path_is_dir
28
29
  from specfact_cli.utils.env_manager import EnvManager, EnvManagerInfo, build_tool_command, detect_env_manager
29
30
  from specfact_cli.utils.ide_setup import (
30
31
  IDE_CONFIG,
31
- SPECFACT_COMMANDS,
32
- copy_templates_to_ide,
32
+ _copy_template_files_to_ide,
33
+ _discover_module_resource_dirs,
33
34
  detect_ide,
35
+ discover_prompt_template_files,
34
36
  find_package_resources_path,
35
37
  )
36
38
 
@@ -49,8 +51,36 @@ install_bundles_for_init = first_run_selection.install_bundles_for_init
49
51
  is_first_run = first_run_selection.is_first_run
50
52
 
51
53
 
54
+ @beartype
55
+ @require(repo_path_exists, "Repo path must exist")
56
+ @require(repo_path_is_dir, "Repo path must be a directory")
57
+ @require(lambda ide: ide in IDE_CONFIG, "IDE must be valid")
58
+ @ensure(
59
+ lambda result: (
60
+ isinstance(result, tuple)
61
+ and len(result) == 2
62
+ and isinstance(result[0], list)
63
+ and all(isinstance(path, Path) for path in result[0])
64
+ and (result[1] is None or isinstance(result[1], Path))
65
+ ),
66
+ "Must return copied files and optional settings path",
67
+ )
68
+ def copy_templates_to_ide(repo_path: Path, ide: str, force: bool = False) -> tuple[list[Path], Path | None]:
69
+ """Compatibility wrapper that discovers prompt templates before copying them."""
70
+ return _copy_template_files_to_ide(repo_path, ide, discover_prompt_template_files(repo_path), force)
71
+
72
+
52
73
  def _resolve_field_mapping_templates_dir(repo_path: Path) -> Path | None:
53
74
  """Locate backlog field mapping templates (dev checkout or installed package)."""
75
+ for resource_root in _discover_module_resource_dirs(
76
+ "resources/templates/backlog/field_mappings",
77
+ repo_path=repo_path,
78
+ categories={"backlog"},
79
+ ):
80
+ installed_templates_dir = (resource_root / "templates" / "backlog" / "field_mappings").resolve()
81
+ if installed_templates_dir.exists():
82
+ return installed_templates_dir
83
+
54
84
  dev_templates_dir = (repo_path / "resources" / "templates" / "backlog" / "field_mappings").resolve()
55
85
  if dev_templates_dir.exists():
56
86
  return dev_templates_dir
@@ -161,7 +191,8 @@ def _questionary_style() -> Any:
161
191
  import questionary # type: ignore[reportMissingImports]
162
192
  except ImportError:
163
193
  return None
164
- return questionary.Style(
194
+ questionary_module = cast(Any, questionary)
195
+ return questionary_module.Style(
165
196
  [
166
197
  ("qmark", "fg:#00af87 bold"),
167
198
  ("question", "bold"),
@@ -214,12 +245,16 @@ def _run_module_checkbox_prompt(
214
245
  ) -> list[str]:
215
246
  action_title = "Enable" if action == "enable" else "Disable"
216
247
  current_state = "disabled" if action == "enable" else "enabled"
217
- selected: list[str] | None = questionary.checkbox(
218
- f"{action_title} module(s) from currently {current_state}:",
219
- choices=choices,
220
- instruction="(multi-select)",
221
- style=_questionary_style(),
222
- ).ask()
248
+ selected: list[str] | None = (
249
+ cast(Any, questionary)
250
+ .checkbox(
251
+ f"{action_title} module(s) from currently {current_state}:",
252
+ choices=choices,
253
+ instruction="(multi-select)",
254
+ style=_questionary_style(),
255
+ )
256
+ .ask()
257
+ )
223
258
  if not selected:
224
259
  return []
225
260
  return [display_to_id[s] for s in selected if s in display_to_id]
@@ -261,46 +296,35 @@ def _select_module_ids_interactive(action: str, modules_list: list[dict[str, Any
261
296
 
262
297
  def _resolve_templates_dir(repo_path: Path) -> Path | None:
263
298
  """Resolve templates directory from repo checkout or installed package."""
299
+ prompt_files = discover_prompt_template_files(repo_path, include_package_fallback=False)
300
+ if prompt_files:
301
+ return prompt_files[0].parent
302
+
264
303
  dev_templates_dir = (repo_path / "resources" / "prompts").resolve()
265
304
  if dev_templates_dir.exists():
266
305
  return dev_templates_dir
267
- try:
268
- import importlib.resources
269
306
 
270
- resources_ref = importlib.resources.files("specfact_cli")
271
- templates_ref = resources_ref / "resources" / "prompts"
272
- package_templates_dir = Path(str(templates_ref)).resolve()
273
- if package_templates_dir.exists():
274
- return package_templates_dir
275
- except Exception as exc:
276
- if is_debug_mode():
277
- debug_log_operation(
278
- "template_resolution",
279
- "importlib.resources(specfact_cli/resources/prompts)",
280
- "error",
281
- error=repr(exc),
282
- )
283
307
  return find_package_resources_path("specfact_cli", "resources/prompts")
284
308
 
285
309
 
286
- def _expected_ide_prompt_basenames(format_type: str) -> list[str]:
310
+ def _expected_ide_prompt_basenames(repo_path: Path, format_type: str) -> list[str]:
311
+ prompt_files = discover_prompt_template_files(repo_path)
287
312
  if format_type == "prompt.md":
288
- return [f"{cmd}.prompt.md" for cmd in SPECFACT_COMMANDS]
313
+ return [f"{path.stem}.prompt.md" for path in prompt_files]
289
314
  if format_type == "toml":
290
- return [f"{cmd}.toml" for cmd in SPECFACT_COMMANDS]
291
- return [f"{cmd}.md" for cmd in SPECFACT_COMMANDS]
315
+ return [f"{path.stem}.toml" for path in prompt_files]
316
+ return [path.name for path in prompt_files]
292
317
 
293
318
 
294
- def _count_outdated_ide_prompts(ide_dir: Path, templates_dir: Path, format_type: str) -> int:
319
+ def _count_outdated_ide_prompts(ide_dir: Path, prompt_files: list[Path], format_type: str) -> int:
295
320
  outdated = 0
296
- for cmd in SPECFACT_COMMANDS:
297
- src = templates_dir / f"{cmd}.md"
321
+ for src in prompt_files:
298
322
  if format_type == "prompt.md":
299
- dest = ide_dir / f"{cmd}.prompt.md"
323
+ dest = ide_dir / f"{src.stem}.prompt.md"
300
324
  elif format_type == "toml":
301
- dest = ide_dir / f"{cmd}.toml"
325
+ dest = ide_dir / f"{src.stem}.toml"
302
326
  else:
303
- dest = ide_dir / f"{cmd}.md"
327
+ dest = ide_dir / src.name
304
328
  if src.exists() and dest.exists() and dest.stat().st_mtime < src.stat().st_mtime:
305
329
  outdated += 1
306
330
  return outdated
@@ -312,7 +336,7 @@ def _audit_prompt_installation(repo_path: Path) -> None:
312
336
  config = IDE_CONFIG[detected_ide]
313
337
  ide_dir = repo_path / str(config["folder"])
314
338
  format_type = str(config["format"])
315
- expected_files = _expected_ide_prompt_basenames(format_type)
339
+ expected_files = _expected_ide_prompt_basenames(repo_path, format_type)
316
340
 
317
341
  if not ide_dir.exists():
318
342
  console.print(
@@ -322,8 +346,8 @@ def _audit_prompt_installation(repo_path: Path) -> None:
322
346
  return
323
347
 
324
348
  missing = [name for name in expected_files if not (ide_dir / name).exists()]
325
- templates_dir = _resolve_templates_dir(repo_path)
326
- outdated = _count_outdated_ide_prompts(ide_dir, templates_dir, format_type) if templates_dir else 0
349
+ prompt_files = discover_prompt_template_files(repo_path)
350
+ outdated = _count_outdated_ide_prompts(ide_dir, prompt_files, format_type) if prompt_files else 0
327
351
 
328
352
  if not missing and outdated == 0:
329
353
  console.print(f"[green]Prompt status:[/green] {detected_ide} prompts are present and up to date.")
@@ -362,12 +386,16 @@ def _select_ide_interactive(default_ide: str) -> str:
362
386
  choices.append(label)
363
387
 
364
388
  default_label = next((lbl for lbl, iid in label_to_ide.items() if iid == default_ide), choices[0])
365
- selected = questionary.select(
366
- "Select IDE for prompt setup",
367
- choices=choices,
368
- default=default_label,
369
- style=_questionary_style(),
370
- ).ask()
389
+ selected = (
390
+ cast(Any, questionary)
391
+ .select(
392
+ "Select IDE for prompt setup",
393
+ choices=choices,
394
+ default=default_label,
395
+ style=_questionary_style(),
396
+ )
397
+ .ask()
398
+ )
371
399
  if not selected:
372
400
  raise typer.Exit(1)
373
401
  console.print(Rule(style="dim"))
@@ -441,11 +469,15 @@ def _manual_bundle_ids_from_questionary(questionary: Any) -> list[str]:
441
469
  f"{first_run_selection.BUNDLE_DISPLAY.get(bid, bid)} [dim]({bid})[/dim]"
442
470
  for bid in first_run_selection.CANONICAL_BUNDLES
443
471
  ]
444
- selected = questionary.checkbox(
445
- "Select bundles to install:",
446
- choices=bundle_choices,
447
- style=_questionary_style(),
448
- ).ask()
472
+ selected = (
473
+ cast(Any, questionary)
474
+ .checkbox(
475
+ "Select bundles to install:",
476
+ choices=bundle_choices,
477
+ style=_questionary_style(),
478
+ )
479
+ .ask()
480
+ )
449
481
  if not selected:
450
482
  return []
451
483
  return [bid for bid in first_run_selection.CANONICAL_BUNDLES if any(bid in s for s in selected)]
@@ -489,11 +521,15 @@ def _interactive_first_run_bundle_selection() -> list[str]:
489
521
  profile_to_key = {f"{label} [dim]({key})[/dim]": key for key, label in first_run_selection.PROFILE_DISPLAY_ORDER}
490
522
  profile_to_key["Choose bundles manually"] = "_manual_"
491
523
 
492
- choice = questionary.select(
493
- "Select a profile or choose bundles manually:",
494
- choices=[*profile_choices, "Choose bundles manually"],
495
- style=_questionary_style(),
496
- ).ask()
524
+ choice = (
525
+ cast(Any, questionary)
526
+ .select(
527
+ "Select a profile or choose bundles manually:",
528
+ choices=[*profile_choices, "Choose bundles manually"],
529
+ style=_questionary_style(),
530
+ )
531
+ .ask()
532
+ )
497
533
 
498
534
  if not choice:
499
535
  return []
@@ -559,13 +595,14 @@ def init_ide(
559
595
  if install_deps:
560
596
  _install_contract_enhancement_dependencies(repo_path, env_info)
561
597
 
562
- templates_dir = _resolve_templates_dir(repo_path)
563
- if not templates_dir or not templates_dir.exists():
598
+ prompt_files = discover_prompt_template_files(repo_path)
599
+ if not prompt_files:
564
600
  console.print("[red]Error:[/red] Templates directory not found.")
565
601
  raise typer.Exit(1)
566
602
 
567
- console.print(f"[cyan]Templates:[/cyan] {templates_dir}")
568
- copied_files, settings_path = copy_templates_to_ide(repo_path, selected_ide, templates_dir, force)
603
+ template_sources = ", ".join(sorted({str(path.parent) for path in prompt_files}))
604
+ console.print(f"[cyan]Templates:[/cyan] {template_sources}")
605
+ copied_files, settings_path = copy_templates_to_ide(repo_path, selected_ide, force)
569
606
  _copy_backlog_field_mapping_templates(repo_path, force, console)
570
607
 
571
608
  console.print()
@@ -578,7 +578,14 @@ def _make_package_loader(package_dir: Path, package_name: str, command_name: str
578
578
  raise ValueError(f"Cannot load from {package_dir.name}")
579
579
  mod = importlib.util.module_from_spec(spec)
580
580
  sys.modules[spec.name] = mod
581
- spec.loader.exec_module(mod)
581
+ try:
582
+ spec.loader.exec_module(mod)
583
+ except (ImportError, ModuleNotFoundError, OSError) as exc:
584
+ raise ValueError(
585
+ "Runtime compatibility error while loading "
586
+ f"module '{package_name}' command '{command_name}' from {package_dir}: {exc}. "
587
+ f"Reinstall the module and run SpecFact with the same Python interpreter ({sys.executable})."
588
+ ) from exc
582
589
  command_attr = f"{_normalized_module_name(command_name)}_app"
583
590
  app = getattr(mod, command_attr, None)
584
591
  if app is None:
@@ -28,7 +28,7 @@ from specfact_cli.common.logger_setup import (
28
28
  )
29
29
  from specfact_cli.modes import OperationalMode
30
30
  from specfact_cli.utils.structured_io import StructuredFormat
31
- from specfact_cli.utils.terminal import detect_terminal_capabilities, get_console_config
31
+ from specfact_cli.utils.terminal import detect_terminal_capabilities, ensure_output_stream_safety, get_console_config
32
32
 
33
33
 
34
34
  DEBUG_LOG_DATEFMT = "%Y-%m-%d %H:%M:%S"
@@ -201,6 +201,7 @@ def get_configured_console() -> Console:
201
201
  (e.g. CliRunner's captured stdout after invoke() ends).
202
202
  """
203
203
  mode = get_terminal_mode()
204
+ ensure_output_stream_safety()
204
205
 
205
206
  if _is_test_env():
206
207
  config = get_console_config()