specfact-cli 0.46.9__tar.gz → 0.46.17__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.46.9 → specfact_cli-0.46.17}/PKG-INFO +1 -1
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/pyproject.toml +1 -1
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/__init__.py +1 -1
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/cli.py +104 -26
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/upgrade/module-package.yaml +3 -3
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/upgrade/src/commands.py +152 -87
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/module_discovery.py +29 -18
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/.gitignore +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/LICENSE +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/README.md +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/bundled-module-registry/index.json +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/keys/README.md +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/keys/module-signing-public.pem +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/templates/policies/kanban.yaml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/templates/policies/mixed.yaml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/templates/policies/safe.yaml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/templates/policies/scrum.yaml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/__main__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/adapters/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/adapters/ado.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/adapters/backlog_base.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/adapters/base.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/adapters/github.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/adapters/openspec.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/adapters/openspec_parser.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/adapters/registry.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/adapters/speckit.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/backlog/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/backlog/adapters/base.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/backlog/converter.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/backlog/filters.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/backlog/mappers/base.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/_bundle_shim.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/project_cmd.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/update.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/commands/validate.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/common/bundle_factory.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/contracts/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/contracts/crosshair_props.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/contracts/module_interface.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/generators/persona_exporter.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/groups/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/groups/codebase_group.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/groups/govern_group.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/groups/member_group.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/groups/project_group.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/groups/spec_group.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/backlog_item.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/capabilities.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/change.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/dor_config.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/module_package.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/project.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/models/validation.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/_bundle_import.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/init/module-package.yaml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/init/src/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/init/src/app.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/init/src/commands.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/init/src/first_run_selection.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/module_io_shim.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/module_registry/module-package.yaml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/module_registry/src/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/module_registry/src/app.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/module_registry/src/commands.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/upgrade/src/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/upgrade/src/app.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/alias_manager.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/bootstrap.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/bridge_registry.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/crypto_validator.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/custom_registries.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/dependency_resolver.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/extension_registry.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/help_cache.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/marketplace_client.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/metadata.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/module_availability.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/module_grouping.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/module_installer.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/module_lifecycle.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/module_packages.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/module_security.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/module_state.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/registry/registry.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/bridge_sync.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/bridge_sync_openspec_md_parse.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/bridge_sync_requirement_from_proposal.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/bridge_sync_requirement_helpers.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/bridge_sync_tasks_from_proposal.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/bridge_sync_what_changes_format.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/bridge_sync_write_openspec_from_proposal.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/templates/registry.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/auth_tokens.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/bundle_converters.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/code_change_detector.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/content_sanitizer.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/contract_predicates.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/env_manager.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/icontract_helpers.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/metadata.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/persona_ownership.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/project_artifact_write.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/startup_checks.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/terminal.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validation/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validation/command_audit.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/schema.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/models.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/versioning/analyzer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specfact-cli
|
|
3
|
-
Version: 0.46.
|
|
3
|
+
Version: 0.46.17
|
|
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
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specfact-cli"
|
|
7
|
-
version = "0.46.
|
|
7
|
+
version = "0.46.17"
|
|
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"
|
|
@@ -14,7 +14,7 @@ from collections.abc import Callable, Mapping
|
|
|
14
14
|
from dataclasses import dataclass
|
|
15
15
|
from datetime import datetime
|
|
16
16
|
from pathlib import Path
|
|
17
|
-
from typing import Annotated, Any, cast
|
|
17
|
+
from typing import Annotated, Any, NoReturn, cast
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
_DetectShellFn = Callable[..., tuple[str | None, str | None]]
|
|
@@ -532,6 +532,84 @@ def _lazy_delegate_cmd_name_ready(self: _LazyDelegateGroup) -> bool:
|
|
|
532
532
|
return len(self._lazy_cmd_name) > 0
|
|
533
533
|
|
|
534
534
|
|
|
535
|
+
def _args_request_help(args: tuple[str, ...] | list[str]) -> bool:
|
|
536
|
+
"""Return True when delegated args are asking only for command help."""
|
|
537
|
+
return any(arg in ("--help", "-h", "--help-advanced", "-ha") for arg in args)
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def _delegated_help_path(cmd_name: str, args: tuple[str, ...] | list[str]) -> str:
|
|
541
|
+
"""Build a stable command path for fallback help output."""
|
|
542
|
+
path_parts = [cmd_name]
|
|
543
|
+
for arg in args:
|
|
544
|
+
if arg.startswith("-"):
|
|
545
|
+
continue
|
|
546
|
+
path_parts.append(arg)
|
|
547
|
+
return " ".join(path_parts)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def _print_lazy_help_fallback(cmd_name: str, args: tuple[str, ...] | list[str]) -> None:
|
|
551
|
+
"""Print minimal help when Typer cannot materialize a command for a loaded bundle."""
|
|
552
|
+
command_path = _delegated_help_path(cmd_name, args)
|
|
553
|
+
get_configured_console().print(
|
|
554
|
+
f"[bold]{command_path}[/bold]\n\n"
|
|
555
|
+
"Help is available for this installed command path, but the command metadata could not be "
|
|
556
|
+
"materialized in this runtime. Reinstall the providing module or run the command without "
|
|
557
|
+
"`--help` to execute it."
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def _raise_lazy_delegate_click_exception(exc: Exception) -> NoReturn:
|
|
562
|
+
raise click.ClickException(str(exc)) from exc
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def _load_lazy_delegate_typer(cmd_name: str) -> typer.Typer:
|
|
566
|
+
resolved_name = resolve_command(cmd_name)
|
|
567
|
+
try:
|
|
568
|
+
return CommandRegistry.get_typer(resolved_name)
|
|
569
|
+
except ValueError as exc:
|
|
570
|
+
if cmd_name in KNOWN_BUNDLE_GROUP_OR_SHIM_NAMES:
|
|
571
|
+
_print_missing_bundle_command_help(cmd_name)
|
|
572
|
+
raise SystemExit(1) from None
|
|
573
|
+
_raise_lazy_delegate_click_exception(exc)
|
|
574
|
+
raise AssertionError("unreachable") from None
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
def _build_lazy_delegate_click_command(cmd_name: str, args: tuple[str, ...], real_typer: typer.Typer) -> click.Command:
|
|
578
|
+
from typer.main import get_command
|
|
579
|
+
|
|
580
|
+
try:
|
|
581
|
+
return get_command(real_typer)
|
|
582
|
+
except (RuntimeError, ValueError) as exc:
|
|
583
|
+
if _args_request_help(args):
|
|
584
|
+
_print_lazy_help_fallback(cmd_name, args)
|
|
585
|
+
raise SystemExit(0) from None
|
|
586
|
+
_raise_lazy_delegate_click_exception(exc)
|
|
587
|
+
raise AssertionError("unreachable") from None
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def _lazy_delegate_prog_name(ctx: click.Context, cmd_name: str) -> str:
|
|
591
|
+
parts: list[str] = []
|
|
592
|
+
parent = ctx.parent
|
|
593
|
+
while parent and getattr(parent, "command", None):
|
|
594
|
+
name = getattr(parent.command, "name", None)
|
|
595
|
+
if name and name != "__delegate__":
|
|
596
|
+
parts.append(name)
|
|
597
|
+
parent = getattr(parent, "parent", None)
|
|
598
|
+
if parts:
|
|
599
|
+
return " ".join(reversed(parts))
|
|
600
|
+
original_prog_name = ctx.meta.get("original_prog_name")
|
|
601
|
+
if isinstance(original_prog_name, str) and original_prog_name:
|
|
602
|
+
return original_prog_name
|
|
603
|
+
return cmd_name
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def _strip_redundant_single_command_arg(click_cmd: click.Command, args: tuple[str, ...]) -> list[str]:
|
|
607
|
+
args_list = list(args)
|
|
608
|
+
if not isinstance(click_cmd, click.Group) and args_list and args_list[0] == getattr(click_cmd, "name", None):
|
|
609
|
+
return args_list[1:]
|
|
610
|
+
return args_list
|
|
611
|
+
|
|
612
|
+
|
|
535
613
|
class _LazyDelegateGroup(click.Group):
|
|
536
614
|
"""Click Group that delegates all args to the real command (lazy-loaded)."""
|
|
537
615
|
|
|
@@ -544,6 +622,8 @@ class _LazyDelegateGroup(click.Group):
|
|
|
544
622
|
name=name or cmd_name,
|
|
545
623
|
help=help or help_str,
|
|
546
624
|
context_settings={"ignore_unknown_options": True},
|
|
625
|
+
invoke_without_command=True,
|
|
626
|
+
no_args_is_help=False,
|
|
547
627
|
)
|
|
548
628
|
self._lazy_cmd_name = cmd_name
|
|
549
629
|
self._lazy_help_str = help_str
|
|
@@ -553,31 +633,15 @@ class _LazyDelegateGroup(click.Group):
|
|
|
553
633
|
cmd_name = self._lazy_cmd_name
|
|
554
634
|
|
|
555
635
|
def _invoke(args: tuple[str, ...]) -> None:
|
|
556
|
-
from typer.main import get_command
|
|
557
|
-
|
|
558
636
|
ctx = click.get_current_context()
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
click_cmd = get_command(real_typer)
|
|
637
|
+
real_typer = _load_lazy_delegate_typer(cmd_name)
|
|
638
|
+
click_cmd = _build_lazy_delegate_click_command(cmd_name, args, real_typer)
|
|
562
639
|
# Build full prog name from root (e.g. "specfact sync") so usage shows "specfact sync bridge", not "sync sync bridge"
|
|
563
|
-
|
|
564
|
-
p = ctx.parent
|
|
565
|
-
while p and getattr(p, "command", None):
|
|
566
|
-
name = getattr(p.command, "name", None)
|
|
567
|
-
if name and name != "__delegate__":
|
|
568
|
-
parts.append(name)
|
|
569
|
-
p = getattr(p, "parent", None)
|
|
570
|
-
prog_name = " ".join(reversed(parts)) if parts else cmd_name
|
|
571
|
-
args_list = list(args)
|
|
640
|
+
prog_name = _lazy_delegate_prog_name(ctx, cmd_name)
|
|
572
641
|
# When the real app is a single command (e.g. drift has only "detect"), Typer
|
|
573
642
|
# builds a TyperCommand, not a Group. Then args are ["detect", "bundle", "--repo", ...]
|
|
574
643
|
# and the command expects ["bundle", "--repo", ...] (no leading "detect").
|
|
575
|
-
|
|
576
|
-
not isinstance(click_cmd, click.Group)
|
|
577
|
-
and args_list
|
|
578
|
-
and args_list[0] == getattr(click_cmd, "name", None)
|
|
579
|
-
):
|
|
580
|
-
args_list = args_list[1:]
|
|
644
|
+
args_list = _strip_redundant_single_command_arg(click_cmd, args)
|
|
581
645
|
exit_code = click_cmd.main(args=args_list, prog_name=prog_name, standalone_mode=False)
|
|
582
646
|
if exit_code and exit_code != 0:
|
|
583
647
|
raise SystemExit(exit_code)
|
|
@@ -590,6 +654,14 @@ class _LazyDelegateGroup(click.Group):
|
|
|
590
654
|
add_help_option=False, # Pass --help through to real Typer so "specfact backlog daily ado --help" shows correct usage
|
|
591
655
|
)
|
|
592
656
|
|
|
657
|
+
@require(lambda ctx: ctx is not None, "ctx must not be None")
|
|
658
|
+
@ensure(lambda result: result is None or isinstance(result, int), "result must be None or an exit code")
|
|
659
|
+
def invoke(self, ctx: click.Context) -> Any:
|
|
660
|
+
if ctx.invoked_subcommand is None and not ctx.args:
|
|
661
|
+
ctx.meta["original_prog_name"] = ctx.command_path
|
|
662
|
+
return self._delegate_cmd.main(args=[], prog_name=ctx.command_path, standalone_mode=False)
|
|
663
|
+
return super().invoke(ctx)
|
|
664
|
+
|
|
593
665
|
@require(_lazy_delegate_cmd_name_ready, "lazy command name must be set")
|
|
594
666
|
@ensure(lambda result: isinstance(result, tuple) and len(result) == 3, "result must be a 3-tuple")
|
|
595
667
|
def resolve_command(
|
|
@@ -597,7 +669,7 @@ class _LazyDelegateGroup(click.Group):
|
|
|
597
669
|
) -> tuple[str | None, click.Command | None, list[str]]:
|
|
598
670
|
# Pass through all args to the delegate so "plan init bundle" becomes args for the real plan Typer.
|
|
599
671
|
if not args:
|
|
600
|
-
return
|
|
672
|
+
return self._delegate_cmd.name, self._delegate_cmd, []
|
|
601
673
|
return self._delegate_cmd.name, self._delegate_cmd, list(args)
|
|
602
674
|
|
|
603
675
|
@ensure(lambda result: isinstance(result, list), "result must be a list of command names")
|
|
@@ -621,8 +693,11 @@ class _LazyDelegateGroup(click.Group):
|
|
|
621
693
|
from typer.main import get_command
|
|
622
694
|
|
|
623
695
|
resolved_name = resolve_command(self._lazy_cmd_name)
|
|
624
|
-
|
|
625
|
-
|
|
696
|
+
try:
|
|
697
|
+
real_typer = CommandRegistry.get_typer(resolved_name)
|
|
698
|
+
click_cmd = get_command(real_typer)
|
|
699
|
+
except (RuntimeError, ValueError):
|
|
700
|
+
return None
|
|
626
701
|
if isinstance(click_cmd, click.Group):
|
|
627
702
|
return click_cmd
|
|
628
703
|
return None
|
|
@@ -633,8 +708,11 @@ class _LazyDelegateGroup(click.Group):
|
|
|
633
708
|
from typer.main import get_command
|
|
634
709
|
|
|
635
710
|
resolved_name = resolve_command(self._lazy_cmd_name)
|
|
636
|
-
|
|
637
|
-
|
|
711
|
+
try:
|
|
712
|
+
real_typer = CommandRegistry.get_typer(resolved_name)
|
|
713
|
+
click_cmd = get_command(real_typer)
|
|
714
|
+
except (RuntimeError, ValueError):
|
|
715
|
+
return
|
|
638
716
|
prog_name = (
|
|
639
717
|
f"{ctx.parent.command.name} {self._lazy_cmd_name}"
|
|
640
718
|
if ctx.parent and ctx.parent.command
|
{specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/upgrade/module-package.yaml
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name: upgrade
|
|
2
|
-
version: 0.1.
|
|
2
|
+
version: 0.1.15
|
|
3
3
|
commands:
|
|
4
4
|
- upgrade
|
|
5
5
|
category: core
|
|
@@ -17,5 +17,5 @@ publisher:
|
|
|
17
17
|
description: Check and apply SpecFact CLI version upgrades.
|
|
18
18
|
license: Apache-2.0
|
|
19
19
|
integrity:
|
|
20
|
-
checksum: sha256:
|
|
21
|
-
signature:
|
|
20
|
+
checksum: sha256:134da8c48f8be96e9a4337a7cc2076bbf8b19e0d3f69161636bcec050f97e4c1
|
|
21
|
+
signature: CK8Gq+/d0zWlU1Lo1I6EcgPiT3FzVsXj+CXFn7+zUnpXRdpYZxccLLZqh1Hvl2IPv3Y4yecVlOEcWZKtn0tJBg==
|
{specfact_cli-0.46.9 → specfact_cli-0.46.17}/src/specfact_cli/modules/upgrade/src/commands.py
RENAMED
|
@@ -9,6 +9,8 @@ CrossHair: skip (subprocess-based installation checks are intentionally side-eff
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
import os
|
|
13
|
+
import shlex
|
|
12
14
|
import subprocess
|
|
13
15
|
import sys
|
|
14
16
|
from datetime import UTC
|
|
@@ -45,7 +47,7 @@ validate_bundle = module_io_shim.validate_bundle
|
|
|
45
47
|
class InstallationMethod(NamedTuple):
|
|
46
48
|
"""Installation method information."""
|
|
47
49
|
|
|
48
|
-
method: str # "pip", "uvx", "pipx", or "unknown"
|
|
50
|
+
method: str # "pip", "uv", "uvx", "pipx", or "unknown"
|
|
49
51
|
command: str # Command to run for update
|
|
50
52
|
location: str | None # Installation location if known
|
|
51
53
|
|
|
@@ -53,39 +55,102 @@ class InstallationMethod(NamedTuple):
|
|
|
53
55
|
@beartype
|
|
54
56
|
@ensure(lambda result: isinstance(result, InstallationMethod), "Must return InstallationMethod")
|
|
55
57
|
def detect_installation_method() -> InstallationMethod:
|
|
56
|
-
"""
|
|
57
|
-
|
|
58
|
+
"""Detect how SpecFact CLI was installed."""
|
|
59
|
+
executable_path = str(Path(sys.executable))
|
|
60
|
+
|
|
61
|
+
uvx_method = _detect_uvx_installation(executable_path)
|
|
62
|
+
if uvx_method:
|
|
63
|
+
return uvx_method
|
|
64
|
+
|
|
65
|
+
uv_method = _detect_uv_project_installation(executable_path)
|
|
66
|
+
if uv_method:
|
|
67
|
+
return uv_method
|
|
68
|
+
|
|
69
|
+
pipx_method = _detect_pipx_installation()
|
|
70
|
+
if pipx_method:
|
|
71
|
+
return pipx_method
|
|
72
|
+
|
|
73
|
+
uv_method = _detect_uv_tool_installation()
|
|
74
|
+
if uv_method:
|
|
75
|
+
return uv_method
|
|
76
|
+
|
|
77
|
+
pip_method = _detect_pip_installation()
|
|
78
|
+
if pip_method:
|
|
79
|
+
return pip_method
|
|
80
|
+
|
|
81
|
+
quoted_executable = shlex.quote(sys.executable)
|
|
82
|
+
return InstallationMethod(
|
|
83
|
+
method="pip",
|
|
84
|
+
command=f"{quoted_executable} -m pip install --upgrade specfact-cli",
|
|
85
|
+
location=None,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _detect_uvx_installation(executable_path: str) -> InstallationMethod | None:
|
|
90
|
+
if _path_segments_contain_uvx(sys.argv[0]) or _path_segments_contain_uvx(executable_path):
|
|
91
|
+
return InstallationMethod(method="uvx", command="uvx --from specfact-cli specfact --version", location=None)
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _path_segments_contain_uvx(path_value: str) -> bool:
|
|
96
|
+
segments = [segment for segment in path_value.replace("\\", "/").split("/") if segment]
|
|
97
|
+
return any(_is_uvx_executable_name(segment) for segment in segments)
|
|
58
98
|
|
|
59
|
-
Returns:
|
|
60
|
-
InstallationMethod with detected method and update command
|
|
61
|
-
"""
|
|
62
|
-
# Check if running via uvx
|
|
63
|
-
if "uvx" in sys.argv[0] or "uvx" in str(Path(sys.executable)):
|
|
64
|
-
return InstallationMethod(
|
|
65
|
-
method="uvx",
|
|
66
|
-
command="uvx --from specfact-cli specfact --version",
|
|
67
|
-
location=None,
|
|
68
|
-
)
|
|
69
99
|
|
|
70
|
-
|
|
100
|
+
def _is_uvx_executable_name(segment: str) -> bool:
|
|
101
|
+
lower_segment = segment.lower()
|
|
102
|
+
for suffix in (".exe", ".cmd", ".bat"):
|
|
103
|
+
if lower_segment.endswith(suffix):
|
|
104
|
+
lower_segment = lower_segment[: -len(suffix)]
|
|
105
|
+
break
|
|
106
|
+
return lower_segment == "uvx"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _detect_uv_project_installation(executable_path: str) -> InstallationMethod | None:
|
|
110
|
+
uv_project_env = os.environ.get("UV_PROJECT_ENVIRONMENT", "").strip()
|
|
111
|
+
if uv_project_env:
|
|
112
|
+
try:
|
|
113
|
+
uv_root = Path(uv_project_env).resolve()
|
|
114
|
+
executable = Path(executable_path).resolve()
|
|
115
|
+
except OSError:
|
|
116
|
+
return None
|
|
117
|
+
if executable == uv_root or uv_root in executable.parents:
|
|
118
|
+
executable_text = str(executable)
|
|
119
|
+
return InstallationMethod(
|
|
120
|
+
method="uv",
|
|
121
|
+
command=f"uv pip install --python {shlex.quote(executable_text)} --upgrade specfact-cli",
|
|
122
|
+
location=executable_text,
|
|
123
|
+
)
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _detect_uv_tool_installation() -> InstallationMethod | None:
|
|
71
128
|
try:
|
|
72
129
|
result = subprocess.run(
|
|
73
|
-
["
|
|
130
|
+
["uv", "tool", "list"],
|
|
74
131
|
capture_output=True,
|
|
75
132
|
text=True,
|
|
76
133
|
timeout=5,
|
|
77
134
|
check=False,
|
|
78
135
|
)
|
|
79
|
-
if "specfact-cli" in result.stdout:
|
|
80
|
-
return InstallationMethod(
|
|
81
|
-
method="pipx",
|
|
82
|
-
command="pipx upgrade specfact-cli",
|
|
83
|
-
location=None,
|
|
84
|
-
)
|
|
85
136
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
86
|
-
|
|
137
|
+
return None
|
|
138
|
+
if "specfact-cli" in result.stdout:
|
|
139
|
+
return InstallationMethod(method="uv", command="uv tool upgrade specfact-cli", location=None)
|
|
140
|
+
return None
|
|
141
|
+
|
|
87
142
|
|
|
88
|
-
|
|
143
|
+
def _detect_pipx_installation() -> InstallationMethod | None:
|
|
144
|
+
try:
|
|
145
|
+
result = subprocess.run(["pipx", "list"], capture_output=True, text=True, timeout=5, check=False)
|
|
146
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
147
|
+
return None
|
|
148
|
+
if "specfact-cli" in result.stdout:
|
|
149
|
+
return InstallationMethod(method="pipx", command="pipx upgrade specfact-cli", location=None)
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _detect_pip_installation() -> InstallationMethod | None:
|
|
89
154
|
try:
|
|
90
155
|
result = subprocess.run(
|
|
91
156
|
[sys.executable, "-m", "pip", "show", "specfact-cli"],
|
|
@@ -94,95 +159,88 @@ def detect_installation_method() -> InstallationMethod:
|
|
|
94
159
|
timeout=5,
|
|
95
160
|
check=False,
|
|
96
161
|
)
|
|
97
|
-
if result.returncode == 0:
|
|
98
|
-
# Parse location from output
|
|
99
|
-
location = None
|
|
100
|
-
for line in result.stdout.splitlines():
|
|
101
|
-
if line.startswith("Location:"):
|
|
102
|
-
location = line.split(":", 1)[1].strip()
|
|
103
|
-
break
|
|
104
|
-
|
|
105
|
-
return InstallationMethod(
|
|
106
|
-
method="pip",
|
|
107
|
-
command=f"{sys.executable} -m pip install --upgrade specfact-cli",
|
|
108
|
-
location=location,
|
|
109
|
-
)
|
|
110
162
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
111
|
-
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
if result.returncode != 0:
|
|
166
|
+
return None
|
|
112
167
|
|
|
113
|
-
|
|
168
|
+
location = None
|
|
169
|
+
for line in result.stdout.splitlines():
|
|
170
|
+
if line.startswith("Location:"):
|
|
171
|
+
location = line.split(":", 1)[1].strip()
|
|
172
|
+
break
|
|
173
|
+
quoted_executable = shlex.quote(sys.executable)
|
|
114
174
|
return InstallationMethod(
|
|
115
175
|
method="pip",
|
|
116
|
-
command="pip install --upgrade specfact-cli",
|
|
117
|
-
location=
|
|
176
|
+
command=f"{quoted_executable} -m pip install --upgrade specfact-cli",
|
|
177
|
+
location=location,
|
|
118
178
|
)
|
|
119
179
|
|
|
120
180
|
|
|
121
181
|
@beartype
|
|
122
182
|
@ensure(lambda result: isinstance(result, bool), "Must return bool")
|
|
123
183
|
def install_update(method: InstallationMethod, yes: bool = False) -> bool:
|
|
124
|
-
"""
|
|
125
|
-
Install update using the detected installation method.
|
|
126
|
-
|
|
127
|
-
Args:
|
|
128
|
-
method: InstallationMethod with update command
|
|
129
|
-
yes: If True, skip confirmation prompt
|
|
130
|
-
|
|
131
|
-
Returns:
|
|
132
|
-
True if update was successful, False otherwise
|
|
133
|
-
"""
|
|
184
|
+
"""Install update using the detected installation method."""
|
|
134
185
|
if not yes:
|
|
135
186
|
console.print(f"[yellow]This will update SpecFact CLI using:[/yellow] [cyan]{method.command}[/cyan]")
|
|
136
187
|
if not Confirm.ask("Continue?", default=True):
|
|
137
188
|
console.print("[dim]Update cancelled[/dim]")
|
|
138
189
|
return False
|
|
139
190
|
|
|
140
|
-
|
|
141
|
-
console.print(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
elif method.method == "pip":
|
|
146
|
-
# Handle both formats: "python -m pip" and "pip"
|
|
147
|
-
if " -m pip" in method.command:
|
|
148
|
-
parts = method.command.split()
|
|
149
|
-
cmd = [parts[0], "-m", "pip", "install", "--upgrade", "specfact-cli"]
|
|
150
|
-
else:
|
|
151
|
-
cmd = ["pip", "install", "--upgrade", "specfact-cli"]
|
|
152
|
-
else:
|
|
153
|
-
# uvx - just inform user
|
|
154
|
-
console.print(
|
|
155
|
-
"[yellow]uvx automatically uses the latest version.[/yellow]\n"
|
|
156
|
-
"[dim]No update needed. If you want to force a refresh, run:[/dim]\n"
|
|
157
|
-
"[cyan]uvx --from specfact-cli@latest specfact --version[/cyan]"
|
|
158
|
-
)
|
|
159
|
-
return True
|
|
160
|
-
|
|
161
|
-
result = subprocess.run(
|
|
162
|
-
cmd,
|
|
163
|
-
check=False,
|
|
164
|
-
timeout=300, # 5 minute timeout
|
|
191
|
+
if method.method == "uvx":
|
|
192
|
+
console.print(
|
|
193
|
+
"[yellow]uvx automatically uses the latest version.[/yellow]\n"
|
|
194
|
+
"[dim]No update needed. If you want to force a refresh, run:[/dim]\n"
|
|
195
|
+
"[cyan]uvx --from specfact-cli@latest specfact --version[/cyan]"
|
|
165
196
|
)
|
|
197
|
+
return True
|
|
166
198
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
from datetime import datetime
|
|
171
|
-
|
|
172
|
-
update_metadata(
|
|
173
|
-
last_checked_version=__version__,
|
|
174
|
-
last_version_check_timestamp=datetime.now(UTC).isoformat(),
|
|
175
|
-
)
|
|
176
|
-
return True
|
|
177
|
-
console.print(f"[red]✗ Update failed with exit code {result.returncode}[/red]")
|
|
199
|
+
command = _build_upgrade_command(method)
|
|
200
|
+
if command is None:
|
|
201
|
+
console.print(f"[red]✗ Unsupported installation method: {method.method}[/red]")
|
|
178
202
|
return False
|
|
179
203
|
|
|
204
|
+
return _execute_upgrade_command(command)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _build_upgrade_command(method: InstallationMethod) -> list[str] | None:
|
|
208
|
+
if method.method == "pipx":
|
|
209
|
+
return ["pipx", "upgrade", "specfact-cli"]
|
|
210
|
+
if method.method == "uv":
|
|
211
|
+
if "uv tool" in method.command:
|
|
212
|
+
return ["uv", "tool", "upgrade", "specfact-cli"]
|
|
213
|
+
python_target = method.location or sys.executable
|
|
214
|
+
return ["uv", "pip", "install", "--python", python_target, "--upgrade", "specfact-cli"]
|
|
215
|
+
if method.method == "pip":
|
|
216
|
+
parts = shlex.split(method.command)
|
|
217
|
+
if len(parts) >= 3 and parts[1:3] == ["-m", "pip"]:
|
|
218
|
+
return [parts[0], "-m", "pip", "install", "--upgrade", "specfact-cli"]
|
|
219
|
+
return ["pip", "install", "--upgrade", "specfact-cli"]
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _execute_upgrade_command(command: list[str]) -> bool:
|
|
224
|
+
try:
|
|
225
|
+
console.print("[cyan]Updating SpecFact CLI...[/cyan]")
|
|
226
|
+
result = subprocess.run(command, check=False, timeout=300)
|
|
180
227
|
except subprocess.TimeoutExpired:
|
|
181
228
|
console.print("[red]✗ Update timed out (exceeded 5 minutes)[/red]")
|
|
182
229
|
return False
|
|
183
|
-
except
|
|
230
|
+
except OSError as e:
|
|
184
231
|
console.print(f"[red]✗ Update failed: {e}[/red]")
|
|
185
232
|
return False
|
|
233
|
+
if result.returncode != 0:
|
|
234
|
+
console.print(f"[red]✗ Update failed with exit code {result.returncode}[/red]")
|
|
235
|
+
return False
|
|
236
|
+
console.print("[green]✓ Update successful![/green]")
|
|
237
|
+
from datetime import datetime
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
update_metadata(last_checked_version=__version__, last_version_check_timestamp=datetime.now(UTC).isoformat())
|
|
241
|
+
except (OSError, TypeError) as exc:
|
|
242
|
+
console.print(f"[yellow]Update succeeded, but metadata update failed: {exc}[/yellow]")
|
|
243
|
+
return True
|
|
186
244
|
|
|
187
245
|
|
|
188
246
|
def _upgrade_log_started(check_only: bool, yes: bool) -> None:
|
|
@@ -246,6 +304,13 @@ def _upgrade_render_update_panel(version_result: Any) -> None:
|
|
|
246
304
|
def _upgrade_install_or_check_only(version_result: Any, check_only: bool, yes: bool) -> None:
|
|
247
305
|
if check_only:
|
|
248
306
|
method = detect_installation_method()
|
|
307
|
+
if method.method == "uvx":
|
|
308
|
+
console.print(
|
|
309
|
+
"[yellow]uvx automatically uses the latest version.[/yellow]\n"
|
|
310
|
+
"[dim]No update needed. If you want to force a refresh, run:[/dim]\n"
|
|
311
|
+
"[cyan]uvx --from specfact-cli@latest specfact --version[/cyan]"
|
|
312
|
+
)
|
|
313
|
+
return
|
|
249
314
|
console.print(f"\n[yellow]To upgrade, run:[/yellow] [cyan]{method.command}[/cyan]")
|
|
250
315
|
console.print("[dim]Or run:[/dim] [cyan]specfact upgrade --yes[/cyan]")
|
|
251
316
|
return
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import os
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Any
|
|
@@ -48,18 +49,18 @@ class _DiscoveryMergeState:
|
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
def _resolve_include_legacy_roots(
|
|
51
|
-
|
|
52
|
-
builtin_root: Path | None,
|
|
53
|
-
user_root: Path | None,
|
|
54
|
-
marketplace_root: Path | None,
|
|
55
|
-
custom_root: Path | None,
|
|
56
|
-
project_base_path: Path | None = None,
|
|
52
|
+
options: _DiscoveryRootOptions,
|
|
57
53
|
) -> bool:
|
|
58
|
-
if include_legacy_roots is not None:
|
|
59
|
-
return include_legacy_roots
|
|
60
|
-
if project_base_path is not None:
|
|
54
|
+
if options.include_legacy_roots is not None:
|
|
55
|
+
return options.include_legacy_roots
|
|
56
|
+
if options.project_base_path is not None:
|
|
61
57
|
return False
|
|
62
|
-
return
|
|
58
|
+
return (
|
|
59
|
+
options.builtin_root is None
|
|
60
|
+
and options.user_root is None
|
|
61
|
+
and options.marketplace_root is None
|
|
62
|
+
and options.custom_root is None
|
|
63
|
+
)
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
def _append_legacy_module_roots(roots: list[tuple[str, Path]]) -> None:
|
|
@@ -74,6 +75,22 @@ def _append_legacy_module_roots(roots: list[tuple[str, Path]]) -> None:
|
|
|
74
75
|
roots.append(("custom", extra_root))
|
|
75
76
|
|
|
76
77
|
|
|
78
|
+
def _append_explicit_module_roots(roots: list[tuple[str, Path]]) -> None:
|
|
79
|
+
seen_root_paths = {path.resolve() for _source, path in roots}
|
|
80
|
+
for raw_root in os.environ.get("SPECFACT_MODULES_ROOTS", "").split(os.pathsep):
|
|
81
|
+
candidate = raw_root.strip()
|
|
82
|
+
if not candidate:
|
|
83
|
+
continue
|
|
84
|
+
path = Path(candidate).expanduser()
|
|
85
|
+
if not path.exists():
|
|
86
|
+
continue
|
|
87
|
+
resolved = path.resolve()
|
|
88
|
+
if resolved in seen_root_paths:
|
|
89
|
+
continue
|
|
90
|
+
seen_root_paths.add(resolved)
|
|
91
|
+
roots.append(("custom", path))
|
|
92
|
+
|
|
93
|
+
|
|
77
94
|
def _discovery_root_list(options: _DiscoveryRootOptions) -> list[tuple[str, Path]]:
|
|
78
95
|
from specfact_cli.registry.module_packages import get_modules_root, get_workspace_modules_root
|
|
79
96
|
|
|
@@ -93,6 +110,7 @@ def _discovery_root_list(options: _DiscoveryRootOptions) -> list[tuple[str, Path
|
|
|
93
110
|
|
|
94
111
|
if effective_project_root is not None and not project_matches_user_root:
|
|
95
112
|
roots.append(("project", effective_project_root))
|
|
113
|
+
_append_explicit_module_roots(roots)
|
|
96
114
|
roots.extend(
|
|
97
115
|
[
|
|
98
116
|
("user", effective_user_root),
|
|
@@ -101,14 +119,7 @@ def _discovery_root_list(options: _DiscoveryRootOptions) -> list[tuple[str, Path
|
|
|
101
119
|
]
|
|
102
120
|
)
|
|
103
121
|
|
|
104
|
-
legacy = _resolve_include_legacy_roots(
|
|
105
|
-
options.include_legacy_roots,
|
|
106
|
-
options.builtin_root,
|
|
107
|
-
options.user_root,
|
|
108
|
-
options.marketplace_root,
|
|
109
|
-
options.custom_root,
|
|
110
|
-
options.project_base_path,
|
|
111
|
-
)
|
|
122
|
+
legacy = _resolve_include_legacy_roots(options)
|
|
112
123
|
if legacy:
|
|
113
124
|
_append_legacy_module_roots(roots)
|
|
114
125
|
return roots
|
|
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.46.9 → specfact_cli-0.46.17}/resources/templates/persona/product-owner.md.j2
RENAMED
|
File without changes
|