specfact-cli 0.31.0__tar.gz → 0.32.0__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 (346) hide show
  1. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/.gitignore +3 -1
  2. specfact_cli-0.31.0/LICENSE.md → specfact_cli-0.32.0/LICENSE +3 -4
  3. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/PKG-INFO +8 -8
  4. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/README.md +3 -2
  5. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/pyproject.toml +3 -3
  6. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/__init__.py +1 -1
  7. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/__init__.py +1 -1
  8. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/code_analyzer.py +7 -5
  9. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/change.py +8 -4
  10. specfact_cli-0.32.0/src/specfact_cli/models/module_package.py +158 -0
  11. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/plan.py +30 -0
  12. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/project.py +28 -0
  13. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/commands.py +3 -4
  14. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/plan/src/commands.py +3 -3
  15. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/sync/src/commands.py +3 -2
  16. specfact_cli-0.32.0/src/specfact_cli/registry/crypto_validator.py +124 -0
  17. specfact_cli-0.32.0/src/specfact_cli/registry/extension_registry.py +59 -0
  18. specfact_cli-0.32.0/src/specfact_cli/registry/module_installer.py +60 -0
  19. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/registry/module_packages.py +88 -2
  20. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/telemetry.py +19 -14
  21. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/registry.py +5 -3
  22. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/ide_setup.py +5 -3
  23. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/terminal.py +3 -4
  24. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/repro_checker.py +4 -2
  25. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/schema.py +8 -4
  26. specfact_cli-0.31.0/src/specfact_cli/models/module_package.py +0 -73
  27. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/mappings/node-async.yaml +0 -0
  28. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/mappings/python-async.yaml +0 -0
  29. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/mappings/speckit-default.yaml +0 -0
  30. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/shared/cli-enforcement.md +0 -0
  31. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.01-import.md +0 -0
  32. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.02-plan.md +0 -0
  33. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.03-review.md +0 -0
  34. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.04-sdd.md +0 -0
  35. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.05-enforce.md +0 -0
  36. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.06-sync.md +0 -0
  37. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.07-contracts.md +0 -0
  38. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.backlog-daily.md +0 -0
  39. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.backlog-refine.md +0 -0
  40. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.compare.md +0 -0
  41. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.sync-backlog.md +0 -0
  42. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.validate.md +0 -0
  43. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/schemas/deviation.schema.json +0 -0
  44. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/schemas/plan.schema.json +0 -0
  45. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/schemas/protocol.schema.json +0 -0
  46. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/defaults/defect_v1.yaml +0 -0
  47. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/defaults/enabler_v1.yaml +0 -0
  48. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/defaults/spike_v1.yaml +0 -0
  49. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/defaults/user_story_v1.yaml +0 -0
  50. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/field_mappings/ado_agile.yaml +0 -0
  51. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/field_mappings/ado_default.yaml +0 -0
  52. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/field_mappings/ado_kanban.yaml +0 -0
  53. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/field_mappings/ado_safe.yaml +0 -0
  54. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/field_mappings/ado_scrum.yaml +0 -0
  55. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/frameworks/safe/safe_feature_v1.yaml +0 -0
  56. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/frameworks/scrum/user_story_v1.yaml +0 -0
  57. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/personas/developer/developer_task_v1.yaml +0 -0
  58. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/personas/product-owner/user_story_v1.yaml +0 -0
  59. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/providers/ado/work_item_v1.yaml +0 -0
  60. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/github-action.yml.j2 +0 -0
  61. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/persona/architect.md.j2 +0 -0
  62. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/persona/developer.md.j2 +0 -0
  63. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/persona/product-owner.md.j2 +0 -0
  64. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/plan.bundle.yaml.j2 +0 -0
  65. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/pr-template.md.j2 +0 -0
  66. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/protocol.yaml.j2 +0 -0
  67. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/telemetry.yaml.example +0 -0
  68. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/__main__.py +0 -0
  69. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/__init__.py +0 -0
  70. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/ado.py +0 -0
  71. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/backlog_base.py +0 -0
  72. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/base.py +0 -0
  73. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/github.py +0 -0
  74. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/openspec.py +0 -0
  75. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/openspec_parser.py +0 -0
  76. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/registry.py +0 -0
  77. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/speckit.py +0 -0
  78. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/agents/__init__.py +0 -0
  79. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/agents/analyze_agent.py +0 -0
  80. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/agents/base.py +0 -0
  81. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/agents/plan_agent.py +0 -0
  82. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/agents/registry.py +0 -0
  83. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/agents/sync_agent.py +0 -0
  84. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/__init__.py +0 -0
  85. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
  86. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
  87. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
  88. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
  89. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
  90. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
  91. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
  92. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
  93. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/__init__.py +0 -0
  94. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
  95. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/adapters/base.py +0 -0
  96. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/adapters/local_yaml_adapter.py +0 -0
  97. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/ai_refiner.py +0 -0
  98. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/converter.py +0 -0
  99. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/filters.py +0 -0
  100. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/format_detector.py +0 -0
  101. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/formats/__init__.py +0 -0
  102. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/formats/base.py +0 -0
  103. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/formats/markdown_format.py +0 -0
  104. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/formats/structured_format.py +0 -0
  105. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
  106. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
  107. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/mappers/base.py +0 -0
  108. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
  109. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
  110. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/template_detector.py +0 -0
  111. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/cli.py +0 -0
  112. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/__init__.py +0 -0
  113. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/analyze.py +0 -0
  114. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/auth.py +0 -0
  115. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/backlog_commands.py +0 -0
  116. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/contract_cmd.py +0 -0
  117. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/drift.py +0 -0
  118. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/enforce.py +0 -0
  119. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/generate.py +0 -0
  120. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/import_cmd.py +0 -0
  121. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/init.py +0 -0
  122. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/migrate.py +0 -0
  123. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/plan.py +0 -0
  124. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/project_cmd.py +0 -0
  125. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/repro.py +0 -0
  126. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/sdd.py +0 -0
  127. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/spec.py +0 -0
  128. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/sync.py +0 -0
  129. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/update.py +0 -0
  130. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/validate.py +0 -0
  131. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/common/__init__.py +0 -0
  132. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/common/logger_setup.py +0 -0
  133. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/common/logging_utils.py +0 -0
  134. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/common/text_utils.py +0 -0
  135. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/common/utils.py +0 -0
  136. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/comparators/__init__.py +0 -0
  137. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/comparators/plan_comparator.py +0 -0
  138. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/contracts/__init__.py +0 -0
  139. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/contracts/crosshair_props.py +0 -0
  140. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/contracts/module_interface.py +0 -0
  141. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
  142. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
  143. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/__init__.py +0 -0
  144. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/contract_generator.py +0 -0
  145. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/openapi_extractor.py +0 -0
  146. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/persona_exporter.py +0 -0
  147. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/plan_generator.py +0 -0
  148. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/protocol_generator.py +0 -0
  149. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/report_generator.py +0 -0
  150. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/task_generator.py +0 -0
  151. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/test_to_openapi.py +0 -0
  152. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/workflow_generator.py +0 -0
  153. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/importers/__init__.py +0 -0
  154. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/importers/speckit_converter.py +0 -0
  155. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/importers/speckit_scanner.py +0 -0
  156. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/integrations/__init__.py +0 -0
  157. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/integrations/specmatic.py +0 -0
  158. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/merge/__init__.py +0 -0
  159. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/merge/resolver.py +0 -0
  160. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/migrations/__init__.py +0 -0
  161. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/migrations/plan_migrator.py +0 -0
  162. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/__init__.py +0 -0
  163. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/backlog_item.py +0 -0
  164. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/bridge.py +0 -0
  165. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/capabilities.py +0 -0
  166. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/contract.py +0 -0
  167. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/deviation.py +0 -0
  168. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/dor_config.py +0 -0
  169. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/enforcement.py +0 -0
  170. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/persona_template.py +0 -0
  171. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/protocol.py +0 -0
  172. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/quality.py +0 -0
  173. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/sdd.py +0 -0
  174. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/source_tracking.py +0 -0
  175. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/task.py +0 -0
  176. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/validation.py +0 -0
  177. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modes/__init__.py +0 -0
  178. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modes/detector.py +0 -0
  179. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modes/router.py +0 -0
  180. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/__init__.py +0 -0
  181. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/analyze/module-package.yaml +0 -0
  182. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/analyze/src/__init__.py +0 -0
  183. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/analyze/src/app.py +0 -0
  184. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/analyze/src/commands.py +0 -0
  185. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/auth/module-package.yaml +0 -0
  186. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/auth/src/__init__.py +0 -0
  187. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/auth/src/app.py +0 -0
  188. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/auth/src/commands.py +0 -0
  189. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/module-package.yaml +0 -0
  190. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/__init__.py +0 -0
  191. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/adapters/__init__.py +0 -0
  192. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/adapters/ado.py +0 -0
  193. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/adapters/base.py +0 -0
  194. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/adapters/github.py +0 -0
  195. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/adapters/jira.py +0 -0
  196. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/adapters/linear.py +0 -0
  197. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/app.py +0 -0
  198. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/contract/module-package.yaml +0 -0
  199. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/contract/src/__init__.py +0 -0
  200. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/contract/src/app.py +0 -0
  201. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/contract/src/commands.py +0 -0
  202. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/drift/module-package.yaml +0 -0
  203. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/drift/src/__init__.py +0 -0
  204. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/drift/src/app.py +0 -0
  205. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/drift/src/commands.py +0 -0
  206. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/enforce/module-package.yaml +0 -0
  207. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/enforce/src/__init__.py +0 -0
  208. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/enforce/src/app.py +0 -0
  209. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/enforce/src/commands.py +0 -0
  210. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/generate/module-package.yaml +0 -0
  211. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/generate/src/__init__.py +0 -0
  212. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/generate/src/app.py +0 -0
  213. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/generate/src/commands.py +0 -0
  214. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/import_cmd/module-package.yaml +0 -0
  215. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/import_cmd/src/__init__.py +0 -0
  216. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/import_cmd/src/app.py +0 -0
  217. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/import_cmd/src/commands.py +0 -0
  218. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/init/module-package.yaml +0 -0
  219. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/init/src/__init__.py +0 -0
  220. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/init/src/app.py +0 -0
  221. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/init/src/commands.py +0 -0
  222. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/migrate/module-package.yaml +0 -0
  223. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/migrate/src/__init__.py +0 -0
  224. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/migrate/src/app.py +0 -0
  225. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/migrate/src/commands.py +0 -0
  226. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/module_io_shim.py +0 -0
  227. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/plan/module-package.yaml +0 -0
  228. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/plan/src/__init__.py +0 -0
  229. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/plan/src/app.py +0 -0
  230. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/project/module-package.yaml +0 -0
  231. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/project/src/__init__.py +0 -0
  232. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/project/src/app.py +0 -0
  233. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/project/src/commands.py +0 -0
  234. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/repro/module-package.yaml +0 -0
  235. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/repro/src/__init__.py +0 -0
  236. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/repro/src/app.py +0 -0
  237. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/repro/src/commands.py +0 -0
  238. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/sdd/module-package.yaml +0 -0
  239. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/sdd/src/__init__.py +0 -0
  240. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/sdd/src/app.py +0 -0
  241. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/sdd/src/commands.py +0 -0
  242. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/spec/module-package.yaml +0 -0
  243. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/spec/src/__init__.py +0 -0
  244. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/spec/src/app.py +0 -0
  245. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/spec/src/commands.py +0 -0
  246. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/sync/module-package.yaml +0 -0
  247. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/sync/src/__init__.py +0 -0
  248. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/sync/src/app.py +0 -0
  249. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/upgrade/module-package.yaml +0 -0
  250. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/upgrade/src/__init__.py +0 -0
  251. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/upgrade/src/app.py +0 -0
  252. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/upgrade/src/commands.py +0 -0
  253. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/validate/module-package.yaml +0 -0
  254. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/validate/src/__init__.py +0 -0
  255. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/validate/src/app.py +0 -0
  256. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/validate/src/commands.py +0 -0
  257. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/parsers/__init__.py +0 -0
  258. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/parsers/persona_importer.py +0 -0
  259. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/registry/__init__.py +0 -0
  260. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/registry/bootstrap.py +0 -0
  261. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/registry/bridge_registry.py +0 -0
  262. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/registry/help_cache.py +0 -0
  263. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/registry/metadata.py +0 -0
  264. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/registry/module_state.py +0 -0
  265. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/registry/registry.py +0 -0
  266. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/resources/semgrep/async.yml +0 -0
  267. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
  268. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
  269. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/runtime.py +0 -0
  270. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/__init__.py +0 -0
  271. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/bridge_probe.py +0 -0
  272. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/bridge_sync.py +0 -0
  273. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/bridge_watch.py +0 -0
  274. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/change_detector.py +0 -0
  275. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/code_to_spec.py +0 -0
  276. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/drift_detector.py +0 -0
  277. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/repository_sync.py +0 -0
  278. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/spec_to_code.py +0 -0
  279. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/spec_to_tests.py +0 -0
  280. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/watcher.py +0 -0
  281. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
  282. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/__init__.py +0 -0
  283. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/bridge_templates.py +0 -0
  284. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
  285. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
  286. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
  287. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
  288. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
  289. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
  290. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
  291. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/specification_templates.py +0 -0
  292. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/__init__.py +0 -0
  293. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
  294. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/auth_tokens.py +0 -0
  295. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/bundle_converters.py +0 -0
  296. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/bundle_loader.py +0 -0
  297. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/code_change_detector.py +0 -0
  298. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/console.py +0 -0
  299. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/content_sanitizer.py +0 -0
  300. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/context_detection.py +0 -0
  301. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/enrichment_context.py +0 -0
  302. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/enrichment_parser.py +0 -0
  303. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/env_manager.py +0 -0
  304. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/feature_keys.py +0 -0
  305. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/git.py +0 -0
  306. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/github_annotations.py +0 -0
  307. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/incremental_check.py +0 -0
  308. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/metadata.py +0 -0
  309. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/optional_deps.py +0 -0
  310. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/performance.py +0 -0
  311. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/persona_ownership.py +0 -0
  312. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/progress.py +0 -0
  313. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
  314. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/prompts.py +0 -0
  315. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/sdd_discovery.py +0 -0
  316. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/source_scanner.py +0 -0
  317. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/startup_checks.py +0 -0
  318. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/structure.py +0 -0
  319. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/structured_io.py +0 -0
  320. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/suggestions.py +0 -0
  321. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/yaml_utils.py +0 -0
  322. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/__init__.py +0 -0
  323. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/agile_validation.py +0 -0
  324. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
  325. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/cli_first_validator.py +0 -0
  326. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/contract_validator.py +0 -0
  327. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/fsm.py +0 -0
  328. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
  329. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
  330. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
  331. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
  332. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
  333. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
  334. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
  335. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
  336. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
  337. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
  338. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
  339. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
  340. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
  341. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/models.py +0 -0
  342. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
  343. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
  344. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
  345. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/versioning/__init__.py +0 -0
  346. {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/versioning/analyzer.py +0 -0
@@ -138,4 +138,6 @@ harness_contracts.py
138
138
  # semgrep artifacts
139
139
  lang.json
140
140
  Language.ml
141
- Language.mli
141
+ Language.mli
142
+
143
+ .artifacts
@@ -35,8 +35,7 @@
35
35
  "Work" shall mean the work of authorship, whether in Source or
36
36
  Object form, made available under the License, as indicated by a
37
37
  copyright notice that is included in or attached to the work
38
- (which shall not include Communications that are clearly marked or
39
- otherwise designated in writing by the copyright owner as "Not a Work").
38
+ (an example is provided in the Appendix below).
40
39
 
41
40
  "Derivative Works" shall mean any work, whether in Source or Object
42
41
  form, that is based on (or derived from) the Work and for which the
@@ -57,8 +56,8 @@
57
56
  communication on electronic mailing lists, source code control systems,
58
57
  and issue tracking systems that are managed by, or on behalf of, the
59
58
  Licensor for the purpose of discussing and improving the Work, but
60
- excluding communication that is clearly marked or otherwise designated
61
- in writing by the copyright owner as "Not a Contribution".
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
62
61
 
63
62
  "Contributor" shall mean Licensor and any individual or Legal Entity
64
63
  on behalf of whom a Contribution has been received by Licensor and
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specfact-cli
3
- Version: 0.31.0
3
+ Version: 0.32.0
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
@@ -45,8 +45,7 @@ License: Apache License
45
45
  "Work" shall mean the work of authorship, whether in Source or
46
46
  Object form, made available under the License, as indicated by a
47
47
  copyright notice that is included in or attached to the work
48
- (which shall not include Communications that are clearly marked or
49
- otherwise designated in writing by the copyright owner as "Not a Work").
48
+ (an example is provided in the Appendix below).
50
49
 
51
50
  "Derivative Works" shall mean any work, whether in Source or Object
52
51
  form, that is based on (or derived from) the Work and for which the
@@ -67,8 +66,8 @@ License: Apache License
67
66
  communication on electronic mailing lists, source code control systems,
68
67
  and issue tracking systems that are managed by, or on behalf of, the
69
68
  Licensor for the purpose of discussing and improving the Work, but
70
- excluding communication that is clearly marked or otherwise designated
71
- in writing by the copyright owner as "Not a Contribution".
69
+ excluding communication that is conspicuously marked or otherwise
70
+ designated in writing by the copyright owner as "Not a Contribution."
72
71
 
73
72
  "Contributor" shall mean Licensor and any individual or Legal Entity
74
73
  on behalf of whom a Contribution has been received by Licensor and
@@ -210,7 +209,7 @@ License: Apache License
210
209
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
211
210
  See the License for the specific language governing permissions and
212
211
  limitations under the License.
213
- License-File: LICENSE.md
212
+ License-File: LICENSE
214
213
  Keywords: agile,backlog,beartype,ceremonies,cli,contract-driven-development,contracts,crosshair,devops,existing-code,icontract,kanban,legacy,modernization,policy-as-code,property-based-testing,safe,scrum,specfact,specs,tdd,validation
215
214
  Classifier: Development Status :: 4 - Beta
216
215
  Classifier: Intended Audience :: Developers
@@ -286,7 +285,7 @@ Description-Content-Type: text/markdown
286
285
 
287
286
  [![PyPI version](https://img.shields.io/pypi/v/specfact-cli.svg?color=22c55e)](https://pypi.org/project/specfact-cli/)
288
287
  [![Python versions](https://img.shields.io/pypi/pyversions/specfact-cli.svg)](https://pypi.org/project/specfact-cli/)
289
- [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE.md)
288
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
290
289
  [![Status](https://img.shields.io/badge/status-beta-F59E0B.svg)](https://github.com/nold-ai/specfact-cli)
291
290
 
292
291
  <div align="center">
@@ -442,6 +441,7 @@ Contract-first module architecture highlights:
442
441
  - Registration tracks protocol operation coverage and schema compatibility metadata.
443
442
  - Bridge registry support allows module manifests to declare `service_bridges` converters (for example ADO/Jira/Linear/GitHub) loaded at lifecycle startup without direct core-to-module imports.
444
443
  - Protocol reporting classifies modules from effective runtime interfaces with a single aggregate summary (`Full/Partial/Legacy`).
444
+ - Module manifests support publisher and integrity metadata (arch-06) with optional checksum and signature verification at registration time.
445
445
 
446
446
  Why this matters:
447
447
 
@@ -521,7 +521,7 @@ hatch run contract-test-full
521
521
 
522
522
  **Apache License 2.0** - Open source and enterprise-friendly.
523
523
 
524
- [Full license](LICENSE.md)
524
+ [Full license](LICENSE)
525
525
 
526
526
  ---
527
527
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  [![PyPI version](https://img.shields.io/pypi/v/specfact-cli.svg?color=22c55e)](https://pypi.org/project/specfact-cli/)
10
10
  [![Python versions](https://img.shields.io/pypi/pyversions/specfact-cli.svg)](https://pypi.org/project/specfact-cli/)
11
- [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE.md)
11
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
12
12
  [![Status](https://img.shields.io/badge/status-beta-F59E0B.svg)](https://github.com/nold-ai/specfact-cli)
13
13
 
14
14
  <div align="center">
@@ -164,6 +164,7 @@ Contract-first module architecture highlights:
164
164
  - Registration tracks protocol operation coverage and schema compatibility metadata.
165
165
  - Bridge registry support allows module manifests to declare `service_bridges` converters (for example ADO/Jira/Linear/GitHub) loaded at lifecycle startup without direct core-to-module imports.
166
166
  - Protocol reporting classifies modules from effective runtime interfaces with a single aggregate summary (`Full/Partial/Legacy`).
167
+ - Module manifests support publisher and integrity metadata (arch-06) with optional checksum and signature verification at registration time.
167
168
 
168
169
  Why this matters:
169
170
 
@@ -243,7 +244,7 @@ hatch run contract-test-full
243
244
 
244
245
  **Apache License 2.0** - Open source and enterprise-friendly.
245
246
 
246
- [Full license](LICENSE.md)
247
+ [Full license](LICENSE)
247
248
 
248
249
  ---
249
250
 
@@ -4,11 +4,11 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "specfact-cli"
7
- version = "0.31.0"
7
+ version = "0.32.0"
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"
11
- license = { file = "LICENSE.md" } # Apache License 2.0
11
+ license = { file = "LICENSE" } # Apache License 2.0
12
12
  authors = [
13
13
  {name = "NOLD AI (Owner: Dominikus Nold)", email = "hello@noldai.com"}
14
14
  ]
@@ -385,7 +385,7 @@ include = [
385
385
  "/src",
386
386
  "/resources",
387
387
  "/README.md",
388
- "/LICENSE.md",
388
+ "/LICENSE",
389
389
  "/pyproject.toml",
390
390
  ]
391
391
  # Exclude development files, tests, docs, tools, etc.
@@ -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.31.0"
6
+ __version__ = "0.32.0"
@@ -8,6 +8,6 @@ This package provides command-line tools for:
8
8
  - Supporting agile ceremonies and team workflows
9
9
  """
10
10
 
11
- __version__ = "0.31.0"
11
+ __version__ = "0.32.0"
12
12
 
13
13
  __all__ = ["__version__"]
@@ -126,11 +126,13 @@ class CodeAnalyzer:
126
126
  @beartype
127
127
  @ensure(lambda result: isinstance(result, PlanBundle), "Must return PlanBundle")
128
128
  @ensure(
129
- lambda result: isinstance(result, PlanBundle)
130
- and hasattr(result, "version")
131
- and hasattr(result, "features")
132
- and result.version == get_current_schema_version() # type: ignore[reportUnknownMemberType]
133
- and len(result.features) >= 0, # type: ignore[reportUnknownMemberType]
129
+ lambda result: (
130
+ isinstance(result, PlanBundle)
131
+ and hasattr(result, "version")
132
+ and hasattr(result, "features")
133
+ and result.version == get_current_schema_version() # type: ignore[reportUnknownMemberType]
134
+ and len(result.features) >= 0
135
+ ), # type: ignore[reportUnknownMemberType]
134
136
  "Plan bundle must be valid",
135
137
  )
136
138
  def analyze(self) -> PlanBundle:
@@ -48,13 +48,17 @@ class FeatureDelta(BaseModel):
48
48
 
49
49
  @model_validator(mode="after")
50
50
  @require(
51
- lambda self: self.change_type == ChangeType.ADDED
52
- or (self.change_type in (ChangeType.MODIFIED, ChangeType.REMOVED) and self.original_feature is not None),
51
+ lambda self: (
52
+ self.change_type == ChangeType.ADDED
53
+ or (self.change_type in (ChangeType.MODIFIED, ChangeType.REMOVED) and self.original_feature is not None)
54
+ ),
53
55
  "MODIFIED/REMOVED changes must have original_feature",
54
56
  )
55
57
  @require(
56
- lambda self: self.change_type == ChangeType.REMOVED
57
- or (self.change_type in (ChangeType.ADDED, ChangeType.MODIFIED) and self.proposed_feature is not None),
58
+ lambda self: (
59
+ self.change_type == ChangeType.REMOVED
60
+ or (self.change_type in (ChangeType.ADDED, ChangeType.MODIFIED) and self.proposed_feature is not None)
61
+ ),
58
62
  "ADDED/MODIFIED changes must have proposed_feature",
59
63
  )
60
64
  @ensure(lambda result: isinstance(result, FeatureDelta), "Must return FeatureDelta")
@@ -0,0 +1,158 @@
1
+ """Module package metadata models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from beartype import beartype
8
+ from icontract import ensure
9
+ from pydantic import BaseModel, Field, model_validator
10
+
11
+
12
+ CHECKSUM_ALGO_RE = re.compile(r"^sha256:[a-fA-F0-9]{64}$|^sha384:[a-fA-F0-9]{96}$|^sha512:[a-fA-F0-9]{128}$")
13
+ CONVERTER_CLASS_PATH_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)+$")
14
+ MODULE_NAME_RE = re.compile(r"^[a-z][a-z0-9_-]*$")
15
+ FIELD_NAME_RE = re.compile(r"^[a-z][a-z0-9_]*$")
16
+
17
+
18
+ @beartype
19
+ class SchemaExtension(BaseModel):
20
+ """Declarative schema extension for Feature or ProjectBundle (arch-07)."""
21
+
22
+ target: str = Field(..., description="Target model: Feature or ProjectBundle")
23
+ field: str = Field(..., description="Field name (snake_case)")
24
+ type_hint: str = Field(..., description="Type hint for documentation (e.g. str, int)")
25
+ description: str = Field(default="", description="Human-readable description")
26
+
27
+ @model_validator(mode="after")
28
+ def _validate_target_and_field(self) -> SchemaExtension:
29
+ if self.target not in ("Feature", "ProjectBundle"):
30
+ raise ValueError("target must be Feature or ProjectBundle")
31
+ if not FIELD_NAME_RE.match(self.field):
32
+ raise ValueError("field must match [a-z][a-z0-9_]*")
33
+ return self
34
+
35
+
36
+ @beartype
37
+ class PublisherInfo(BaseModel):
38
+ """Publisher identity from module manifest (arch-06)."""
39
+
40
+ name: str = Field(..., description="Publisher display name")
41
+ email: str = Field(..., description="Publisher contact email")
42
+ attributes: dict[str, str] = Field(default_factory=dict, description="Optional publisher attributes")
43
+
44
+ @model_validator(mode="after")
45
+ def _validate_non_empty(self) -> PublisherInfo:
46
+ if not self.name.strip():
47
+ raise ValueError("Publisher name must not be empty")
48
+ if not self.email.strip():
49
+ raise ValueError("Publisher email must not be empty")
50
+ return self
51
+
52
+
53
+ @beartype
54
+ class IntegrityInfo(BaseModel):
55
+ """Integrity metadata for module artifact verification (arch-06)."""
56
+
57
+ checksum: str = Field(..., description="Checksum in algo:hex format (e.g. sha256:...)")
58
+ signature: str | None = Field(default=None, description="Optional detached signature (base64)")
59
+
60
+ @model_validator(mode="after")
61
+ def _validate_checksum_format(self) -> IntegrityInfo:
62
+ """Validation SHALL ensure checksum format correctness."""
63
+ if not CHECKSUM_ALGO_RE.match(self.checksum):
64
+ raise ValueError(
65
+ "integrity.checksum must be algo:hex (e.g. sha256:<64 hex chars>, sha384:<96>, sha512:<128>)"
66
+ )
67
+ return self
68
+
69
+
70
+ @beartype
71
+ class ServiceBridgeMetadata(BaseModel):
72
+ """Service bridge declaration from module package manifest."""
73
+
74
+ id: str = Field(..., description="Bridge identifier (for example: ado, jira, linear, github).")
75
+ converter_class: str = Field(..., description="Fully-qualified converter class path.")
76
+ description: str | None = Field(default=None, description="Optional bridge description.")
77
+
78
+ @model_validator(mode="after")
79
+ def _validate_bridge_metadata(self) -> ServiceBridgeMetadata:
80
+ """Validate required bridge fields."""
81
+ if not self.id.strip():
82
+ raise ValueError("service_bridges.id must not be empty.")
83
+ if not self.converter_class.strip():
84
+ raise ValueError("service_bridges.converter_class must not be empty.")
85
+ if not CONVERTER_CLASS_PATH_RE.match(self.converter_class):
86
+ raise ValueError(
87
+ "service_bridges.converter_class must be a dotted path (for example: package.module.ClassName)."
88
+ )
89
+ return self
90
+
91
+
92
+ @beartype
93
+ class VersionedModuleDependency(BaseModel):
94
+ """Versioned module dependency entry (arch-06)."""
95
+
96
+ name: str = Field(..., description="Module package id")
97
+ version_specifier: str | None = Field(default=None, description="PEP 440 version specifier")
98
+
99
+
100
+ @beartype
101
+ class VersionedPipDependency(BaseModel):
102
+ """Versioned pip dependency entry (arch-06)."""
103
+
104
+ name: str = Field(..., description="PyPI package name")
105
+ version_specifier: str | None = Field(default=None, description="PEP 440 version specifier")
106
+
107
+
108
+ @beartype
109
+ class ModulePackageMetadata(BaseModel):
110
+ """Schema for a module package manifest."""
111
+
112
+ name: str = Field(..., description="Package identifier (e.g. backlog_refine)")
113
+ version: str = Field(default="0.1.0", description="Package version")
114
+ commands: list[str] = Field(default_factory=list, description="Command names this package provides")
115
+ command_help: dict[str, str] | None = Field(
116
+ default=None,
117
+ description="Optional command name -> help text for root help.",
118
+ )
119
+ pip_dependencies: list[str] = Field(default_factory=list, description="Optional pip dependencies")
120
+ module_dependencies: list[str] = Field(default_factory=list, description="Optional other package ids")
121
+ core_compatibility: str | None = Field(
122
+ default=None,
123
+ description="CLI core version compatibility (PEP 440 specifier, e.g. '>=0.28.0,<1.0.0').",
124
+ )
125
+ tier: str = Field(default="community", description="Tier: community or enterprise")
126
+ addon_id: str | None = Field(default=None, description="Optional addon identifier")
127
+ schema_version: str | None = Field(
128
+ default=None,
129
+ description="Compatible ProjectBundle schema version. None means current schema.",
130
+ )
131
+ protocol_operations: list[str] = Field(
132
+ default_factory=list,
133
+ description="Detected ModuleIOContract operations: import, export, sync, validate.",
134
+ )
135
+ publisher: PublisherInfo | None = Field(default=None, description="Publisher identity (arch-06)")
136
+ integrity: IntegrityInfo | None = Field(default=None, description="Integrity metadata (arch-06)")
137
+ module_dependencies_versioned: list[VersionedModuleDependency] = Field(
138
+ default_factory=list,
139
+ description="Versioned module dependency declarations (arch-06)",
140
+ )
141
+ pip_dependencies_versioned: list[VersionedPipDependency] = Field(
142
+ default_factory=list,
143
+ description="Versioned pip dependency declarations (arch-06)",
144
+ )
145
+ service_bridges: list[ServiceBridgeMetadata] = Field(
146
+ default_factory=list,
147
+ description="Optional bridge declarations for converter registration.",
148
+ )
149
+ schema_extensions: list[SchemaExtension] = Field(
150
+ default_factory=list,
151
+ description="Declarative schema extensions for Feature/ProjectBundle (arch-07).",
152
+ )
153
+
154
+ @beartype
155
+ @ensure(lambda result: isinstance(result, list), "Validated bridges must be returned as a list")
156
+ def validate_service_bridges(self) -> list[ServiceBridgeMetadata]:
157
+ """Return validated bridge declarations for lifecycle registration."""
158
+ return list(self.service_bridges)
@@ -7,13 +7,20 @@ and stories following the CLI-First specification.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import re
10
11
  from typing import Any
11
12
 
13
+ from beartype import beartype
14
+ from icontract import ensure, require
12
15
  from pydantic import BaseModel, Field, model_validator
13
16
 
14
17
  from specfact_cli.models.source_tracking import SourceTracking
15
18
 
16
19
 
20
+ MODULE_NAME_RE = re.compile(r"^[a-z][a-z0-9_-]*$")
21
+ FIELD_NAME_RE = re.compile(r"^[a-z][a-z0-9_]*$")
22
+
23
+
17
24
  class Story(BaseModel):
18
25
  """User story model following Scrum/Agile practices."""
19
26
 
@@ -121,6 +128,29 @@ class Feature(BaseModel):
121
128
  estimated_story_points: int | None = Field(
122
129
  default=None, description="Total estimated story points (sum of all stories, computed automatically)"
123
130
  )
131
+ extensions: dict[str, Any] = Field(
132
+ default_factory=dict,
133
+ description="Module-scoped extension data (namespace-prefixed keys, e.g. backlog.ado_work_item_id)",
134
+ )
135
+
136
+ @beartype
137
+ @require(lambda module_name: bool(MODULE_NAME_RE.match(module_name)), "Invalid module name format")
138
+ @require(lambda field: bool(FIELD_NAME_RE.match(field)), "Invalid field name format")
139
+ def get_extension(self, module_name: str, field: str, default: Any = None) -> Any:
140
+ """Return extension value at module.field or default."""
141
+ if "." in module_name:
142
+ raise ValueError("Invalid module name format")
143
+ return self.extensions.get(f"{module_name}.{field}", default)
144
+
145
+ @beartype
146
+ @require(lambda module_name: bool(MODULE_NAME_RE.match(module_name)), "Invalid module name format")
147
+ @require(lambda field: bool(FIELD_NAME_RE.match(field)), "Invalid field name format")
148
+ @ensure(lambda self, module_name, field: f"{module_name}.{field}" in self.extensions)
149
+ def set_extension(self, module_name: str, field: str, value: Any) -> None:
150
+ """Store extension value at module.field."""
151
+ if "." in module_name:
152
+ raise ValueError("Invalid module name format")
153
+ self.extensions[f"{module_name}.{field}"] = value
124
154
 
125
155
 
126
156
  class Release(BaseModel):
@@ -10,6 +10,7 @@ support dual versioning (schema + project).
10
10
  from __future__ import annotations
11
11
 
12
12
  import os
13
+ import re
13
14
  from collections.abc import Callable
14
15
  from concurrent.futures import ThreadPoolExecutor, as_completed
15
16
  from datetime import UTC, datetime
@@ -33,6 +34,10 @@ from specfact_cli.models.plan import (
33
34
  )
34
35
 
35
36
 
37
+ _EXT_MODULE_RE = re.compile(r"^[a-z][a-z0-9_-]*$")
38
+ _EXT_FIELD_RE = re.compile(r"^[a-z][a-z0-9_]*$")
39
+
40
+
36
41
  class BundleFormat(StrEnum):
37
42
  """Bundle format types."""
38
43
 
@@ -217,6 +222,29 @@ class ProjectBundle(BaseModel):
217
222
  default=None,
218
223
  description="Change tracking (tool-agnostic capability, used by OpenSpec and potentially others) (v1.1+)",
219
224
  )
225
+ extensions: dict[str, Any] = Field(
226
+ default_factory=dict,
227
+ description="Module-scoped extension data (namespace-prefixed keys, e.g. sync.last_sync_timestamp)",
228
+ )
229
+
230
+ @beartype
231
+ @require(lambda self, module_name: bool(_EXT_MODULE_RE.match(module_name)), "Invalid module name format")
232
+ @require(lambda self, field: bool(_EXT_FIELD_RE.match(field)), "Invalid field name format")
233
+ def get_extension(self, module_name: str, field: str, default: Any = None) -> Any:
234
+ """Return extension value at module.field or default."""
235
+ if "." in module_name:
236
+ raise ValueError("Invalid module name format")
237
+ return self.extensions.get(f"{module_name}.{field}", default)
238
+
239
+ @beartype
240
+ @require(lambda self, module_name: bool(_EXT_MODULE_RE.match(module_name)), "Invalid module name format")
241
+ @require(lambda self, field: bool(_EXT_FIELD_RE.match(field)), "Invalid field name format")
242
+ @ensure(lambda self, module_name, field: f"{module_name}.{field}" in self.extensions)
243
+ def set_extension(self, module_name: str, field: str, value: Any) -> None:
244
+ """Store extension value at module.field."""
245
+ if "." in module_name:
246
+ raise ValueError("Invalid module name format")
247
+ self.extensions[f"{module_name}.{field}"] = value
220
248
 
221
249
  @model_validator(mode="before")
222
250
  @classmethod
@@ -3626,10 +3626,9 @@ def refine(
3626
3626
 
3627
3627
  @app.command("map-fields")
3628
3628
  @require(
3629
- lambda ado_org, ado_project: isinstance(ado_org, str)
3630
- and len(ado_org) > 0
3631
- and isinstance(ado_project, str)
3632
- and len(ado_project) > 0,
3629
+ lambda ado_org, ado_project: (
3630
+ isinstance(ado_org, str) and len(ado_org) > 0 and isinstance(ado_project, str) and len(ado_project) > 0
3631
+ ),
3633
3632
  "ADO org and project must be non-empty strings",
3634
3633
  )
3635
3634
  @beartype
@@ -3025,9 +3025,9 @@ def _load_and_validate_plan(plan: Path) -> tuple[bool, PlanBundle | None]:
3025
3025
 
3026
3026
  @beartype
3027
3027
  @require(
3028
- lambda bundle, bundle_dir, auto_enrich: isinstance(bundle, PlanBundle)
3029
- and bundle_dir is not None
3030
- and isinstance(bundle_dir, Path),
3028
+ lambda bundle, bundle_dir, auto_enrich: (
3029
+ isinstance(bundle, PlanBundle) and bundle_dir is not None and isinstance(bundle_dir, Path)
3030
+ ),
3031
3031
  "Bundle must be PlanBundle and bundle_dir must be non-None Path",
3032
3032
  )
3033
3033
  @ensure(lambda result: result is None, "Must return None")
@@ -1084,8 +1084,9 @@ def _sync_tool_to_specfact(
1084
1084
  )
1085
1085
  @require(lambda bidirectional: isinstance(bidirectional, bool), "Bidirectional must be bool")
1086
1086
  @require(
1087
- lambda mode: mode is None
1088
- or mode in ("read-only", "export-only", "import-annotation", "bidirectional", "unidirectional"),
1087
+ lambda mode: (
1088
+ mode is None or mode in ("read-only", "export-only", "import-annotation", "bidirectional", "unidirectional")
1089
+ ),
1089
1090
  "Mode must be valid sync mode",
1090
1091
  )
1091
1092
  @require(lambda overwrite: isinstance(overwrite, bool), "Overwrite must be bool")
@@ -0,0 +1,124 @@
1
+ """
2
+ Checksum and optional signature verification for module artifacts (arch-06).
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import base64
8
+ import hashlib
9
+ from pathlib import Path
10
+
11
+ from beartype import beartype
12
+ from icontract import require
13
+
14
+
15
+ _ArtifactInput = bytes | Path
16
+
17
+
18
+ def _algo_and_hex(expected_checksum: str) -> tuple[str, str]:
19
+ """Parse 'algo:hex' format. Raises ValueError if invalid."""
20
+ if ":" not in expected_checksum or not expected_checksum.strip():
21
+ raise ValueError("Expected checksum must be in algo:hex format (e.g. sha256:<64 hex chars>)")
22
+ algo, hex_part = expected_checksum.strip().split(":", 1)
23
+ algo = algo.lower()
24
+ if algo not in ("sha256", "sha384", "sha512"):
25
+ raise ValueError("Supported checksum algorithms: sha256, sha384, sha512")
26
+ if not hex_part or not all(c in "0123456789abcdefABCDEF" for c in hex_part):
27
+ raise ValueError("Checksum hex part must contain only hex digits")
28
+ expected_len = {"sha256": 64, "sha384": 96, "sha512": 128}
29
+ if len(hex_part) != expected_len[algo]:
30
+ raise ValueError(f"Checksum hex length for {algo} must be {expected_len[algo]}, got {len(hex_part)}")
31
+ return algo, hex_part
32
+
33
+
34
+ @beartype
35
+ @require(lambda expected_checksum: expected_checksum.strip() != "", "Expected checksum must not be empty")
36
+ def verify_checksum(artifact: _ArtifactInput, expected_checksum: str) -> bool:
37
+ """
38
+ Verify artifact checksum against expected algo:hex value.
39
+
40
+ Args:
41
+ artifact: Raw bytes or path to file.
42
+ expected_checksum: Expected value in format sha256:<64 hex>, sha384:<96>, or sha512:<128>.
43
+
44
+ Returns:
45
+ True if the artifact's checksum matches.
46
+
47
+ Raises:
48
+ ValueError: If format is invalid or checksum does not match.
49
+ """
50
+ algo, expected_hex = _algo_and_hex(expected_checksum)
51
+ data = artifact.read_bytes() if isinstance(artifact, Path) else artifact
52
+ hasher = hashlib.new(algo)
53
+ hasher.update(data)
54
+ actual_hex = hasher.hexdigest()
55
+ if actual_hex.lower() != expected_hex.lower():
56
+ raise ValueError(f"Checksum mismatch: computed {algo}:{actual_hex[:16]}... does not match expected")
57
+ return True
58
+
59
+
60
+ def _verify_signature_impl(artifact: bytes, signature_b64: str, public_key_pem: str) -> bool:
61
+ """
62
+ Verify detached signature over artifact using public key.
63
+ Uses cryptography if available; otherwise raises.
64
+ """
65
+ try:
66
+ from cryptography.exceptions import InvalidSignature
67
+ from cryptography.hazmat.primitives import hashes, serialization
68
+ from cryptography.hazmat.primitives.asymmetric import ed25519, padding, rsa
69
+ except ImportError as e:
70
+ raise ValueError(
71
+ "Signature verification requires the 'cryptography' package. Install with: pip install cryptography"
72
+ ) from e
73
+ if not public_key_pem or not public_key_pem.strip():
74
+ raise ValueError("Public key PEM must not be empty")
75
+ try:
76
+ key = serialization.load_pem_public_key(public_key_pem.encode())
77
+ except Exception as e:
78
+ raise ValueError(f"Invalid public key PEM: {e}") from e
79
+ try:
80
+ sig_bytes = base64.b64decode(signature_b64, validate=True)
81
+ except Exception as e:
82
+ raise ValueError(f"Invalid base64 signature: {e}") from e
83
+ if isinstance(key, rsa.RSAPublicKey):
84
+ try:
85
+ key.verify(sig_bytes, artifact, padding.PKCS1v15(), hashes.SHA256())
86
+ return True
87
+ except InvalidSignature:
88
+ return False
89
+ if isinstance(key, ed25519.Ed25519PublicKey):
90
+ try:
91
+ key.verify(sig_bytes, artifact)
92
+ return True
93
+ except InvalidSignature:
94
+ return False
95
+ raise ValueError("Unsupported key type for signature verification (RSA or Ed25519 only)")
96
+
97
+
98
+ @beartype
99
+ def verify_signature(
100
+ artifact: _ArtifactInput,
101
+ signature_b64: str,
102
+ public_key_pem: str,
103
+ ) -> bool:
104
+ """
105
+ Verify detached signature over artifact.
106
+
107
+ Args:
108
+ artifact: Raw bytes or path to file.
109
+ signature_b64: Base64-encoded signature.
110
+ public_key_pem: PEM-encoded public key.
111
+
112
+ Returns:
113
+ True if signature is valid. False if no signature to verify (empty).
114
+ Raises ValueError on missing key, invalid format, or verification failure.
115
+ """
116
+ if not signature_b64 or not signature_b64.strip():
117
+ return False
118
+ artifact_bytes = artifact.read_bytes() if isinstance(artifact, Path) else artifact
119
+ if not public_key_pem or not public_key_pem.strip():
120
+ raise ValueError("Public key PEM is required for signature verification")
121
+ ok = _verify_signature_impl(artifact_bytes, signature_b64.strip(), public_key_pem.strip())
122
+ if not ok:
123
+ raise ValueError("Signature verification failed: signature does not match artifact or key")
124
+ return True