specfact-cli 0.49.1__tar.gz → 0.50.1__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.49.1 → specfact_cli-0.50.1}/PKG-INFO +1 -1
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/pyproject.toml +1 -1
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/__init__.py +1 -1
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/project.py +10 -1
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/module_grouping.py +8 -1
- specfact_cli-0.50.1/src/specfact_cli/requirements/__init__.py +28 -0
- specfact_cli-0.50.1/src/specfact_cli/requirements/context.py +330 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/.gitignore +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/LICENSE +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/README.md +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/bundled-module-registry/index.json +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/keys/README.md +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/keys/module-signing-public.pem +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/templates/policies/kanban.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/templates/policies/mixed.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/templates/policies/safe.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/templates/policies/scrum.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/__main__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/adapters/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/adapters/ado.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/adapters/backlog_base.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/adapters/base.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/adapters/github.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/adapters/openspec.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/adapters/openspec_parser.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/adapters/registry.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/adapters/speckit.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/backlog/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/backlog/adapters/base.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/backlog/converter.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/backlog/filters.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/backlog/mappers/base.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/cli.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/_bundle_shim.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/project_cmd.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/update.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/commands/validate.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/common/bundle_factory.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/contracts/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/contracts/crosshair_props.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/contracts/module_interface.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/generators/persona_exporter.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/groups/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/groups/codebase_group.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/groups/govern_group.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/groups/member_group.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/groups/project_group.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/groups/spec_group.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/backlog_item.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/capabilities.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/change.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/dor_config.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/module_package.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/requirements.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/models/validation.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modules/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modules/_bundle_import.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modules/init/module-package.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modules/init/src/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modules/init/src/app.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modules/init/src/commands.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modules/init/src/first_run_selection.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modules/module_io_shim.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modules/module_registry/module-package.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modules/module_registry/src/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modules/module_registry/src/app.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modules/module_registry/src/commands.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modules/upgrade/module-package.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modules/upgrade/src/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modules/upgrade/src/app.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/modules/upgrade/src/commands.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/alias_manager.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/bootstrap.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/bridge_registry.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/crypto_validator.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/custom_registries.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/dependency_resolver.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/extension_registry.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/help_cache.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/marketplace_client.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/metadata.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/module_availability.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/module_discovery.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/module_installer.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/module_lifecycle.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/module_packages.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/module_security.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/module_state.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/registry/registry.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/bridge_sync.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/bridge_sync_openspec_md_parse.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/bridge_sync_requirement_from_proposal.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/bridge_sync_requirement_helpers.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/bridge_sync_tasks_from_proposal.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/bridge_sync_what_changes_format.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/bridge_sync_write_openspec_from_proposal.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/templates/registry.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/auth_tokens.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/bundle_converters.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/code_change_detector.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/content_sanitizer.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/contract_predicates.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/env_manager.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/icontract_helpers.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/metadata.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/persona_ownership.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/project_artifact_write.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/startup_checks.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/terminal.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validation/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validation/command_audit.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/schema.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/models.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.49.1 → specfact_cli-0.50.1}/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.
|
|
3
|
+
Version: 0.50.1
|
|
4
4
|
Summary: AI-bloat defense CLI for Python teams. Run deterministic code review, cleanup forecasts, and spec/contract evidence for AI-assisted and brownfield delivery.
|
|
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
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specfact-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.50.1"
|
|
8
8
|
description = "AI-bloat defense CLI for Python teams. Run deterministic code review, cleanup forecasts, and spec/contract evidence for AI-assisted and brownfield delivery."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -559,7 +559,13 @@ class ProjectBundle(BaseModel):
|
|
|
559
559
|
if progress_callback:
|
|
560
560
|
progress_callback(current + 1, total_artifacts, "bundle.manifest.yaml")
|
|
561
561
|
manifest_data = load_structured_file(manifest_path)
|
|
562
|
-
|
|
562
|
+
if not isinstance(manifest_data, dict):
|
|
563
|
+
raise ValueError("Bundle manifest must be a mapping")
|
|
564
|
+
manifest_mapping = cast(dict[str, Any], manifest_data)
|
|
565
|
+
extensions_data = manifest_mapping.get("extensions", {})
|
|
566
|
+
if not isinstance(extensions_data, dict):
|
|
567
|
+
raise ValueError("ProjectBundle extensions must be a mapping")
|
|
568
|
+
manifest = BundleManifest.model_validate(manifest_mapping)
|
|
563
569
|
current += 1
|
|
564
570
|
|
|
565
571
|
slots = _BundleLoadSlots()
|
|
@@ -585,6 +591,7 @@ class ProjectBundle(BaseModel):
|
|
|
585
591
|
features=slots.features,
|
|
586
592
|
clarifications=slots.clarifications,
|
|
587
593
|
change_tracking=change_tracking,
|
|
594
|
+
extensions=dict(extensions_data),
|
|
588
595
|
)
|
|
589
596
|
|
|
590
597
|
@beartype
|
|
@@ -663,6 +670,8 @@ class ProjectBundle(BaseModel):
|
|
|
663
670
|
progress_callback(total_artifacts, total_artifacts, "bundle.manifest.yaml")
|
|
664
671
|
manifest_path = bundle_dir / "bundle.manifest.yaml"
|
|
665
672
|
manifest_data = self.manifest.model_dump(mode="json")
|
|
673
|
+
if self.extensions:
|
|
674
|
+
manifest_data["extensions"] = self.extensions
|
|
666
675
|
if num_features > 1000:
|
|
667
676
|
import json
|
|
668
677
|
|
|
@@ -8,17 +8,21 @@ from icontract import ensure, require
|
|
|
8
8
|
from specfact_cli.models.module_package import ModulePackageMetadata
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
VALID_CATEGORIES = frozenset({"core", "project", "backlog", "codebase", "spec", "govern"})
|
|
11
|
+
VALID_CATEGORIES = frozenset({"core", "project", "backlog", "codebase", "spec", "govern", "requirements"})
|
|
12
12
|
CATEGORY_TO_GROUP_COMMAND: dict[str, str] = {
|
|
13
13
|
"project": "project",
|
|
14
14
|
"backlog": "backlog",
|
|
15
15
|
"codebase": "code",
|
|
16
16
|
"spec": "spec",
|
|
17
17
|
"govern": "govern",
|
|
18
|
+
"requirements": "requirements",
|
|
18
19
|
}
|
|
19
20
|
LEGACY_GROUP_COMMAND_ALIASES: dict[tuple[str, str], str] = {
|
|
20
21
|
("codebase", "codebase"): "code",
|
|
21
22
|
}
|
|
23
|
+
LEGACY_CATEGORY_ALIASES: dict[tuple[str, str], str] = {
|
|
24
|
+
("project", "requirements"): "requirements",
|
|
25
|
+
}
|
|
22
26
|
|
|
23
27
|
|
|
24
28
|
class ModuleManifestError(Exception):
|
|
@@ -71,6 +75,9 @@ def normalize_legacy_bundle_group_command(meta: ModulePackageMetadata) -> Module
|
|
|
71
75
|
"""Normalize known legacy bundle group values to canonical grouped commands."""
|
|
72
76
|
if meta.category is None or meta.bundle_group_command is None:
|
|
73
77
|
return meta
|
|
78
|
+
normalized_category = LEGACY_CATEGORY_ALIASES.get((meta.category, meta.bundle_group_command))
|
|
79
|
+
if normalized_category is not None:
|
|
80
|
+
meta.category = normalized_category
|
|
74
81
|
normalized = LEGACY_GROUP_COMMAND_ALIASES.get((meta.category, meta.bundle_group_command))
|
|
75
82
|
if normalized is not None:
|
|
76
83
|
meta.bundle_group_command = normalized
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Requirements context adapter helpers."""
|
|
2
|
+
|
|
3
|
+
from specfact_cli.requirements.context import (
|
|
4
|
+
RequirementContextAdapter,
|
|
5
|
+
RequirementContextCoverageSummary,
|
|
6
|
+
RequirementContextDiagnostic,
|
|
7
|
+
RequirementContextDiagnosticSeverity,
|
|
8
|
+
RequirementContextImportResult,
|
|
9
|
+
attach_requirements_to_bundle,
|
|
10
|
+
inspect_requirement_context_coverage,
|
|
11
|
+
load_requirements_from_bundle,
|
|
12
|
+
normalize_requirement_records,
|
|
13
|
+
validate_requirement_context,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"RequirementContextAdapter",
|
|
19
|
+
"RequirementContextCoverageSummary",
|
|
20
|
+
"RequirementContextDiagnostic",
|
|
21
|
+
"RequirementContextDiagnosticSeverity",
|
|
22
|
+
"RequirementContextImportResult",
|
|
23
|
+
"attach_requirements_to_bundle",
|
|
24
|
+
"inspect_requirement_context_coverage",
|
|
25
|
+
"load_requirements_from_bundle",
|
|
26
|
+
"normalize_requirement_records",
|
|
27
|
+
"validate_requirement_context",
|
|
28
|
+
]
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""Core requirements context adapter contracts and helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Literal, Protocol, cast, get_args
|
|
9
|
+
|
|
10
|
+
from beartype import beartype
|
|
11
|
+
from icontract import ensure, require
|
|
12
|
+
from pydantic import BaseModel, ConfigDict, Field, ValidationError
|
|
13
|
+
|
|
14
|
+
from specfact_cli.models.project import ProjectBundle
|
|
15
|
+
from specfact_cli.models.requirements import (
|
|
16
|
+
RequirementEvidenceLinkType,
|
|
17
|
+
RequirementInput,
|
|
18
|
+
load_requirements_input_extension,
|
|
19
|
+
requirements_input_extension_payload,
|
|
20
|
+
)
|
|
21
|
+
from specfact_cli.models.validation import ValidationReport
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
RequirementContextValidationProfile = Literal[
|
|
25
|
+
"solo",
|
|
26
|
+
"startup",
|
|
27
|
+
"team",
|
|
28
|
+
"enterprise",
|
|
29
|
+
"strict",
|
|
30
|
+
"solo_developer",
|
|
31
|
+
"api_first_team",
|
|
32
|
+
"enterprise_full_stack",
|
|
33
|
+
]
|
|
34
|
+
KNOWN_REQUIREMENT_CONTEXT_PROFILES: frozenset[str] = frozenset(get_args(RequirementContextValidationProfile))
|
|
35
|
+
STRICT_REQUIREMENT_CONTEXT_PROFILES: frozenset[str] = frozenset({"enterprise", "strict", "enterprise_full_stack"})
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class RequirementContextDiagnosticSeverity(StrEnum):
|
|
39
|
+
"""Severity for bounded requirements context diagnostics."""
|
|
40
|
+
|
|
41
|
+
INFO = "info"
|
|
42
|
+
WARNING = "warning"
|
|
43
|
+
ERROR = "error"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@beartype
|
|
47
|
+
class RequirementContextDiagnostic(BaseModel):
|
|
48
|
+
"""Bounded diagnostic emitted while importing requirement context."""
|
|
49
|
+
|
|
50
|
+
model_config = ConfigDict(use_enum_values=True)
|
|
51
|
+
|
|
52
|
+
severity: RequirementContextDiagnosticSeverity = Field(..., description="Diagnostic severity")
|
|
53
|
+
code: str = Field(..., min_length=1, description="Stable diagnostic code")
|
|
54
|
+
message: str = Field(..., min_length=1, description="Human-readable diagnostic message")
|
|
55
|
+
source_locator: str | None = Field(default=None, description="Optional upstream source locator")
|
|
56
|
+
requirement_id: str | None = Field(default=None, description="Best-effort requirement identifier")
|
|
57
|
+
record_index: int | None = Field(
|
|
58
|
+
default=None,
|
|
59
|
+
ge=0,
|
|
60
|
+
description="Zero-based source record index when available",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@beartype
|
|
65
|
+
class RequirementContextImportResult(BaseModel):
|
|
66
|
+
"""Normalized requirement import result plus bounded diagnostics."""
|
|
67
|
+
|
|
68
|
+
requirements: list[RequirementInput] = Field(default_factory=list, description="Normalized requirement records")
|
|
69
|
+
diagnostics: list[RequirementContextDiagnostic] = Field(
|
|
70
|
+
default_factory=list,
|
|
71
|
+
description="Import diagnostics for records that could not be normalized",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@beartype
|
|
76
|
+
class RequirementContextCoverageSummary(BaseModel):
|
|
77
|
+
"""Machine-readable coverage summary for normalized requirement inputs."""
|
|
78
|
+
|
|
79
|
+
total_requirements: int = Field(..., ge=0, description="Total normalized requirements")
|
|
80
|
+
with_business_rules: int = Field(..., ge=0, description="Requirements with business rules")
|
|
81
|
+
with_constraints: int = Field(..., ge=0, description="Requirements with constraints")
|
|
82
|
+
with_evidence_links: int = Field(..., ge=0, description="Requirements with evidence links")
|
|
83
|
+
with_architecture_links: int = Field(..., ge=0, description="Requirements linked to architecture evidence")
|
|
84
|
+
with_code_links: int = Field(..., ge=0, description="Requirements linked to code evidence")
|
|
85
|
+
with_test_links: int = Field(..., ge=0, description="Requirements linked to test evidence")
|
|
86
|
+
missing_evidence_requirement_ids: list[str] = Field(
|
|
87
|
+
default_factory=list,
|
|
88
|
+
description="Requirement IDs missing downstream evidence links",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class RequirementContextAdapter(Protocol):
|
|
93
|
+
"""Protocol for module adapters that import requirement-like sources."""
|
|
94
|
+
|
|
95
|
+
@beartype
|
|
96
|
+
@require(lambda source: isinstance(source, Path), "source must be a Path")
|
|
97
|
+
def import_requirements(self, source: Path, config: Mapping[str, Any]) -> RequirementContextImportResult:
|
|
98
|
+
"""Import source-attributed requirement records from an upstream source."""
|
|
99
|
+
raise NotImplementedError
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _all_records_supported(records: Sequence[RequirementInput | Mapping[str, Any]]) -> bool:
|
|
103
|
+
return all(isinstance(record, RequirementInput | Mapping) for record in records)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _normalization_result_valid(result: RequirementContextImportResult) -> bool:
|
|
107
|
+
return all(isinstance(requirement, RequirementInput) for requirement in result.requirements)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _record_requirement_id(record: RequirementInput | Mapping[str, Any]) -> str | None:
|
|
111
|
+
if isinstance(record, RequirementInput):
|
|
112
|
+
return _optional_text(record.requirement_id)
|
|
113
|
+
value = record.get("requirement_id")
|
|
114
|
+
return _optional_text(value) if isinstance(value, str) and value else None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _validation_error_message(exc: ValidationError) -> str:
|
|
118
|
+
first_error = exc.errors()[0] if exc.errors() else {"msg": str(exc)}
|
|
119
|
+
location = ".".join(str(part) for part in first_error.get("loc", ()))
|
|
120
|
+
message = str(first_error.get("msg", "invalid requirement input")) or "invalid requirement input"
|
|
121
|
+
return f"{location}: {message}" if location else message
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _optional_text(value: str | None) -> str | None:
|
|
125
|
+
if not value:
|
|
126
|
+
return None
|
|
127
|
+
realize = getattr(value, "__ch_realize__", None)
|
|
128
|
+
if callable(realize):
|
|
129
|
+
realized = realize()
|
|
130
|
+
return realized if isinstance(realized, str) else str(realized)
|
|
131
|
+
return value
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _profile_nonempty(profile: str) -> bool:
|
|
135
|
+
return profile.strip() != ""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _profile_supported(profile: str) -> bool:
|
|
139
|
+
return profile in KNOWN_REQUIREMENT_CONTEXT_PROFILES
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _source_locator_supported(source_locator: str | None) -> bool:
|
|
143
|
+
return source_locator is None or type(source_locator) is str
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _coverage_summary_consistent(result: RequirementContextCoverageSummary) -> bool:
|
|
147
|
+
return result.total_requirements >= len(result.missing_evidence_requirement_ids)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _requirements_with_evidence_link_type(
|
|
151
|
+
requirements: Sequence[RequirementInput],
|
|
152
|
+
link_type: RequirementEvidenceLinkType,
|
|
153
|
+
) -> int:
|
|
154
|
+
count = 0
|
|
155
|
+
for requirement in requirements:
|
|
156
|
+
if any(link.link_type == link_type for link in requirement.evidence_links):
|
|
157
|
+
count += 1
|
|
158
|
+
return max(0, count)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _missing_evidence_requirement_ids(requirements: Sequence[RequirementInput]) -> list[str]:
|
|
162
|
+
return [requirement.requirement_id for requirement in requirements if not requirement.evidence_links]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _invalid_requirement_diagnostic(
|
|
166
|
+
exc: ValidationError,
|
|
167
|
+
record: RequirementInput | Mapping[str, Any],
|
|
168
|
+
source_locator: str | None,
|
|
169
|
+
record_index: int,
|
|
170
|
+
) -> RequirementContextDiagnostic:
|
|
171
|
+
return RequirementContextDiagnostic(
|
|
172
|
+
severity=RequirementContextDiagnosticSeverity.ERROR,
|
|
173
|
+
code="invalid_requirement_input",
|
|
174
|
+
message=_validation_error_message(exc),
|
|
175
|
+
source_locator=_optional_text(source_locator),
|
|
176
|
+
requirement_id=_record_requirement_id(record),
|
|
177
|
+
record_index=record_index,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@beartype
|
|
182
|
+
@require(_all_records_supported, "records must be RequirementInput or mapping values")
|
|
183
|
+
@require(_source_locator_supported, "source_locator must be text when provided")
|
|
184
|
+
@ensure(_normalization_result_valid)
|
|
185
|
+
def normalize_requirement_records(
|
|
186
|
+
records: Sequence[RequirementInput | Mapping[str, Any]],
|
|
187
|
+
*,
|
|
188
|
+
source_locator: str | None = None,
|
|
189
|
+
) -> RequirementContextImportResult:
|
|
190
|
+
"""Normalize source-attributed requirement records with bounded diagnostics."""
|
|
191
|
+
normalized: list[RequirementInput] = []
|
|
192
|
+
diagnostics: list[RequirementContextDiagnostic] = []
|
|
193
|
+
|
|
194
|
+
for record_index, record in enumerate(records):
|
|
195
|
+
if isinstance(record, RequirementInput):
|
|
196
|
+
normalized.append(record)
|
|
197
|
+
continue
|
|
198
|
+
try:
|
|
199
|
+
normalized.append(RequirementInput.model_validate(record))
|
|
200
|
+
except ValidationError as exc:
|
|
201
|
+
diagnostics.append(_invalid_requirement_diagnostic(exc, record, source_locator, record_index))
|
|
202
|
+
|
|
203
|
+
return RequirementContextImportResult(requirements=normalized, diagnostics=diagnostics)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@beartype
|
|
207
|
+
@require(lambda bundle: isinstance(bundle, ProjectBundle), "bundle must be a ProjectBundle")
|
|
208
|
+
@require(lambda requirements: all(isinstance(record, RequirementInput) for record in requirements))
|
|
209
|
+
@ensure(lambda result: isinstance(result, ProjectBundle))
|
|
210
|
+
def attach_requirements_to_bundle(bundle: ProjectBundle, requirements: Sequence[RequirementInput]) -> ProjectBundle:
|
|
211
|
+
"""Store normalized requirements on the existing requirements.inputs extension."""
|
|
212
|
+
bundle.set_extension("requirements", "inputs", requirements_input_extension_payload(list(requirements)))
|
|
213
|
+
return bundle
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@beartype
|
|
217
|
+
@require(lambda bundle: isinstance(bundle, ProjectBundle), "bundle must be a ProjectBundle")
|
|
218
|
+
@ensure(lambda result: all(isinstance(record, RequirementInput) for record in result))
|
|
219
|
+
def load_requirements_from_bundle(bundle: ProjectBundle) -> list[RequirementInput]:
|
|
220
|
+
"""Load normalized requirements from the requirements.inputs extension."""
|
|
221
|
+
payload = bundle.get_extension("requirements", "inputs", default=None)
|
|
222
|
+
if payload is None:
|
|
223
|
+
return []
|
|
224
|
+
if not isinstance(payload, dict):
|
|
225
|
+
raise ValueError("requirements.inputs extension must be a mapping payload")
|
|
226
|
+
return load_requirements_input_extension(cast(dict[str, Any], payload))
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@beartype
|
|
230
|
+
@require(lambda requirements: all(isinstance(record, RequirementInput) for record in requirements))
|
|
231
|
+
@ensure(_coverage_summary_consistent)
|
|
232
|
+
def inspect_requirement_context_coverage(
|
|
233
|
+
requirements: Sequence[RequirementInput],
|
|
234
|
+
) -> RequirementContextCoverageSummary:
|
|
235
|
+
"""Return machine-readable coverage counts for normalized requirement inputs."""
|
|
236
|
+
requirement_list = list(requirements)
|
|
237
|
+
return RequirementContextCoverageSummary(
|
|
238
|
+
total_requirements=len(requirement_list),
|
|
239
|
+
with_business_rules=sum(bool(requirement.business_rules) for requirement in requirement_list),
|
|
240
|
+
with_constraints=sum(bool(requirement.constraints) for requirement in requirement_list),
|
|
241
|
+
with_evidence_links=sum(bool(requirement.evidence_links) for requirement in requirement_list),
|
|
242
|
+
with_architecture_links=_requirements_with_evidence_link_type(
|
|
243
|
+
requirement_list,
|
|
244
|
+
RequirementEvidenceLinkType.ARCHITECTURE,
|
|
245
|
+
),
|
|
246
|
+
with_code_links=_requirements_with_evidence_link_type(requirement_list, RequirementEvidenceLinkType.CODE),
|
|
247
|
+
with_test_links=_requirements_with_evidence_link_type(requirement_list, RequirementEvidenceLinkType.TEST),
|
|
248
|
+
missing_evidence_requirement_ids=_missing_evidence_requirement_ids(requirement_list),
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _missing_evidence_severity(profile: RequirementContextValidationProfile) -> str:
|
|
253
|
+
return "error" if profile in STRICT_REQUIREMENT_CONTEXT_PROFILES else "warning"
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _empty_requirements_report() -> ValidationReport:
|
|
257
|
+
return ValidationReport(
|
|
258
|
+
status="warnings",
|
|
259
|
+
violations=[
|
|
260
|
+
{
|
|
261
|
+
"severity": "warning",
|
|
262
|
+
"message": "No requirements.inputs records are attached to the bundle.",
|
|
263
|
+
"location": "requirements.inputs",
|
|
264
|
+
}
|
|
265
|
+
],
|
|
266
|
+
summary={"total_checks": 1, "passed": 0, "failed": 0, "warnings": 1},
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _missing_evidence_violations(
|
|
271
|
+
coverage: RequirementContextCoverageSummary,
|
|
272
|
+
*,
|
|
273
|
+
severity: str,
|
|
274
|
+
) -> list[dict[str, str]]:
|
|
275
|
+
return [
|
|
276
|
+
{
|
|
277
|
+
"severity": severity,
|
|
278
|
+
"message": "Requirement input has no downstream evidence links.",
|
|
279
|
+
"location": f"requirements.inputs[{requirement_id}].evidence_links",
|
|
280
|
+
}
|
|
281
|
+
for requirement_id in coverage.missing_evidence_requirement_ids
|
|
282
|
+
]
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _validation_status(failed: int, warnings: int) -> Literal["passed", "failed", "warnings"]:
|
|
286
|
+
if failed:
|
|
287
|
+
return "failed"
|
|
288
|
+
if warnings:
|
|
289
|
+
return "warnings"
|
|
290
|
+
return "passed"
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _validation_summary(
|
|
294
|
+
requirements: Sequence[RequirementInput],
|
|
295
|
+
coverage: RequirementContextCoverageSummary,
|
|
296
|
+
violations: Sequence[Mapping[str, str]],
|
|
297
|
+
) -> dict[str, int]:
|
|
298
|
+
failed = sum(violation["severity"] == "error" for violation in violations)
|
|
299
|
+
warnings = sum(violation["severity"] == "warning" for violation in violations)
|
|
300
|
+
return {
|
|
301
|
+
"total_checks": max(1, len(requirements) * 2),
|
|
302
|
+
"passed": len(requirements) + coverage.with_evidence_links,
|
|
303
|
+
"failed": failed,
|
|
304
|
+
"warnings": warnings,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@beartype
|
|
309
|
+
@require(lambda bundle: isinstance(bundle, ProjectBundle), "bundle must be a ProjectBundle")
|
|
310
|
+
@require(_profile_nonempty, "profile must not be empty")
|
|
311
|
+
@require(_profile_supported, "profile must be a supported requirements context validation profile")
|
|
312
|
+
@ensure(lambda result: isinstance(result, ValidationReport))
|
|
313
|
+
def validate_requirement_context(
|
|
314
|
+
bundle: ProjectBundle,
|
|
315
|
+
*,
|
|
316
|
+
profile: RequirementContextValidationProfile = "startup",
|
|
317
|
+
) -> ValidationReport:
|
|
318
|
+
"""Validate requirements context evidence usefulness for a ProjectBundle."""
|
|
319
|
+
requirements = load_requirements_from_bundle(bundle)
|
|
320
|
+
if not requirements:
|
|
321
|
+
return _empty_requirements_report()
|
|
322
|
+
|
|
323
|
+
coverage = inspect_requirement_context_coverage(requirements)
|
|
324
|
+
violations = _missing_evidence_violations(coverage, severity=_missing_evidence_severity(profile))
|
|
325
|
+
summary = _validation_summary(requirements, coverage, violations)
|
|
326
|
+
return ValidationReport(
|
|
327
|
+
status=_validation_status(summary["failed"], summary["warnings"]),
|
|
328
|
+
violations=violations,
|
|
329
|
+
summary=summary,
|
|
330
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/analyzers/contract_extractor.py
RENAMED
|
File without changes
|
{specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/analyzers/control_flow_analyzer.py
RENAMED
|
File without changes
|
|
File without changes
|
{specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/analyzers/relationship_mapper.py
RENAMED
|
File without changes
|
{specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/analyzers/requirement_extractor.py
RENAMED
|
File without changes
|
{specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/analyzers/test_pattern_extractor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/backlog/mappers/github_mapper.py
RENAMED
|
File without changes
|
{specfact_cli-0.49.1 → specfact_cli-0.50.1}/src/specfact_cli/backlog/mappers/template_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|