specfact-cli 0.46.0__tar.gz → 0.46.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 (277) hide show
  1. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/PKG-INFO +23 -11
  2. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/README.md +16 -4
  3. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/pyproject.toml +43 -28
  4. specfact_cli-0.46.4/resources/bundled-module-registry/index.json +20 -0
  5. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/__init__.py +1 -1
  6. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/__init__.py +1 -1
  7. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/adapters/ado.py +9 -9
  8. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/adapters/github.py +22 -3
  9. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/adapters/speckit.py +14 -11
  10. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/analyzers/code_analyzer.py +47 -22
  11. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/analyzers/graph_analyzer.py +56 -65
  12. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/common/logger_setup.py +2 -1
  13. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/generators/persona_exporter.py +2 -2
  14. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/generators/test_to_openapi.py +13 -7
  15. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modules/init/module-package.yaml +3 -3
  16. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modules/init/src/first_run_selection.py +1 -1
  17. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modules/module_registry/module-package.yaml +3 -3
  18. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modules/module_registry/src/commands.py +1 -1
  19. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/module_installer.py +49 -5
  20. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/bridge_watch.py +6 -2
  21. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/optional_deps.py +12 -15
  22. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/project_artifact_write.py +7 -6
  23. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/source_scanner.py +0 -4
  24. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/cli_first_validator.py +20 -12
  25. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/crosshair_runner.py +5 -1
  26. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/crosshair_summary.py +2 -2
  27. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +11 -6
  28. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/.gitignore +0 -0
  29. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/LICENSE +0 -0
  30. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/keys/README.md +0 -0
  31. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/keys/module-signing-public.pem +0 -0
  32. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/mappings/node-async.yaml +0 -0
  33. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/mappings/python-async.yaml +0 -0
  34. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/mappings/speckit-default.yaml +0 -0
  35. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/schemas/deviation.schema.json +0 -0
  36. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/schemas/plan.schema.json +0 -0
  37. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/schemas/protocol.schema.json +0 -0
  38. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/templates/github-action.yml.j2 +0 -0
  39. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/templates/persona/architect.md.j2 +0 -0
  40. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/templates/persona/developer.md.j2 +0 -0
  41. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/templates/persona/product-owner.md.j2 +0 -0
  42. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/templates/plan.bundle.yaml.j2 +0 -0
  43. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/templates/policies/kanban.yaml +0 -0
  44. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/templates/policies/mixed.yaml +0 -0
  45. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/templates/policies/safe.yaml +0 -0
  46. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/templates/policies/scrum.yaml +0 -0
  47. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/templates/pr-template.md.j2 +0 -0
  48. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/templates/protocol.yaml.j2 +0 -0
  49. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/resources/templates/telemetry.yaml.example +0 -0
  50. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/__main__.py +0 -0
  51. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/adapters/__init__.py +0 -0
  52. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/adapters/backlog_base.py +0 -0
  53. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/adapters/base.py +0 -0
  54. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/adapters/openspec.py +0 -0
  55. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/adapters/openspec_parser.py +0 -0
  56. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/adapters/registry.py +0 -0
  57. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/agents/__init__.py +0 -0
  58. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/agents/analyze_agent.py +0 -0
  59. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/agents/base.py +0 -0
  60. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/agents/plan_agent.py +0 -0
  61. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/agents/registry.py +0 -0
  62. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/agents/sync_agent.py +0 -0
  63. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/analyzers/__init__.py +0 -0
  64. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
  65. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
  66. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
  67. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
  68. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
  69. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
  70. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
  71. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/backlog/__init__.py +0 -0
  72. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
  73. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/backlog/adapters/base.py +0 -0
  74. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/backlog/converter.py +0 -0
  75. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/backlog/filters.py +0 -0
  76. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
  77. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
  78. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/backlog/mappers/base.py +0 -0
  79. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
  80. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
  81. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/cli.py +0 -0
  82. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/__init__.py +0 -0
  83. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/_bundle_shim.py +0 -0
  84. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/analyze.py +0 -0
  85. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/contract_cmd.py +0 -0
  86. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/drift.py +0 -0
  87. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/enforce.py +0 -0
  88. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/generate.py +0 -0
  89. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/import_cmd.py +0 -0
  90. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/init.py +0 -0
  91. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/migrate.py +0 -0
  92. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/plan.py +0 -0
  93. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/project_cmd.py +0 -0
  94. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/repro.py +0 -0
  95. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/sdd.py +0 -0
  96. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/spec.py +0 -0
  97. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/sync.py +0 -0
  98. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/update.py +0 -0
  99. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/commands/validate.py +0 -0
  100. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/common/__init__.py +0 -0
  101. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/common/bundle_factory.py +0 -0
  102. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/common/logging_utils.py +0 -0
  103. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/common/text_utils.py +0 -0
  104. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/common/utils.py +0 -0
  105. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/comparators/__init__.py +0 -0
  106. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/comparators/plan_comparator.py +0 -0
  107. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/contracts/__init__.py +0 -0
  108. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/contracts/crosshair_props.py +0 -0
  109. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/contracts/module_interface.py +0 -0
  110. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
  111. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
  112. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/generators/__init__.py +0 -0
  113. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/generators/contract_generator.py +0 -0
  114. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/generators/openapi_extractor.py +0 -0
  115. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/generators/plan_generator.py +0 -0
  116. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/generators/protocol_generator.py +0 -0
  117. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/generators/report_generator.py +0 -0
  118. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/generators/task_generator.py +0 -0
  119. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/generators/workflow_generator.py +0 -0
  120. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/groups/__init__.py +0 -0
  121. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/groups/codebase_group.py +0 -0
  122. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/groups/govern_group.py +0 -0
  123. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/groups/member_group.py +0 -0
  124. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/groups/project_group.py +0 -0
  125. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/groups/spec_group.py +0 -0
  126. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/importers/__init__.py +0 -0
  127. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/importers/speckit_converter.py +0 -0
  128. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/importers/speckit_scanner.py +0 -0
  129. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/integrations/__init__.py +0 -0
  130. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/integrations/specmatic.py +0 -0
  131. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/merge/__init__.py +0 -0
  132. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/merge/resolver.py +0 -0
  133. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/migrations/__init__.py +0 -0
  134. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/migrations/plan_migrator.py +0 -0
  135. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/__init__.py +0 -0
  136. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/backlog_item.py +0 -0
  137. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/bridge.py +0 -0
  138. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/capabilities.py +0 -0
  139. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/change.py +0 -0
  140. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/contract.py +0 -0
  141. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/deviation.py +0 -0
  142. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/dor_config.py +0 -0
  143. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/enforcement.py +0 -0
  144. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/module_package.py +0 -0
  145. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/persona_template.py +0 -0
  146. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/plan.py +0 -0
  147. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/project.py +0 -0
  148. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/protocol.py +0 -0
  149. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/quality.py +0 -0
  150. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/sdd.py +0 -0
  151. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/source_tracking.py +0 -0
  152. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/task.py +0 -0
  153. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/models/validation.py +0 -0
  154. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modes/__init__.py +0 -0
  155. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modes/detector.py +0 -0
  156. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modes/router.py +0 -0
  157. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modules/__init__.py +0 -0
  158. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modules/_bundle_import.py +0 -0
  159. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modules/init/src/__init__.py +0 -0
  160. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modules/init/src/app.py +0 -0
  161. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modules/init/src/commands.py +0 -0
  162. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modules/module_io_shim.py +0 -0
  163. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modules/module_registry/src/__init__.py +0 -0
  164. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modules/module_registry/src/app.py +0 -0
  165. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modules/upgrade/module-package.yaml +0 -0
  166. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modules/upgrade/src/__init__.py +0 -0
  167. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modules/upgrade/src/app.py +0 -0
  168. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/modules/upgrade/src/commands.py +0 -0
  169. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/parsers/__init__.py +0 -0
  170. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/parsers/persona_importer.py +0 -0
  171. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/__init__.py +0 -0
  172. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/alias_manager.py +0 -0
  173. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/bootstrap.py +0 -0
  174. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/bridge_registry.py +0 -0
  175. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/crypto_validator.py +0 -0
  176. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/custom_registries.py +0 -0
  177. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/dependency_resolver.py +0 -0
  178. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/extension_registry.py +0 -0
  179. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/help_cache.py +0 -0
  180. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/marketplace_client.py +0 -0
  181. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/metadata.py +0 -0
  182. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/module_discovery.py +0 -0
  183. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/module_grouping.py +0 -0
  184. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/module_lifecycle.py +0 -0
  185. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/module_packages.py +0 -0
  186. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/module_security.py +0 -0
  187. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/module_state.py +0 -0
  188. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/registry/registry.py +0 -0
  189. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/resources/semgrep/async.yml +0 -0
  190. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
  191. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
  192. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/runtime.py +0 -0
  193. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/__init__.py +0 -0
  194. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/bridge_probe.py +0 -0
  195. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/bridge_sync.py +0 -0
  196. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/bridge_sync_openspec_md_parse.py +0 -0
  197. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/bridge_sync_requirement_from_proposal.py +0 -0
  198. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/bridge_sync_requirement_helpers.py +0 -0
  199. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/bridge_sync_tasks_from_proposal.py +0 -0
  200. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/bridge_sync_what_changes_format.py +0 -0
  201. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/bridge_sync_write_openspec_from_proposal.py +0 -0
  202. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/change_detector.py +0 -0
  203. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/code_to_spec.py +0 -0
  204. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/drift_detector.py +0 -0
  205. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/repository_sync.py +0 -0
  206. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/spec_to_code.py +0 -0
  207. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/spec_to_tests.py +0 -0
  208. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/watcher.py +0 -0
  209. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
  210. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/telemetry.py +0 -0
  211. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/templates/__init__.py +0 -0
  212. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
  213. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
  214. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
  215. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
  216. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
  217. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
  218. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
  219. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/templates/registry.py +0 -0
  220. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/templates/specification_templates.py +0 -0
  221. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/__init__.py +0 -0
  222. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
  223. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/auth_tokens.py +0 -0
  224. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/bundle_converters.py +0 -0
  225. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/bundle_loader.py +0 -0
  226. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/code_change_detector.py +0 -0
  227. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/console.py +0 -0
  228. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/content_sanitizer.py +0 -0
  229. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/context_detection.py +0 -0
  230. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/contract_predicates.py +0 -0
  231. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/enrichment_context.py +0 -0
  232. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/enrichment_parser.py +0 -0
  233. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/env_manager.py +0 -0
  234. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/feature_keys.py +0 -0
  235. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/git.py +0 -0
  236. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/github_annotations.py +0 -0
  237. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/icontract_helpers.py +0 -0
  238. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/ide_setup.py +0 -0
  239. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/incremental_check.py +0 -0
  240. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/metadata.py +0 -0
  241. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/performance.py +0 -0
  242. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/persona_ownership.py +0 -0
  243. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/progress.py +0 -0
  244. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
  245. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/prompts.py +0 -0
  246. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/sdd_discovery.py +0 -0
  247. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/startup_checks.py +0 -0
  248. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/structure.py +0 -0
  249. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/structured_io.py +0 -0
  250. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/suggestions.py +0 -0
  251. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/terminal.py +0 -0
  252. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/utils/yaml_utils.py +0 -0
  253. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validation/__init__.py +0 -0
  254. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validation/command_audit.py +0 -0
  255. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/__init__.py +0 -0
  256. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/agile_validation.py +0 -0
  257. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
  258. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/contract_validator.py +0 -0
  259. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/fsm.py +0 -0
  260. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/repro_checker.py +0 -0
  261. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/schema.py +0 -0
  262. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
  263. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
  264. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
  265. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
  266. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
  267. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
  268. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
  269. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
  270. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
  271. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
  272. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/models.py +0 -0
  273. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
  274. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
  275. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
  276. {specfact_cli-0.46.0 → specfact_cli-0.46.4}/src/specfact_cli/versioning/__init__.py +0 -0
  277. {specfact_cli-0.46.0 → specfact_cli-0.46.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.46.0
3
+ Version: 0.46.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
@@ -224,12 +224,12 @@ Classifier: Topic :: Software Development :: Testing
224
224
  Requires-Python: >=3.11
225
225
  Requires-Dist: azure-identity>=1.17.1
226
226
  Requires-Dist: beartype>=0.22.4
227
+ Requires-Dist: commentjson>=0.9.0
227
228
  Requires-Dist: cryptography>=43.0.0
228
229
  Requires-Dist: gitpython>=3.1.45
229
230
  Requires-Dist: graphviz>=0.20.1
230
231
  Requires-Dist: icontract>=2.7.1
231
232
  Requires-Dist: jinja2>=3.1.6
232
- Requires-Dist: json5>=0.9.28
233
233
  Requires-Dist: jsonschema>=4.23.0
234
234
  Requires-Dist: networkx>=3.4.2
235
235
  Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27.0
@@ -248,16 +248,18 @@ Provides-Extra: contracts
248
248
  Requires-Dist: crosshair-tool>=0.0.97; extra == 'contracts'
249
249
  Requires-Dist: hypothesis>=6.142.4; extra == 'contracts'
250
250
  Provides-Extra: dev
251
+ Requires-Dist: bandit>=1.7.0; extra == 'dev'
251
252
  Requires-Dist: basedpyright>=1.32.1; extra == 'dev'
252
- Requires-Dist: bearer>=3.1.0; extra == 'dev'
253
253
  Requires-Dist: beartype>=0.22.4; extra == 'dev'
254
254
  Requires-Dist: crosshair-tool>=0.0.97; extra == 'dev'
255
255
  Requires-Dist: graphviz>=0.20.1; extra == 'dev'
256
256
  Requires-Dist: hypothesis>=6.142.4; extra == 'dev'
257
257
  Requires-Dist: icontract>=2.7.1; extra == 'dev'
258
258
  Requires-Dist: isort>=7.0.0; extra == 'dev'
259
+ Requires-Dist: pip-audit>=2.0.0; extra == 'dev'
260
+ Requires-Dist: pip-licenses>=4.0.0; extra == 'dev'
259
261
  Requires-Dist: pip-tools>=7.5.1; extra == 'dev'
260
- Requires-Dist: pyan3>=1.2.0; extra == 'dev'
262
+ Requires-Dist: pycg==0.0.7; extra == 'dev'
261
263
  Requires-Dist: pylint>=4.0.2; extra == 'dev'
262
264
  Requires-Dist: pytest-asyncio>=1.2.0; extra == 'dev'
263
265
  Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
@@ -271,10 +273,8 @@ Requires-Dist: setuptools<82,>=69.0.0; extra == 'dev'
271
273
  Requires-Dist: tomlkit>=0.13.3; extra == 'dev'
272
274
  Requires-Dist: types-pyyaml>=6.0.12.20250516; extra == 'dev'
273
275
  Provides-Extra: enhanced-analysis
274
- Requires-Dist: bearer>=3.1.0; extra == 'enhanced-analysis'
275
276
  Requires-Dist: graphviz>=0.20.1; extra == 'enhanced-analysis'
276
- Requires-Dist: pyan3>=1.2.0; extra == 'enhanced-analysis'
277
- Requires-Dist: syft>=0.9.5; extra == 'enhanced-analysis'
277
+ Requires-Dist: pycg==0.0.7; extra == 'enhanced-analysis'
278
278
  Provides-Extra: scanning
279
279
  Requires-Dist: semgrep>=1.144.0; extra == 'scanning'
280
280
  Description-Content-Type: text/markdown
@@ -308,7 +308,7 @@ uvx specfact-cli code review run --path . --scope full
308
308
  **Sample output:**
309
309
 
310
310
  ```text
311
- SpecFact CLI - v0.46.0
311
+ SpecFact CLI - v0.46.4
312
312
 
313
313
  Running Ruff checks...
314
314
  Running Radon complexity checks...
@@ -361,16 +361,28 @@ It exists because delivery drifts in predictable ways:
361
361
 
362
362
  ## Add SpecFact to your workflow
363
363
 
364
- **Pre-commit hook**
364
+ ### Pre-commit hook
365
+
366
+ This repository uses a **modular** local hook layout (parity with `specfact-cli-modules`: `fail_fast`,
367
+ separate verify / format / YAML / Markdown / workflow / lint / Block 2 hooks). If you copy
368
+ [`.pre-commit-config.yaml`](.pre-commit-config.yaml) into another repo, you must also vendor the
369
+ referenced `scripts/*.sh` entrypoints (at minimum `scripts/pre-commit-quality-checks.sh`,
370
+ `scripts/pre-commit-verify-modules.sh`, `scripts/module-verify-policy.sh`, and
371
+ `scripts/git-branch-module-signature-flag.sh`) so hook
372
+ `entry:` paths resolve. Alternatively, skip vendoring the modular file and use the remote hook below.
373
+
374
+ For a **single-hook** setup in downstream repos, keep using the stable id and script shim:
365
375
 
366
376
  ```yaml
367
377
  - repo: https://github.com/nold-ai/specfact-cli
368
- rev: v0.46.0
378
+ rev: v0.46.4
369
379
  hooks:
370
380
  - id: specfact-smart-checks
371
381
  ```
372
382
 
373
- **GitHub Actions**
383
+ The shim runs `scripts/pre-commit-quality-checks.sh all` (full pipeline including module verify).
384
+
385
+ ### GitHub Actions
374
386
 
375
387
  ```yaml
376
388
  - name: SpecFact Gate
@@ -27,7 +27,7 @@ uvx specfact-cli code review run --path . --scope full
27
27
  **Sample output:**
28
28
 
29
29
  ```text
30
- SpecFact CLI - v0.46.0
30
+ SpecFact CLI - v0.46.4
31
31
 
32
32
  Running Ruff checks...
33
33
  Running Radon complexity checks...
@@ -80,16 +80,28 @@ It exists because delivery drifts in predictable ways:
80
80
 
81
81
  ## Add SpecFact to your workflow
82
82
 
83
- **Pre-commit hook**
83
+ ### Pre-commit hook
84
+
85
+ This repository uses a **modular** local hook layout (parity with `specfact-cli-modules`: `fail_fast`,
86
+ separate verify / format / YAML / Markdown / workflow / lint / Block 2 hooks). If you copy
87
+ [`.pre-commit-config.yaml`](.pre-commit-config.yaml) into another repo, you must also vendor the
88
+ referenced `scripts/*.sh` entrypoints (at minimum `scripts/pre-commit-quality-checks.sh`,
89
+ `scripts/pre-commit-verify-modules.sh`, `scripts/module-verify-policy.sh`, and
90
+ `scripts/git-branch-module-signature-flag.sh`) so hook
91
+ `entry:` paths resolve. Alternatively, skip vendoring the modular file and use the remote hook below.
92
+
93
+ For a **single-hook** setup in downstream repos, keep using the stable id and script shim:
84
94
 
85
95
  ```yaml
86
96
  - repo: https://github.com/nold-ai/specfact-cli
87
- rev: v0.46.0
97
+ rev: v0.46.4
88
98
  hooks:
89
99
  - id: specfact-smart-checks
90
100
  ```
91
101
 
92
- **GitHub Actions**
102
+ The shim runs `scripts/pre-commit-quality-checks.sh all` (full pipeline including module verify).
103
+
104
+ ### GitHub Actions
93
105
 
94
106
  ```yaml
95
107
  - name: SpecFact Gate
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "specfact-cli"
7
- version = "0.46.0"
7
+ version = "0.46.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"
@@ -77,6 +77,7 @@ dependencies = [
77
77
  "graphviz>=0.20.1", # Graph visualization (requires system Graphviz: apt-get install graphviz)
78
78
 
79
79
  # Git operations
80
+ # gitpython: CVE history (CVE-2022-24439, CVE-2023-41040, CVE-2023-40590). Phase 2: replace with dulwich.
80
81
  "gitpython>=3.1.45",
81
82
 
82
83
  # YAML utilities
@@ -84,8 +85,8 @@ dependencies = [
84
85
 
85
86
  # Schema validation
86
87
  "jsonschema>=4.23.0",
87
- # VS Code settings.json is often JSON with comments (JSONC); parse/emit via JSON5 subset
88
- "json5>=0.9.28",
88
+ # VS Code settings.json is JSONC (comments + trailing commas); commentjson (MIT) strips them, delegates to stdlib json
89
+ "commentjson>=0.9.0",
89
90
 
90
91
  # Contract-First (runtime decorators; exploration tools are optional extra `contracts`)
91
92
  "icontract>=2.7.1", # Design-by-contract decorators
@@ -114,13 +115,13 @@ dev = [
114
115
  "pytest-xdist>=3.8.0",
115
116
  "basedpyright>=1.32.1",
116
117
  "isort>=7.0.0",
117
- "pylint>=4.0.2",
118
+ "pylint>=4.0.2", # GPL-2.0-or-later — dev-only exception (not in module manifests). Phase 2: replace with ruff --select ALL.
118
119
  "ruff>=0.14.2",
119
120
  "radon>=6.0.1",
120
121
  "tomlkit>=0.13.3", # Style-preserving TOML library (recommended successor to pytoml)
121
122
  "types-PyYAML>=6.0.12.20250516",
122
123
  "pip-tools>=7.5.1",
123
- "semgrep>=1.144.0", # Latest version compatible with rich~=13.5.2
124
+ "semgrep>=1.144.0", # LGPL-2.1 required for code analysis; invoked as subprocess (accepted exception)
124
125
 
125
126
  # Same contract exploration stack as [contracts] (extras cannot self-reference)
126
127
  "crosshair-tool>=0.0.97",
@@ -131,11 +132,14 @@ dev = [
131
132
  "beartype>=0.22.4",
132
133
 
133
134
  # Enhanced Analysis Tools (for local development)
134
- # Note: syft excluded from dev/test due to rich version conflict with semgrep
135
- # Install separately: pip install specfact-cli[enhanced-analysis] if needed
136
135
  "graphviz>=0.20.1", # Graph visualization (requires system Graphviz: apt-get install graphviz)
137
- "pyan3>=1.2.0", # Python call graph analysis
138
- "bearer>=3.1.0", # Data flow analysis for security
136
+ "pycg==0.0.7", # Python call graph analysis (Apache-2.0; replaces removed GPL-2.0 pyan3; pin avoids 0.0.8)
137
+ # Removed from dev: pyan3 (GPL-2.0) and bearer (wrong PyPI package; SaaS auth, not scanner)
138
+ # syft removed — wrong PyPI package (OpenMined ML framework, not Anchore SBOM)
139
+ "bandit>=1.7.0", # SAST scanner (MIT)
140
+ "pip-licenses>=4.0.0", # License enumeration for compliance gate (MIT)
141
+ "pip-audit>=2.0.0", # CVE audit via OSV database (Apache-2.0)
142
+ # pylint: GPL-2.0-or-later — dev-only exception (not in module manifests). Phase 2: replace with ruff --select ALL.
139
143
  ]
140
144
 
141
145
  scanning = [
@@ -144,9 +148,8 @@ scanning = [
144
148
 
145
149
  enhanced-analysis = [
146
150
  "graphviz>=0.20.1", # Graph visualization (requires system Graphviz: apt-get install graphviz)
147
- "pyan3>=1.2.0", # Python call graph analysis
148
- "syft>=0.9.5", # Software Bill of Materials (SBOM) generation
149
- "bearer>=3.1.0", # Data flow analysis for security
151
+ "pycg==0.0.7", # Python call graph analysis (Apache-2.0; replaces removed GPL-2.0 pyan3; pin avoids 0.0.8)
152
+ # Removed from enhanced-analysis: pyan3 (GPL-2.0), bearer, and syft (wrong PyPI packages)
150
153
  ]
151
154
 
152
155
  # Note: Specmatic integration (specfact spec commands) requires the Specmatic CLI tool
@@ -188,28 +191,36 @@ dependencies = [
188
191
  "pylint>=4.0.2",
189
192
  "ruff>=0.14.2",
190
193
  "radon>=6.0.1",
191
- "yamllint>=1.37.1",
192
- "semgrep>=1.144.0", # Latest version compatible with rich~=13.5.2
194
+ "yamllint>=1.37.1", # GPL-3.0-or-later — dev/test-only exception. Phase 2: replace with non-GPL YAML lint path.
195
+ "semgrep>=1.144.0", # LGPL-2.1 required for code analysis; invoked as subprocess (accepted exception)
193
196
  # Contract-First Development Dependencies
194
197
  "icontract>=2.7.1",
195
- "beartype>=0.22.4",
198
+ "beartype>=0.22.4",
196
199
  "crosshair-tool>=0.0.97",
197
200
  "hypothesis>=6.142.4",
198
201
  # Enhanced Analysis Tools (for local development)
199
- # Note: syft excluded from dev/test due to rich version conflict with semgrep
200
- # Install separately: pip install specfact-cli[enhanced-analysis] if needed
201
202
  "graphviz>=0.20.1", # Graph visualization (requires system Graphviz: apt-get install graphviz)
202
- "pyan3>=1.2.0", # Python call graph analysis
203
- "bearer>=3.1.0", # Data flow analysis for security
203
+ "pycg==0.0.7", # Python call graph analysis (Apache-2.0; replaces removed GPL-2.0 pyan3; pin avoids 0.0.8)
204
+ # Removed from Hatch default: pyan3 (GPL-2.0), bearer, and syft (wrong PyPI packages)
205
+ "bandit>=1.7.0", # SAST scanner (MIT)
206
+ "pip-licenses>=4.0.0", # License enumeration for compliance gate (MIT)
207
+ "pip-audit>=2.0.0", # CVE audit via OSV database (Apache-2.0)
208
+ # pylint: GPL-2.0-or-later — dev-only exception (not in module manifests). Phase 2: replace with ruff --select ALL.
204
209
  ]
205
210
 
206
211
  [tool.hatch.envs.default.scripts]
207
212
  validate-prompts = "python tools/validate_prompts.py"
213
+ # Security and license compliance gates
214
+ bandit-scan = "bandit -r src/ -ll"
215
+ license-check = "python scripts/check_license_compliance.py"
216
+ # Wrap pip-audit so JSON parsing and CVSS threshold checks fail closed.
217
+ security-audit = "python scripts/security_audit_gate.py"
208
218
  # Development scripts
209
219
  test = "pytest {args}"
210
220
  test-cov = "pytest --cov=src --cov-report=term-missing {args}"
211
221
  type-check = "basedpyright --pythonpath $(python -c 'import sys; print(sys.executable)') {args}"
212
- lint = "ruff format . --check && basedpyright --pythonpath $(python -c 'import sys; print(sys.executable)') && ruff check . && pylint src tests tools && python scripts/verify_safe_project_writes.py"
222
+ # basedpyright --level error: suppress warning noise in pre-commit (Block 1 runs `hatch run lint`).
223
+ lint = "ruff format . --check && basedpyright --level error --pythonpath $(python -c 'import sys; print(sys.executable)') && ruff check . && pylint src tests tools && python scripts/verify_safe_project_writes.py"
213
224
  governance = "pylint src tests tools --reports=y --output-format=parseable"
214
225
  format = "ruff check . --fix && ruff format ."
215
226
 
@@ -236,7 +247,8 @@ check-cross-site-links = "python scripts/check-cross-site-links.py"
236
247
  doc-frontmatter-check = "python scripts/check_doc_frontmatter.py"
237
248
  validate-agent-rule-signals = "python scripts/validate_agent_rule_applies_when.py"
238
249
  check-version-sources = "python scripts/check_version_sources.py"
239
- release = "python scripts/check_version_sources.py"
250
+ check-pypi-ahead = "python scripts/check_local_version_ahead_of_pypi.py"
251
+ release = "python scripts/check_local_version_ahead_of_pypi.py && python scripts/check_version_sources.py"
240
252
  docs-validate = "python scripts/check-docs-commands.py && python scripts/check-cross-site-links.py --warn-only && python scripts/check_doc_frontmatter.py && python scripts/validate_agent_rule_applies_when.py"
241
253
 
242
254
  # Legacy entry (kept for compatibility); prefer `workflows-lint` above
@@ -259,10 +271,15 @@ smart-test-e2e = "python tools/smart_test_coverage.py run --level e2e {args}"
259
271
  smart-test-full = "python tools/smart_test_coverage.py run --level full {args}"
260
272
  smart-test-auto = "python tools/smart_test_coverage.py run --level auto {args}"
261
273
 
274
+ # Bundled module verify (flags from scripts/module-verify-policy.sh via run_verify_modules_policy.sh)
275
+ verify-modules-signature = "bash scripts/run_verify_modules_policy.sh strict {args}"
276
+ verify-modules-signature-pr = "bash scripts/run_verify_modules_policy.sh pr {args}"
277
+ verify-modules-signature-push = "bash scripts/run_verify_modules_policy.sh push-orchestrator {args}"
278
+
262
279
  # Module migration pre-deletion gate
263
280
  verify-removal-gate = [
264
281
  "python scripts/verify-bundle-published.py --modules project,plan,import_cmd,sync,migrate,backlog,policy_engine,analyze,drift,validate,repro,contract,spec,sdd,generate,enforce,patch_mode",
265
- "python scripts/verify-modules-signature.py --require-signature",
282
+ "hatch run verify-modules-signature",
266
283
  ]
267
284
  export-change-github = "python scripts/export-change-to-github.py {args}"
268
285
 
@@ -286,7 +303,6 @@ contract-prune = "python tools/migrate_tests_to_contracts.py --prune --dry-run"
286
303
  # Pre-commit hooks
287
304
  pre-commit-install = "bash scripts/setup-git-hooks.sh"
288
305
  pre-commit-checks = "bash scripts/pre-commit-smart-checks.sh"
289
- pre-commit-test = "bash scripts/pre-commit-smart-test.sh"
290
306
 
291
307
  [tool.hatch.envs.py311]
292
308
  python = "3.11"
@@ -313,13 +329,12 @@ dependencies = [
313
329
  "beartype>=0.22.4",
314
330
  "crosshair-tool>=0.0.97",
315
331
  "hypothesis>=6.142.4",
316
- "yamllint>=1.37.1",
332
+ "yamllint>=1.37.1", # GPL-3.0-or-later — dev/test-only exception. Phase 2: replace with non-GPL YAML lint path.
317
333
  # Enhanced Analysis Tools (for testing)
318
- # Note: syft excluded from test due to rich version conflict with semgrep
319
- # Install separately: pip install specfact-cli[enhanced-analysis] if needed
320
334
  "graphviz>=0.20.1", # Graph visualization (requires system Graphviz: apt-get install graphviz)
321
- "pyan3>=1.2.0", # Python call graph analysis
322
- "bearer>=3.1.0", # Data flow analysis for security
335
+ "pycg==0.0.7", # Python call graph analysis (Apache-2.0; replaces removed GPL-2.0 pyan3; pin avoids 0.0.8)
336
+ # Removed from Hatch test: pyan3 (GPL-2.0), bearer, and syft (wrong PyPI packages)
337
+ "commentjson>=0.9.0", # JSONC parser (MIT; replaces json5 for VS Code settings JSONC support)
323
338
  ]
324
339
  dev-mode = true
325
340
  parallel = true
@@ -0,0 +1,20 @@
1
+ {
2
+ "modules": [
3
+ {
4
+ "id": "init",
5
+ "latest_version": "0.1.30"
6
+ },
7
+ {
8
+ "id": "upgrade",
9
+ "latest_version": "0.1.4"
10
+ },
11
+ {
12
+ "id": "module-registry",
13
+ "latest_version": "0.1.19"
14
+ },
15
+ {
16
+ "id": "bundle-mapper",
17
+ "latest_version": "0.1.9"
18
+ }
19
+ ]
20
+ }
@@ -3,4 +3,4 @@ SpecFact CLI - Spec→Contract→Sentinel tool for contract-driven development.
3
3
  """
4
4
 
5
5
  # Package version: keep in sync with pyproject.toml, setup.py, src/specfact_cli/__init__.py
6
- __version__ = "0.46.0"
6
+ __version__ = "0.46.4"
@@ -45,6 +45,6 @@ def _bootstrap_bundle_paths() -> None:
45
45
 
46
46
  _bootstrap_bundle_paths()
47
47
 
48
- __version__ = "0.46.0"
48
+ __version__ = "0.46.4"
49
49
 
50
50
  __all__ = ["__version__"]
@@ -54,7 +54,7 @@ console = Console()
54
54
 
55
55
  @dataclass(frozen=True, slots=True)
56
56
  class _AdoCreatedWorkItemRef:
57
- work_item_id: Any
57
+ work_item_id: int | str
58
58
  work_item_url: str
59
59
  org: str
60
60
  project: str
@@ -744,7 +744,10 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
744
744
  },
745
745
  }
746
746
  source_tracking = proposal_data.get("source_tracking")
747
- if source_tracking is None:
747
+ if isinstance(source_tracking, list):
748
+ cast(list[dict[str, Any]], source_tracking).append(tracking_update)
749
+ return
750
+ if source_tracking is None or (isinstance(source_tracking, dict) and len(source_tracking) == 0):
748
751
  proposal_data["source_tracking"] = tracking_update
749
752
  return
750
753
  if isinstance(source_tracking, dict):
@@ -752,11 +755,6 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
752
755
  st.update(tracking_update)
753
756
  proposal_data["source_tracking"] = st
754
757
  return
755
- if isinstance(source_tracking, list):
756
- if not source_tracking:
757
- proposal_data["source_tracking"] = [tracking_update]
758
- return
759
- cast(list[dict[str, Any]], source_tracking).append(tracking_update)
760
758
 
761
759
  def _is_on_premise(self) -> bool:
762
760
  """
@@ -2270,8 +2268,10 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
2270
2268
  Returns:
2271
2269
  Dict with updated work item data: {"work_item_id": int, "work_item_url": str, "state": str}
2272
2270
  """
2273
- target_repo = f"{org}/{project}"
2274
- work_item_id = self._get_source_tracking_work_item_id(proposal_data.get("source_tracking", {}), target_repo)
2271
+ work_item_id = self._get_source_tracking_work_item_id(
2272
+ proposal_data.get("source_tracking", {}),
2273
+ f"{org}/{project}",
2274
+ )
2275
2275
  ado_state = self._resolve_proposal_ado_state(proposal_data)
2276
2276
  work_item_data = self._patch_work_item(
2277
2277
  org,
@@ -159,7 +159,11 @@ def _get_github_token_from_gh_cli() -> str | None:
159
159
  return None
160
160
 
161
161
 
162
- _GITHUB_GIT_CONFIG_URL_RE = re.compile(r"(?im)^\s*url\s*=\s*(https?://\S+|ssh://\S+|git://\S+|git@[^:\s]+:\S+)\s*$")
162
+ # Line-anchored so ``pushurl =`` / ``insteadOf`` lines do not match the ``url`` token inside another key.
163
+ # Matches: https://, http://, ssh://, git://, and git@host:path remotes.
164
+ _GITHUB_GIT_CONFIG_URL_RE = re.compile(
165
+ r"(?m)^\s*url\s*=\s*(https?://[^\s]+|ssh://[^\s]+|git://[^\s]+|git@[^:]+:[^\s]+)"
166
+ )
163
167
 
164
168
 
165
169
  def _git_config_content_indicates_github(config_content: str) -> bool:
@@ -1471,7 +1475,14 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
1471
1475
  title = raw_title
1472
1476
 
1473
1477
  body = self._render_issue_body(
1474
- _IssueBodyRenderInput(title, description, rationale, impact, change_id, raw_body)
1478
+ _IssueBodyRenderInput(
1479
+ title=title,
1480
+ description=description,
1481
+ rationale=rationale,
1482
+ impact=impact,
1483
+ change_id=change_id,
1484
+ raw_body=raw_body,
1485
+ )
1475
1486
  )
1476
1487
 
1477
1488
  # Check for API token before making request
@@ -1735,7 +1746,15 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
1735
1746
  current_body, current_title, current_state = self._fetch_issue_snapshot(repo_owner, repo_name, issue_number)
1736
1747
  preserved_sections = self._preserved_issue_sections(current_body, change_id)
1737
1748
  body = self._render_issue_body(
1738
- _IssueBodyRenderInput(title, description, rationale, impact, change_id, raw_body, preserved_sections)
1749
+ _IssueBodyRenderInput(
1750
+ title=title,
1751
+ description=description,
1752
+ rationale=rationale,
1753
+ impact=impact,
1754
+ change_id=change_id,
1755
+ raw_body=raw_body,
1756
+ preserved_sections=preserved_sections,
1757
+ )
1739
1758
  )
1740
1759
 
1741
1760
  # Update issue body via GitHub API PATCH
@@ -462,15 +462,19 @@ class SpecKitAdapter(BridgeAdapter):
462
462
  story_title = sd.get("title", "Unknown Story")
463
463
  priority = sd.get("priority", "P3")
464
464
  acceptance_raw = sd.get("acceptance", [])
465
- default_acceptance = [f"{story_title} is implemented"]
466
465
  if isinstance(acceptance_raw, list) and acceptance_raw:
467
466
  if all(isinstance(x, str) for x in acceptance_raw):
468
- acceptance = list(acceptance_raw) or default_acceptance
467
+ acceptance = [s.strip() for s in acceptance_raw if isinstance(s, str) and s.strip()]
469
468
  else:
470
- extracted = self._extract_text_list(cast(list[Any], acceptance_raw))
471
- acceptance = extracted or default_acceptance
469
+ acceptance = [
470
+ s.strip() for s in self._extract_text_list(cast(list[Any], acceptance_raw)) if s.strip()
471
+ ]
472
+ if not acceptance:
473
+ acceptance = [f"{story_title} is implemented"]
474
+ if not acceptance:
475
+ acceptance = [f"{story_title} is implemented"]
472
476
  else:
473
- acceptance = default_acceptance
477
+ acceptance = [f"{story_title} is implemented"]
474
478
  story_points = priority_map.get(str(priority), 3)
475
479
  stories.append(
476
480
  Story(
@@ -548,14 +552,13 @@ class SpecKitAdapter(BridgeAdapter):
548
552
 
549
553
  if existing_feature:
550
554
  existing_feature.title = payload.feature_title
551
- existing_feature.outcomes = (
552
- list(payload.outcomes) if payload.outcomes else list(existing_feature.outcomes or [])
553
- )
554
- existing_feature.acceptance = (
555
- list(payload.acceptance) if payload.acceptance else list(existing_feature.acceptance or [])
556
- )
555
+ existing_feature.outcomes = list(payload.outcomes)
556
+ existing_feature.acceptance = list(payload.acceptance)
557
557
  existing_feature.stories = [s.model_copy(deep=True) for s in payload.stories]
558
558
  existing_feature.constraints = list(constraints)
559
+ existing_feature.source_tracking = self._build_speckit_source_tracking(
560
+ payload.spec_path, payload.bridge_config
561
+ )
559
562
  return
560
563
 
561
564
  feature = Feature(
@@ -507,16 +507,16 @@ class CodeAnalyzer:
507
507
  }
508
508
  )
509
509
 
510
- # Dependency Graph Analysis (requires pyan3 and networkx)
511
- pyan3_available, _ = check_cli_tool_available("pyan3")
510
+ # Dependency Graph Analysis (requires pycg and networkx)
511
+ pycg_available, _ = check_cli_tool_available("pycg")
512
512
  networkx_available = check_python_package_available("networkx")
513
- graph_enabled = pyan3_available and networkx_available
513
+ graph_enabled = pycg_available and networkx_available
514
514
  graph_used = graph_enabled # Used if both dependencies are available
515
515
 
516
- if not pyan3_available and not networkx_available:
517
- reason = "pyan3 and networkx not installed (install: pip install pyan3 networkx)"
518
- elif not pyan3_available:
519
- reason = "pyan3 not installed (install: pip install pyan3)"
516
+ if not pycg_available and not networkx_available:
517
+ reason = "pycg and networkx not installed (install: pip install pycg networkx)"
518
+ elif not pycg_available:
519
+ reason = "pycg not installed (install: pip install pycg)"
520
520
  elif not networkx_available:
521
521
  reason = "networkx not installed (install: pip install networkx)"
522
522
  else:
@@ -738,19 +738,8 @@ class CodeAnalyzer:
738
738
  @staticmethod
739
739
  def _themes_for_import_module(module_name: str, theme_keywords: dict[str, str]) -> set[str]:
740
740
  lowered = module_name.lower()
741
- tokens = [t for t in re.split(r"[._-]", lowered) if t]
742
- top_level = lowered.split(".", 1)[0]
743
- found: set[str] = set()
744
- for keyword, theme in theme_keywords.items():
745
- if keyword == lowered or lowered.startswith(f"{keyword}."):
746
- found.add(theme)
747
- continue
748
- if keyword == top_level or top_level.startswith(f"{keyword}."):
749
- found.add(theme)
750
- continue
751
- if keyword in tokens:
752
- found.add(theme)
753
- return found
741
+ segments = {p for p in lowered.replace("-", ".").split(".") if p}
742
+ return {theme for keyword, theme in theme_keywords.items() if keyword in segments}
754
743
 
755
744
  def _themes_for_import_node(self, node: ast.Import | ast.ImportFrom, theme_keywords: dict[str, str]) -> set[str]:
756
745
  if isinstance(node, ast.Import):
@@ -1745,14 +1734,50 @@ class CodeAnalyzer:
1745
1734
  self.async_patterns[module_name].extend(async_methods)
1746
1735
  return async_methods
1747
1736
 
1748
- def _detect_async_patterns_parallel(self, tree: ast.AST, file_path: Path) -> list[str]:
1737
+ @staticmethod
1738
+ def _build_ast_parent_map(tree: ast.AST) -> dict[ast.AST, ast.AST | None]:
1739
+ parents: dict[ast.AST, ast.AST | None] = {}
1740
+
1741
+ def visit(node: ast.AST, parent: ast.AST | None) -> None:
1742
+ parents[node] = parent
1743
+ for child in ast.iter_child_nodes(node):
1744
+ visit(child, node)
1745
+
1746
+ visit(tree, None)
1747
+ return parents
1748
+
1749
+ @staticmethod
1750
+ def _function_name_holding_await(parents: dict[ast.AST, ast.AST | None], await_node: ast.Await) -> str | None:
1751
+ current: ast.AST | None = await_node
1752
+ while current is not None:
1753
+ par = parents.get(current)
1754
+ if par is None:
1755
+ return None
1756
+ if isinstance(par, (ast.FunctionDef, ast.AsyncFunctionDef)):
1757
+ return par.name
1758
+ current = par
1759
+ return None
1760
+
1761
+ def _detect_async_patterns_parallel(self, tree: ast.AST, _file_path: Path) -> list[str]:
1749
1762
  """
1750
1763
  Detect async/await patterns in code (thread-safe version).
1751
1764
 
1752
1765
  Returns:
1753
1766
  List of async method/function names
1754
1767
  """
1755
- return [node.name for node in ast.walk(tree) if isinstance(node, ast.AsyncFunctionDef)]
1768
+ async_methods: list[str] = []
1769
+ parents = self._build_ast_parent_map(tree)
1770
+
1771
+ for node in ast.walk(tree):
1772
+ if isinstance(node, ast.AsyncFunctionDef):
1773
+ async_methods.append(node.name)
1774
+ if not isinstance(node, ast.Await):
1775
+ continue
1776
+ host = self._function_name_holding_await(parents, node)
1777
+ if host and host not in async_methods:
1778
+ async_methods.append(host)
1779
+
1780
+ return async_methods
1756
1781
 
1757
1782
  def _apply_commit_hash_to_matching_features(self, feature_num: str, commit_hash: str) -> None:
1758
1783
  for feature in self.features: