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.
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/.gitignore +3 -1
- specfact_cli-0.31.0/LICENSE.md → specfact_cli-0.32.0/LICENSE +3 -4
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/PKG-INFO +8 -8
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/README.md +3 -2
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/pyproject.toml +3 -3
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/__init__.py +1 -1
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/code_analyzer.py +7 -5
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/change.py +8 -4
- specfact_cli-0.32.0/src/specfact_cli/models/module_package.py +158 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/plan.py +30 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/project.py +28 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/commands.py +3 -4
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/plan/src/commands.py +3 -3
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/sync/src/commands.py +3 -2
- specfact_cli-0.32.0/src/specfact_cli/registry/crypto_validator.py +124 -0
- specfact_cli-0.32.0/src/specfact_cli/registry/extension_registry.py +59 -0
- specfact_cli-0.32.0/src/specfact_cli/registry/module_installer.py +60 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/registry/module_packages.py +88 -2
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/telemetry.py +19 -14
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/registry.py +5 -3
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/ide_setup.py +5 -3
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/terminal.py +3 -4
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/repro_checker.py +4 -2
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/schema.py +8 -4
- specfact_cli-0.31.0/src/specfact_cli/models/module_package.py +0 -73
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/shared/cli-enforcement.md +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.01-import.md +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.02-plan.md +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.03-review.md +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.04-sdd.md +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.05-enforce.md +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.06-sync.md +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.07-contracts.md +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.backlog-daily.md +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.backlog-refine.md +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.compare.md +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.sync-backlog.md +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/prompts/specfact.validate.md +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/field_mappings/ado_agile.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/field_mappings/ado_default.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/field_mappings/ado_kanban.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/field_mappings/ado_safe.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/field_mappings/ado_scrum.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/frameworks/safe/safe_feature_v1.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/personas/developer/developer_task_v1.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/backlog/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/__main__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/ado.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/backlog_base.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/base.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/github.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/openspec.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/openspec_parser.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/registry.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/adapters/speckit.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/adapters/base.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/adapters/local_yaml_adapter.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/ai_refiner.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/converter.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/filters.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/format_detector.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/formats/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/formats/base.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/formats/markdown_format.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/formats/structured_format.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/mappers/base.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/backlog/template_detector.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/cli.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/auth.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/backlog_commands.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/project_cmd.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/update.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/commands/validate.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/contracts/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/contracts/crosshair_props.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/contracts/module_interface.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/persona_exporter.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/backlog_item.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/capabilities.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/dor_config.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/models/validation.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/analyze/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/analyze/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/analyze/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/analyze/src/commands.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/auth/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/auth/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/auth/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/auth/src/commands.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/adapters/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/adapters/ado.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/adapters/base.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/adapters/github.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/adapters/jira.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/adapters/linear.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/contract/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/contract/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/contract/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/contract/src/commands.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/drift/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/drift/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/drift/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/drift/src/commands.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/enforce/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/enforce/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/enforce/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/enforce/src/commands.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/generate/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/generate/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/generate/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/generate/src/commands.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/import_cmd/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/import_cmd/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/import_cmd/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/import_cmd/src/commands.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/init/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/init/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/init/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/init/src/commands.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/migrate/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/migrate/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/migrate/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/migrate/src/commands.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/module_io_shim.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/plan/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/plan/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/plan/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/project/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/project/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/project/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/project/src/commands.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/repro/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/repro/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/repro/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/repro/src/commands.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/sdd/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/sdd/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/sdd/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/sdd/src/commands.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/spec/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/spec/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/spec/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/spec/src/commands.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/sync/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/sync/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/sync/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/upgrade/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/upgrade/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/upgrade/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/upgrade/src/commands.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/validate/module-package.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/validate/src/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/validate/src/app.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/validate/src/commands.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/registry/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/registry/bootstrap.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/registry/bridge_registry.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/registry/help_cache.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/registry/metadata.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/registry/module_state.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/registry/registry.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/bridge_sync.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/bridge_templates.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/auth_tokens.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/bundle_converters.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/code_change_detector.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/content_sanitizer.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/env_manager.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/metadata.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/persona_ownership.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/startup_checks.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/models.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/versioning/analyzer.py +0 -0
|
@@ -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
|
-
(
|
|
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
|
|
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.
|
|
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
|
-
(
|
|
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
|
|
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
|
|
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
|
[](https://pypi.org/project/specfact-cli/)
|
|
288
287
|
[](https://pypi.org/project/specfact-cli/)
|
|
289
|
-
[](LICENSE
|
|
288
|
+
[](LICENSE)
|
|
290
289
|
[](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
|
|
524
|
+
[Full license](LICENSE)
|
|
525
525
|
|
|
526
526
|
---
|
|
527
527
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
[](https://pypi.org/project/specfact-cli/)
|
|
10
10
|
[](https://pypi.org/project/specfact-cli/)
|
|
11
|
-
[](LICENSE
|
|
11
|
+
[](LICENSE)
|
|
12
12
|
[](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
|
|
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.
|
|
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
|
|
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
|
|
388
|
+
"/LICENSE",
|
|
389
389
|
"/pyproject.toml",
|
|
390
390
|
]
|
|
391
391
|
# Exclude development files, tests, docs, tools, etc.
|
|
@@ -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:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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:
|
|
52
|
-
|
|
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:
|
|
57
|
-
|
|
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
|
{specfact_cli-0.31.0 → specfact_cli-0.32.0}/src/specfact_cli/modules/backlog/src/commands.py
RENAMED
|
@@ -3626,10 +3626,9 @@ def refine(
|
|
|
3626
3626
|
|
|
3627
3627
|
@app.command("map-fields")
|
|
3628
3628
|
@require(
|
|
3629
|
-
lambda ado_org, ado_project:
|
|
3630
|
-
|
|
3631
|
-
|
|
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:
|
|
3029
|
-
|
|
3030
|
-
|
|
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:
|
|
1088
|
-
|
|
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
|