specfact-cli 0.46.19__tar.gz → 0.46.28__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.19 → specfact_cli-0.46.28}/PKG-INFO +8 -4
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/README.md +2 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/pyproject.toml +8 -6
- specfact_cli-0.46.28/resources/bundled-module-registry/index.json +26 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/__init__.py +1 -1
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/adapters/ado.py +13 -11
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/adapters/github.py +21 -17
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/cli.py +2 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/project.py +7 -1
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/module_registry/module-package.yaml +3 -3
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/module_registry/src/commands.py +107 -1
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/upgrade/module-package.yaml +3 -3
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/upgrade/src/commands.py +56 -2
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/module_installer.py +91 -17
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/module_packages.py +110 -7
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/module_state.py +11 -2
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/bridge_sync.py +2 -2
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/yaml_utils.py +23 -9
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/change_proposal_integration.py +3 -3
- specfact_cli-0.46.19/resources/bundled-module-registry/index.json +0 -20
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/.gitignore +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/LICENSE +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/keys/README.md +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/keys/module-signing-public.pem +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/templates/policies/kanban.yaml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/templates/policies/mixed.yaml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/templates/policies/safe.yaml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/templates/policies/scrum.yaml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/__main__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/adapters/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/adapters/backlog_base.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/adapters/base.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/adapters/openspec.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/adapters/openspec_parser.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/adapters/registry.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/adapters/speckit.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/backlog/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/backlog/adapters/base.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/backlog/converter.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/backlog/filters.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/backlog/mappers/base.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/_bundle_shim.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/project_cmd.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/update.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/commands/validate.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/common/bundle_factory.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/contracts/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/contracts/crosshair_props.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/contracts/module_interface.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/generators/persona_exporter.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/groups/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/groups/codebase_group.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/groups/govern_group.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/groups/member_group.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/groups/project_group.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/groups/spec_group.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/backlog_item.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/capabilities.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/change.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/dor_config.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/module_package.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/models/validation.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/_bundle_import.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/init/module-package.yaml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/init/src/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/init/src/app.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/init/src/commands.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/init/src/first_run_selection.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/module_io_shim.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/module_registry/src/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/module_registry/src/app.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/upgrade/src/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/upgrade/src/app.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/alias_manager.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/bootstrap.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/bridge_registry.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/crypto_validator.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/custom_registries.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/dependency_resolver.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/extension_registry.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/help_cache.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/marketplace_client.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/metadata.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/module_availability.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/module_discovery.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/module_grouping.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/module_lifecycle.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/module_security.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/registry/registry.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/bridge_sync_openspec_md_parse.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/bridge_sync_requirement_from_proposal.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/bridge_sync_requirement_helpers.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/bridge_sync_tasks_from_proposal.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/bridge_sync_what_changes_format.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/bridge_sync_write_openspec_from_proposal.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/templates/registry.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/auth_tokens.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/bundle_converters.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/code_change_detector.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/content_sanitizer.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/contract_predicates.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/env_manager.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/icontract_helpers.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/metadata.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/persona_ownership.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/project_artifact_write.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/startup_checks.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/utils/terminal.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validation/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validation/command_audit.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/schema.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/models.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.46.19 → specfact_cli-0.46.28}/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.28
|
|
4
4
|
Summary: The swiss knife CLI for agile DevOps teams. Keep backlog, specs, tests, and code in sync with validation and contract enforcement for new projects and long-lived codebases.
|
|
5
5
|
Project-URL: Homepage, https://github.com/nold-ai/specfact-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/nold-ai/specfact-cli.git
|
|
@@ -224,6 +224,7 @@ Classifier: Topic :: Software Development :: Testing
|
|
|
224
224
|
Requires-Python: >=3.11
|
|
225
225
|
Requires-Dist: azure-identity>=1.17.1
|
|
226
226
|
Requires-Dist: beartype>=0.22.4
|
|
227
|
+
Requires-Dist: click<8.2,>=8.1.8
|
|
227
228
|
Requires-Dist: commentjson>=0.9.0
|
|
228
229
|
Requires-Dist: cryptography>=43.0.0
|
|
229
230
|
Requires-Dist: gitpython>=3.1.45
|
|
@@ -232,14 +233,12 @@ Requires-Dist: icontract>=2.7.1
|
|
|
232
233
|
Requires-Dist: jinja2>=3.1.6
|
|
233
234
|
Requires-Dist: jsonschema>=4.23.0
|
|
234
235
|
Requires-Dist: networkx>=3.4.2
|
|
235
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27.0
|
|
236
|
-
Requires-Dist: opentelemetry-sdk>=1.27.0
|
|
237
236
|
Requires-Dist: packaging>=24.0
|
|
238
237
|
Requires-Dist: pydantic>=2.12.3
|
|
239
238
|
Requires-Dist: pyyaml>=6.0.3
|
|
240
239
|
Requires-Dist: questionary>=2.0.1
|
|
241
240
|
Requires-Dist: requests>=2.32.3
|
|
242
|
-
Requires-Dist: rich<
|
|
241
|
+
Requires-Dist: rich<16.0.0,>=13.5.2
|
|
243
242
|
Requires-Dist: ruamel-yaml>=0.18.16
|
|
244
243
|
Requires-Dist: typer<0.24,>=0.20.0
|
|
245
244
|
Requires-Dist: typing-extensions>=4.15.0
|
|
@@ -277,6 +276,9 @@ Requires-Dist: graphviz>=0.20.1; extra == 'enhanced-analysis'
|
|
|
277
276
|
Requires-Dist: pycg==0.0.7; extra == 'enhanced-analysis'
|
|
278
277
|
Provides-Extra: scanning
|
|
279
278
|
Requires-Dist: semgrep>=1.144.0; extra == 'scanning'
|
|
279
|
+
Provides-Extra: telemetry
|
|
280
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27.0; extra == 'telemetry'
|
|
281
|
+
Requires-Dist: opentelemetry-sdk>=1.27.0; extra == 'telemetry'
|
|
280
282
|
Description-Content-Type: text/markdown
|
|
281
283
|
|
|
282
284
|
# SpecFact CLI
|
|
@@ -340,6 +342,8 @@ specfact code review run --path . --scope full
|
|
|
340
342
|
|
|
341
343
|
The sample output comes from a pinned capture against `nold-ai/specfact-demo-repo`. Reproduce it with `docs/_support/readme-first-contact/capture-readme-output.sh`; capture metadata lives alongside the raw logs in `docs/_support/readme-first-contact/sample-output/`.
|
|
342
344
|
|
|
345
|
+
The Code Review bundle also reports `ai_bloat` advisories for code shapes that AI-assisted coding often amplifies, such as redundant wrappers, passthrough lambdas, identity `try`/`except` blocks, and avoidable intermediate lists. These findings are advisory, score-neutral, and not AI-authorship detection. Use the generated `.specfact/code-review.json` report with the Project bundle's `/specfact.08-simplify` IDE prompt to review each cleanup before accepting it. See the [AI bloat quickstart](https://modules.specfact.io/quickstart-ai-bloat/) on the modules docs site.
|
|
346
|
+
|
|
343
347
|
## What SpecFact does
|
|
344
348
|
|
|
345
349
|
- **Reviews AI-assisted changes deterministically** — compare code against contracts, clean-code rules, and policy gates
|
|
@@ -59,6 +59,8 @@ specfact code review run --path . --scope full
|
|
|
59
59
|
|
|
60
60
|
The sample output comes from a pinned capture against `nold-ai/specfact-demo-repo`. Reproduce it with `docs/_support/readme-first-contact/capture-readme-output.sh`; capture metadata lives alongside the raw logs in `docs/_support/readme-first-contact/sample-output/`.
|
|
61
61
|
|
|
62
|
+
The Code Review bundle also reports `ai_bloat` advisories for code shapes that AI-assisted coding often amplifies, such as redundant wrappers, passthrough lambdas, identity `try`/`except` blocks, and avoidable intermediate lists. These findings are advisory, score-neutral, and not AI-authorship detection. Use the generated `.specfact/code-review.json` report with the Project bundle's `/specfact.08-simplify` IDE prompt to review each cleanup before accepting it. See the [AI bloat quickstart](https://modules.specfact.io/quickstart-ai-bloat/) on the modules docs site.
|
|
63
|
+
|
|
62
64
|
## What SpecFact does
|
|
63
65
|
|
|
64
66
|
- **Reviews AI-assisted changes deterministically** — compare code against contracts, clean-code rules, and policy gates
|
|
@@ -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.28"
|
|
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"
|
|
@@ -65,8 +65,9 @@ dependencies = [
|
|
|
65
65
|
|
|
66
66
|
# CLI framework
|
|
67
67
|
# Typer 0.24+ requires click>=8.2.1 (generic Choice[...]); dev semgrep pins click~=8.1.8 — cap Typer.
|
|
68
|
+
"click>=8.1.8,<8.2",
|
|
68
69
|
"typer>=0.20.0,<0.24",
|
|
69
|
-
"rich>=13.5.2,<
|
|
70
|
+
"rich>=13.5.2,<16.0.0", # Semgrep allows rich>=13.5.2; keep below next major.
|
|
70
71
|
"questionary>=2.0.1", # Interactive prompts with arrow key navigation
|
|
71
72
|
|
|
72
73
|
# Template engine
|
|
@@ -94,10 +95,6 @@ dependencies = [
|
|
|
94
95
|
|
|
95
96
|
# File system watching
|
|
96
97
|
"watchdog>=6.0.0",
|
|
97
|
-
|
|
98
|
-
# Telemetry (opt-in; only active when SPECFACT_TELEMETRY_OPT_IN=true)
|
|
99
|
-
"opentelemetry-sdk>=1.27.0",
|
|
100
|
-
"opentelemetry-exporter-otlp-proto-http>=1.27.0",
|
|
101
98
|
]
|
|
102
99
|
|
|
103
100
|
[project.optional-dependencies]
|
|
@@ -106,6 +103,11 @@ contracts = [
|
|
|
106
103
|
"hypothesis>=6.142.4",
|
|
107
104
|
]
|
|
108
105
|
|
|
106
|
+
telemetry = [
|
|
107
|
+
"opentelemetry-sdk>=1.27.0",
|
|
108
|
+
"opentelemetry-exporter-otlp-proto-http>=1.27.0",
|
|
109
|
+
]
|
|
110
|
+
|
|
109
111
|
dev = [
|
|
110
112
|
"setuptools>=69.0.0,<82",
|
|
111
113
|
"pytest>=8.4.2",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"modules": [
|
|
3
|
+
{
|
|
4
|
+
"checksum_sha256": "ce82ea7d2a48dfa8b16ad48599ec6daa922d6aa1a61c242da1143366d5d53ec4",
|
|
5
|
+
"download_url": "https://github.com/nold-ai/specfact-cli/releases/download/init-0.1.33.tar.gz",
|
|
6
|
+
"id": "init",
|
|
7
|
+
"latest_version": "0.1.33"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"checksum_sha256": "49c5bcbef6ce4aba3302b8209d3641cc34430f3fe4754423d61428557f8c641d",
|
|
11
|
+
"download_url": "https://github.com/nold-ai/specfact-cli/releases/download/upgrade-0.1.19.tar.gz",
|
|
12
|
+
"id": "upgrade",
|
|
13
|
+
"latest_version": "0.1.19"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"checksum_sha256": "79359da27cb1c734a928a453a786b626e7b06654f515f32ead0cf96fa8e70bc2",
|
|
17
|
+
"download_url": "https://github.com/nold-ai/specfact-cli/releases/download/module-registry-0.1.26.tar.gz",
|
|
18
|
+
"id": "module-registry",
|
|
19
|
+
"latest_version": "0.1.26"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "bundle-mapper",
|
|
23
|
+
"latest_version": "0.1.9"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -1625,7 +1625,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1625
1625
|
headers = {"Content-Type": "application/json-patch+json", **self._auth_headers()}
|
|
1626
1626
|
try:
|
|
1627
1627
|
response = self._request_with_retry(
|
|
1628
|
-
lambda: requests.patch(url, json=patch_document, headers=headers, timeout=30)
|
|
1628
|
+
lambda: requests.patch(url, json=cast(Any, patch_document), headers=headers, timeout=30)
|
|
1629
1629
|
)
|
|
1630
1630
|
except requests.RequestException as exc:
|
|
1631
1631
|
resp = getattr(exc, "response", None)
|
|
@@ -1939,7 +1939,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1939
1939
|
# Refresh failed (no cached refresh token, refresh token expired, etc.)
|
|
1940
1940
|
return None
|
|
1941
1941
|
|
|
1942
|
-
def _auth_headers(self) -> dict[str, str]:
|
|
1942
|
+
def _auth_headers(self) -> dict[str, str | bytes]:
|
|
1943
1943
|
"""Return authorization headers based on token type."""
|
|
1944
1944
|
if not self.api_token:
|
|
1945
1945
|
return {}
|
|
@@ -1953,7 +1953,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1953
1953
|
self,
|
|
1954
1954
|
url: str,
|
|
1955
1955
|
*,
|
|
1956
|
-
headers: dict[str, str] | None = None,
|
|
1956
|
+
headers: dict[str, str | bytes] | None = None,
|
|
1957
1957
|
params: dict[str, Any] | None = None,
|
|
1958
1958
|
timeout: int = 30,
|
|
1959
1959
|
retry_on_ambiguous_transport: bool = True,
|
|
@@ -1973,7 +1973,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1973
1973
|
self,
|
|
1974
1974
|
url: str,
|
|
1975
1975
|
*,
|
|
1976
|
-
headers: dict[str, str] | None = None,
|
|
1976
|
+
headers: dict[str, str | bytes] | None = None,
|
|
1977
1977
|
params: dict[str, Any] | None = None,
|
|
1978
1978
|
json: dict[str, Any] | None = None,
|
|
1979
1979
|
timeout: int = 30,
|
|
@@ -2213,7 +2213,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
2213
2213
|
|
|
2214
2214
|
try:
|
|
2215
2215
|
response = self._request_with_retry(
|
|
2216
|
-
lambda: requests.post(url, json=patch_document, headers=headers, timeout=30),
|
|
2216
|
+
lambda: requests.post(url, json=cast(Any, patch_document), headers=headers, timeout=30),
|
|
2217
2217
|
retry_on_ambiguous_transport=False,
|
|
2218
2218
|
)
|
|
2219
2219
|
if is_debug_mode():
|
|
@@ -2670,7 +2670,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
2670
2670
|
|
|
2671
2671
|
try:
|
|
2672
2672
|
response = self._request_with_retry(
|
|
2673
|
-
lambda: requests.post(url, json=comment_body, headers=headers, timeout=30),
|
|
2673
|
+
lambda: requests.post(url, json=cast(Any, comment_body), headers=headers, timeout=30),
|
|
2674
2674
|
retry_on_ambiguous_transport=False,
|
|
2675
2675
|
)
|
|
2676
2676
|
comment_data = response.json()
|
|
@@ -3478,7 +3478,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3478
3478
|
**self._auth_headers(),
|
|
3479
3479
|
}
|
|
3480
3480
|
response = self._request_with_retry(
|
|
3481
|
-
lambda: requests.post(url, json=patch_document, headers=headers, timeout=30),
|
|
3481
|
+
lambda: requests.post(url, json=cast(Any, patch_document), headers=headers, timeout=30),
|
|
3482
3482
|
retry_on_ambiguous_transport=False,
|
|
3483
3483
|
)
|
|
3484
3484
|
created = response.json()
|
|
@@ -3782,7 +3782,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3782
3782
|
if operations_no_format == operations:
|
|
3783
3783
|
return None
|
|
3784
3784
|
try:
|
|
3785
|
-
resp = requests.patch(url, headers=headers, json=operations_no_format, timeout=30)
|
|
3785
|
+
resp = requests.patch(url, headers=headers, json=cast(Any, operations_no_format), timeout=30)
|
|
3786
3786
|
resp.raise_for_status()
|
|
3787
3787
|
return resp
|
|
3788
3788
|
except requests.HTTPError as retry_error:
|
|
@@ -3802,7 +3802,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3802
3802
|
) -> requests.Response | None:
|
|
3803
3803
|
operations_replace = self._backlog_ops_replace_multiline_add_with_replace(operations)
|
|
3804
3804
|
try:
|
|
3805
|
-
resp = requests.patch(url, headers=headers, json=operations_replace, timeout=30)
|
|
3805
|
+
resp = requests.patch(url, headers=headers, json=cast(Any, operations_replace), timeout=30)
|
|
3806
3806
|
resp.raise_for_status()
|
|
3807
3807
|
return resp
|
|
3808
3808
|
except requests.HTTPError:
|
|
@@ -3820,7 +3820,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3820
3820
|
)
|
|
3821
3821
|
operations_html = self._backlog_ops_convert_markdown_fields_to_html(operations)
|
|
3822
3822
|
try:
|
|
3823
|
-
resp = requests.patch(url, headers=headers, json=operations_html, timeout=30)
|
|
3823
|
+
resp = requests.patch(url, headers=headers, json=cast(Any, operations_html), timeout=30)
|
|
3824
3824
|
resp.raise_for_status()
|
|
3825
3825
|
return resp
|
|
3826
3826
|
except requests.HTTPError:
|
|
@@ -3834,7 +3834,9 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3834
3834
|
operations: list[dict[str, Any]],
|
|
3835
3835
|
) -> requests.Response:
|
|
3836
3836
|
try:
|
|
3837
|
-
return self._request_with_retry(
|
|
3837
|
+
return self._request_with_retry(
|
|
3838
|
+
lambda: requests.patch(url, headers=headers, json=cast(Any, operations), timeout=30)
|
|
3839
|
+
)
|
|
3838
3840
|
except requests.HTTPError as e:
|
|
3839
3841
|
user_msg = _log_ado_patch_failure(e.response, operations, url)
|
|
3840
3842
|
e.ado_user_message = user_msg # type: ignore[attr-defined]
|
|
@@ -561,7 +561,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
561
561
|
from specfact_cli.backlog.converter import convert_github_issue_to_backlog_item
|
|
562
562
|
|
|
563
563
|
url = f"{self.base_url}/search/issues"
|
|
564
|
-
headers = {
|
|
564
|
+
headers: dict[str, str | bytes] = {
|
|
565
565
|
"Authorization": f"token {self.api_token}",
|
|
566
566
|
"Accept": "application/vnd.github.v3+json",
|
|
567
567
|
}
|
|
@@ -1232,7 +1232,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1232
1232
|
|
|
1233
1233
|
repo_owner, repo_name, issue_number = self._parse_issue_reference(item_ref)
|
|
1234
1234
|
url = f"{self.base_url}/repos/{repo_owner}/{repo_name}/issues/{issue_number}"
|
|
1235
|
-
headers = {
|
|
1235
|
+
headers: dict[str, str | bytes] = {
|
|
1236
1236
|
"Authorization": f"token {self.api_token}",
|
|
1237
1237
|
"Accept": "application/vnd.github.v3+json",
|
|
1238
1238
|
}
|
|
@@ -1498,7 +1498,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1498
1498
|
|
|
1499
1499
|
# Create issue via GitHub API
|
|
1500
1500
|
url = f"{self.base_url}/repos/{repo_owner}/{repo_name}/issues"
|
|
1501
|
-
headers = {
|
|
1501
|
+
headers: dict[str, str | bytes] = {
|
|
1502
1502
|
"Authorization": f"token {self.api_token}",
|
|
1503
1503
|
"Accept": "application/vnd.github.v3+json",
|
|
1504
1504
|
}
|
|
@@ -1592,7 +1592,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1592
1592
|
|
|
1593
1593
|
# Update issue state
|
|
1594
1594
|
url = f"{self.base_url}/repos/{repo_owner}/{repo_name}/issues/{issue_number}"
|
|
1595
|
-
headers = {
|
|
1595
|
+
headers: dict[str, str | bytes] = {
|
|
1596
1596
|
"Authorization": f"token {self.api_token}",
|
|
1597
1597
|
"Accept": "application/vnd.github.v3+json",
|
|
1598
1598
|
}
|
|
@@ -1601,7 +1601,9 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1601
1601
|
payload["state_reason"] = state_reason
|
|
1602
1602
|
|
|
1603
1603
|
try:
|
|
1604
|
-
response = self._request_with_retry(
|
|
1604
|
+
response = self._request_with_retry(
|
|
1605
|
+
lambda: requests.patch(url, json=cast(Any, payload), headers=headers, timeout=30)
|
|
1606
|
+
)
|
|
1605
1607
|
issue_data = response.json()
|
|
1606
1608
|
|
|
1607
1609
|
# Add comment explaining status change
|
|
@@ -1634,7 +1636,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1634
1636
|
return []
|
|
1635
1637
|
|
|
1636
1638
|
url = f"{self.base_url}/repos/{repo_owner}/{repo_name}/issues/{issue_number}/comments"
|
|
1637
|
-
headers = {
|
|
1639
|
+
headers: dict[str, str | bytes] = {
|
|
1638
1640
|
"Authorization": f"token {self.api_token}",
|
|
1639
1641
|
"Accept": "application/vnd.github.v3+json",
|
|
1640
1642
|
}
|
|
@@ -1658,7 +1660,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1658
1660
|
comment: Comment text
|
|
1659
1661
|
"""
|
|
1660
1662
|
url = f"{self.base_url}/repos/{repo_owner}/{repo_name}/issues/{issue_number}/comments"
|
|
1661
|
-
headers = {
|
|
1663
|
+
headers: dict[str, str | bytes] = {
|
|
1662
1664
|
"Authorization": f"token {self.api_token}",
|
|
1663
1665
|
"Accept": "application/vnd.github.v3+json",
|
|
1664
1666
|
}
|
|
@@ -1666,7 +1668,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1666
1668
|
|
|
1667
1669
|
try:
|
|
1668
1670
|
self._request_with_retry(
|
|
1669
|
-
lambda: requests.post(url, json=payload, headers=headers, timeout=30),
|
|
1671
|
+
lambda: requests.post(url, json=cast(Any, payload), headers=headers, timeout=30),
|
|
1670
1672
|
retry_on_ambiguous_transport=False,
|
|
1671
1673
|
)
|
|
1672
1674
|
except requests.RequestException as e:
|
|
@@ -1676,7 +1678,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1676
1678
|
def _fetch_issue_snapshot(self, repo_owner: str, repo_name: str, issue_number: int) -> tuple[str, str, str]:
|
|
1677
1679
|
"""Fetch current issue body, title, and state for preservation-aware updates."""
|
|
1678
1680
|
url = f"{self.base_url}/repos/{repo_owner}/{repo_name}/issues/{issue_number}"
|
|
1679
|
-
headers = {
|
|
1681
|
+
headers: dict[str, str | bytes] = {
|
|
1680
1682
|
"Authorization": f"token {self.api_token}",
|
|
1681
1683
|
"Accept": "application/vnd.github.v3+json",
|
|
1682
1684
|
}
|
|
@@ -1759,7 +1761,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1759
1761
|
|
|
1760
1762
|
# Update issue body via GitHub API PATCH
|
|
1761
1763
|
url = f"{self.base_url}/repos/{repo_owner}/{repo_name}/issues/{issue_number}"
|
|
1762
|
-
headers = {
|
|
1764
|
+
headers: dict[str, str | bytes] = {
|
|
1763
1765
|
"Authorization": f"token {self.api_token}",
|
|
1764
1766
|
"Accept": "application/vnd.github.v3+json",
|
|
1765
1767
|
}
|
|
@@ -1969,7 +1971,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1969
1971
|
|
|
1970
1972
|
# Get current issue to retrieve existing labels
|
|
1971
1973
|
url = f"{self.base_url}/repos/{repo_owner}/{repo_name}/issues/{issue_number}"
|
|
1972
|
-
headers = {
|
|
1974
|
+
headers: dict[str, str | bytes] = {
|
|
1973
1975
|
"Authorization": f"token {self.api_token}",
|
|
1974
1976
|
"Accept": "application/vnd.github.v3+json",
|
|
1975
1977
|
}
|
|
@@ -1987,7 +1989,9 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1987
1989
|
patch_url = f"{self.base_url}/repos/{repo_owner}/{repo_name}/issues/{issue_number}"
|
|
1988
1990
|
patch_payload = {"labels": all_labels}
|
|
1989
1991
|
|
|
1990
|
-
self._request_with_retry(
|
|
1992
|
+
self._request_with_retry(
|
|
1993
|
+
lambda: requests.patch(patch_url, json=cast(Any, patch_payload), headers=headers, timeout=30)
|
|
1994
|
+
)
|
|
1991
1995
|
|
|
1992
1996
|
return {
|
|
1993
1997
|
"issue_number": current_issue.get("number", issue_number), # Use API response number (int)
|
|
@@ -2623,7 +2627,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
2623
2627
|
return None
|
|
2624
2628
|
|
|
2625
2629
|
url = f"{self.base_url}/repos/{self.repo_owner}/{self.repo_name}/issues/{normalized_id}"
|
|
2626
|
-
headers = {
|
|
2630
|
+
headers: dict[str, str | bytes] = {
|
|
2627
2631
|
"Authorization": f"token {self.api_token}",
|
|
2628
2632
|
"Accept": "application/vnd.github.v3+json",
|
|
2629
2633
|
}
|
|
@@ -2761,7 +2765,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
2761
2765
|
@beartype
|
|
2762
2766
|
def _github_graphql(self, query: str, variables: dict[str, Any]) -> dict[str, Any]:
|
|
2763
2767
|
"""Execute GitHub GraphQL request and return `data` payload."""
|
|
2764
|
-
headers = {
|
|
2768
|
+
headers: dict[str, str | bytes] = {
|
|
2765
2769
|
"Authorization": f"token {self.api_token}",
|
|
2766
2770
|
"Accept": "application/vnd.github+json",
|
|
2767
2771
|
}
|
|
@@ -2976,14 +2980,14 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
2976
2980
|
issue_type, str(payload.get("priority") or "").strip(), payload.get("story_points")
|
|
2977
2981
|
)
|
|
2978
2982
|
url = f"{self.base_url}/repos/{owner}/{repo}/issues"
|
|
2979
|
-
headers = {
|
|
2983
|
+
headers: dict[str, str | bytes] = {
|
|
2980
2984
|
"Authorization": f"token {self.api_token}",
|
|
2981
2985
|
"Accept": "application/vnd.github.v3+json",
|
|
2982
2986
|
}
|
|
2983
2987
|
response = self._request_with_retry(
|
|
2984
2988
|
lambda: requests.post(
|
|
2985
2989
|
url,
|
|
2986
|
-
json={"title": title, "body": body, "labels": labels},
|
|
2990
|
+
json=cast(Any, {"title": title, "body": body, "labels": labels}),
|
|
2987
2991
|
headers=headers,
|
|
2988
2992
|
timeout=30,
|
|
2989
2993
|
),
|
|
@@ -3245,7 +3249,7 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3245
3249
|
|
|
3246
3250
|
issue_number = int(item.id)
|
|
3247
3251
|
url = f"{self.base_url}/repos/{self.repo_owner}/{self.repo_name}/issues/{issue_number}"
|
|
3248
|
-
headers = {
|
|
3252
|
+
headers: dict[str, str | bytes] = {
|
|
3249
3253
|
"Authorization": f"token {self.api_token}",
|
|
3250
3254
|
"Accept": "application/vnd.github.v3+json",
|
|
3251
3255
|
}
|
|
@@ -698,6 +698,8 @@ class _LazyDelegateGroup(click.Group):
|
|
|
698
698
|
real_group = self._get_real_click_group()
|
|
699
699
|
if real_group is not None:
|
|
700
700
|
return real_group.get_command(ctx, cmd_name)
|
|
701
|
+
if self._lazy_cmd_name in KNOWN_BUNDLE_GROUP_OR_SHIM_NAMES:
|
|
702
|
+
return self._delegate_cmd
|
|
701
703
|
return None
|
|
702
704
|
|
|
703
705
|
def _get_real_click_group(self) -> click.Group | None:
|
|
@@ -662,7 +662,13 @@ class ProjectBundle(BaseModel):
|
|
|
662
662
|
if progress_callback:
|
|
663
663
|
progress_callback(total_artifacts, total_artifacts, "bundle.manifest.yaml")
|
|
664
664
|
manifest_path = bundle_dir / "bundle.manifest.yaml"
|
|
665
|
-
|
|
665
|
+
manifest_data = self.manifest.model_dump(mode="json")
|
|
666
|
+
if num_features > 1000:
|
|
667
|
+
import json
|
|
668
|
+
|
|
669
|
+
manifest_path.write_text(json.dumps(manifest_data, indent=2), encoding="utf-8")
|
|
670
|
+
else:
|
|
671
|
+
dump_structured_file(manifest_data, manifest_path)
|
|
666
672
|
|
|
667
673
|
@beartype
|
|
668
674
|
@require(lambda self, key: isinstance(key, str) and len(key) > 0, "Feature key must be non-empty string")
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name: module-registry
|
|
2
|
-
version: 0.1.
|
|
2
|
+
version: 0.1.26
|
|
3
3
|
commands:
|
|
4
4
|
- module
|
|
5
5
|
category: core
|
|
@@ -17,5 +17,5 @@ publisher:
|
|
|
17
17
|
description: 'Manage modules: search, list, show, install, and upgrade.'
|
|
18
18
|
license: Apache-2.0
|
|
19
19
|
integrity:
|
|
20
|
-
checksum: sha256:
|
|
21
|
-
signature:
|
|
20
|
+
checksum: sha256:52a65869fb3d2d36910308c954e0ba199cb1c73f24136ee0d4aa7254f0996a22
|
|
21
|
+
signature: Uo2eJ40HiWBqe77N8gjiDg7zGhsvJE/hN82+Jcto7yUoqbH7bidLw9DfVQ0CrJfgM9Y0XHAUS1+LMyPr1jAaBg==
|
|
@@ -28,7 +28,12 @@ from specfact_cli.registry.alias_manager import create_alias, list_aliases, remo
|
|
|
28
28
|
from specfact_cli.registry.custom_registries import add_registry, fetch_all_indexes, list_registries, remove_registry
|
|
29
29
|
from specfact_cli.registry.help_cache import run_discovery_and_write_cache
|
|
30
30
|
from specfact_cli.registry.marketplace_client import fetch_registry_index
|
|
31
|
-
from specfact_cli.registry.module_discovery import
|
|
31
|
+
from specfact_cli.registry.module_discovery import (
|
|
32
|
+
DiscoveredModule,
|
|
33
|
+
discover_all_modules,
|
|
34
|
+
discover_all_modules_for_project,
|
|
35
|
+
discover_all_modules_for_project_with_shadowed,
|
|
36
|
+
)
|
|
32
37
|
from specfact_cli.registry.module_installer import (
|
|
33
38
|
REGISTRY_ID_FILE,
|
|
34
39
|
USER_MODULES_ROOT,
|
|
@@ -1066,6 +1071,107 @@ def _print_bundled_available_table(available: list[ModulePackageMetadata]) -> No
|
|
|
1066
1071
|
console.print("[dim]Install bundled modules into project scope: specfact module init --scope project[/dim]")
|
|
1067
1072
|
|
|
1068
1073
|
|
|
1074
|
+
def _doctor_entry_matches(entry: DiscoveredModule, module_id: str | None) -> bool:
|
|
1075
|
+
if module_id is None:
|
|
1076
|
+
return True
|
|
1077
|
+
requested = module_id.strip()
|
|
1078
|
+
if not requested:
|
|
1079
|
+
return True
|
|
1080
|
+
discovered_id = entry.metadata.name
|
|
1081
|
+
if requested == discovered_id:
|
|
1082
|
+
return True
|
|
1083
|
+
if "/" in requested:
|
|
1084
|
+
return False
|
|
1085
|
+
return requested.rsplit("/", 1)[-1] == discovered_id.rsplit("/", 1)[-1]
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
def _doctor_dev_roots() -> list[tuple[str, str]]:
|
|
1089
|
+
roots: list[tuple[str, str]] = []
|
|
1090
|
+
for env_name in ("SPECFACT_MODULES_REPO", "SPECFACT_CLI_MODULES_REPO"):
|
|
1091
|
+
value = os.environ.get(env_name, "").strip()
|
|
1092
|
+
if value:
|
|
1093
|
+
roots.append((env_name, value))
|
|
1094
|
+
for raw_root in os.environ.get("SPECFACT_MODULES_ROOTS", "").split(os.pathsep):
|
|
1095
|
+
value = raw_root.strip()
|
|
1096
|
+
if value:
|
|
1097
|
+
roots.append(("SPECFACT_MODULES_ROOTS", value))
|
|
1098
|
+
return roots
|
|
1099
|
+
|
|
1100
|
+
|
|
1101
|
+
def _doctor_status(
|
|
1102
|
+
entry: DiscoveredModule,
|
|
1103
|
+
seen_by_module_id: set[str],
|
|
1104
|
+
enabled_state: dict[str, dict[str, Any]],
|
|
1105
|
+
) -> str:
|
|
1106
|
+
module_id = entry.metadata.name
|
|
1107
|
+
if module_id in seen_by_module_id:
|
|
1108
|
+
return "shadowed"
|
|
1109
|
+
seen_by_module_id.add(module_id)
|
|
1110
|
+
if enabled_state.get(module_id, {}).get("enabled", True) is False:
|
|
1111
|
+
return "disabled"
|
|
1112
|
+
return "effective"
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
def _print_doctor_recovery(entries: list[tuple[DiscoveredModule, str]]) -> None:
|
|
1116
|
+
for entry, status in entries:
|
|
1117
|
+
if status != "shadowed" or entry.source != "user":
|
|
1118
|
+
continue
|
|
1119
|
+
console.print(f"[yellow]Recovery:[/yellow] specfact module uninstall {entry.metadata.name} --scope user")
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
@app.command(name="doctor")
|
|
1123
|
+
@beartype
|
|
1124
|
+
@require(_module_id_optional_nonempty, "module_id must be non-empty if provided")
|
|
1125
|
+
def doctor(
|
|
1126
|
+
module_id: str | None = typer.Argument(None, help="Optional module id to inspect"),
|
|
1127
|
+
repo: Path | None = typer.Option(None, "--repo", help="Repository path for project scope (default: current dir)"),
|
|
1128
|
+
) -> None:
|
|
1129
|
+
"""Diagnose module scope, shadowing, and development source roots."""
|
|
1130
|
+
repo_path = (repo or Path.cwd()).resolve()
|
|
1131
|
+
discovered = [
|
|
1132
|
+
entry
|
|
1133
|
+
for entry in discover_all_modules_for_project_with_shadowed(repo_path)
|
|
1134
|
+
if _doctor_entry_matches(entry, module_id)
|
|
1135
|
+
]
|
|
1136
|
+
if not discovered:
|
|
1137
|
+
console.print("[yellow]No matching modules discovered.[/yellow]")
|
|
1138
|
+
else:
|
|
1139
|
+
state = read_modules_state()
|
|
1140
|
+
seen_by_module_id: set[str] = set()
|
|
1141
|
+
rows: list[tuple[DiscoveredModule, str]] = []
|
|
1142
|
+
table = Table(title="Module Doctor")
|
|
1143
|
+
table.add_column("Module", style="cyan")
|
|
1144
|
+
table.add_column("Version", style="magenta")
|
|
1145
|
+
table.add_column("Status", style="yellow")
|
|
1146
|
+
table.add_column("Origin", style="blue")
|
|
1147
|
+
table.add_column("Enabled", style="green")
|
|
1148
|
+
table.add_column("Path", overflow="fold")
|
|
1149
|
+
for entry in discovered:
|
|
1150
|
+
status = _doctor_status(entry, seen_by_module_id, state)
|
|
1151
|
+
enabled = state.get(entry.metadata.name, {}).get("enabled", True) is not False
|
|
1152
|
+
rows.append((entry, status))
|
|
1153
|
+
table.add_row(
|
|
1154
|
+
entry.metadata.name,
|
|
1155
|
+
entry.metadata.version,
|
|
1156
|
+
status,
|
|
1157
|
+
entry.source,
|
|
1158
|
+
"yes" if enabled else "no",
|
|
1159
|
+
str(entry.package_dir),
|
|
1160
|
+
)
|
|
1161
|
+
console.print(table)
|
|
1162
|
+
_print_doctor_recovery(rows)
|
|
1163
|
+
|
|
1164
|
+
dev_roots = _doctor_dev_roots()
|
|
1165
|
+
if not dev_roots:
|
|
1166
|
+
return
|
|
1167
|
+
dev_table = Table(title="Development Source Roots")
|
|
1168
|
+
dev_table.add_column("Source", style="cyan")
|
|
1169
|
+
dev_table.add_column("Path", overflow="fold")
|
|
1170
|
+
for source, path in dev_roots:
|
|
1171
|
+
dev_table.add_row(source, path)
|
|
1172
|
+
console.print(dev_table)
|
|
1173
|
+
|
|
1174
|
+
|
|
1069
1175
|
@app.command(name="list")
|
|
1070
1176
|
@beartype
|
|
1071
1177
|
@require(
|
{specfact_cli-0.46.19 → specfact_cli-0.46.28}/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.19
|
|
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:fda269f874f7b61ad5ec8217ba1550e49b1e2e17b23aafd08b39df1a98d66ee1
|
|
21
|
+
signature: yx89WfwC8DQUkAw2WD1EdHp08Ug0dFw3gNbIXbwgcdoxUBa9qA/0TaHHNBbyhVIxgL6wb+H4XCbYjdwb6XU1Bg==
|
{specfact_cli-0.46.19 → specfact_cli-0.46.28}/src/specfact_cli/modules/upgrade/src/commands.py
RENAMED
|
@@ -23,6 +23,7 @@ from icontract import ensure
|
|
|
23
23
|
from rich.console import Console
|
|
24
24
|
from rich.panel import Panel
|
|
25
25
|
from rich.prompt import Confirm
|
|
26
|
+
from rich.text import Text
|
|
26
27
|
|
|
27
28
|
from specfact_cli import __version__
|
|
28
29
|
from specfact_cli.contracts.module_interface import ModuleIOContract
|
|
@@ -223,16 +224,27 @@ def _build_upgrade_command(method: InstallationMethod) -> list[str] | None:
|
|
|
223
224
|
def _execute_upgrade_command(command: list[str]) -> bool:
|
|
224
225
|
try:
|
|
225
226
|
console.print("[cyan]Updating SpecFact CLI...[/cyan]")
|
|
226
|
-
result = subprocess.run(command, check=False, timeout=300)
|
|
227
|
-
except subprocess.TimeoutExpired:
|
|
227
|
+
result = subprocess.run(command, check=False, timeout=300, capture_output=True)
|
|
228
|
+
except subprocess.TimeoutExpired as exc:
|
|
229
|
+
_replay_upgrade_output(_coerce_subprocess_output(exc.stdout))
|
|
230
|
+
_replay_upgrade_output(_coerce_subprocess_output(exc.stderr))
|
|
228
231
|
console.print("[red]✗ Update timed out (exceeded 5 minutes)[/red]")
|
|
229
232
|
return False
|
|
230
233
|
except OSError as e:
|
|
231
234
|
console.print(f"[red]✗ Update failed: {e}[/red]")
|
|
232
235
|
return False
|
|
236
|
+
stdout = _coerce_subprocess_output(result.stdout)
|
|
237
|
+
stderr = _coerce_subprocess_output(result.stderr)
|
|
233
238
|
if result.returncode != 0:
|
|
239
|
+
_replay_upgrade_output(stdout)
|
|
240
|
+
_replay_upgrade_output(stderr)
|
|
234
241
|
console.print(f"[red]✗ Update failed with exit code {result.returncode}[/red]")
|
|
235
242
|
return False
|
|
243
|
+
if _is_pipx_upgrade_command(command):
|
|
244
|
+
stdout = _filter_pipx_spaced_home_warning(stdout)
|
|
245
|
+
stderr = _filter_pipx_spaced_home_warning(stderr)
|
|
246
|
+
_replay_upgrade_output(stdout)
|
|
247
|
+
_replay_upgrade_output(stderr)
|
|
236
248
|
console.print("[green]✓ Update successful![/green]")
|
|
237
249
|
from datetime import datetime
|
|
238
250
|
|
|
@@ -243,6 +255,48 @@ def _execute_upgrade_command(command: list[str]) -> bool:
|
|
|
243
255
|
return True
|
|
244
256
|
|
|
245
257
|
|
|
258
|
+
def _coerce_subprocess_output(output: object) -> str:
|
|
259
|
+
"""Return subprocess output as displayable text."""
|
|
260
|
+
if isinstance(output, str):
|
|
261
|
+
return output
|
|
262
|
+
if isinstance(output, bytes):
|
|
263
|
+
return output.decode(errors="replace")
|
|
264
|
+
return ""
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _is_pipx_upgrade_command(command: list[str]) -> bool:
|
|
268
|
+
"""Return whether command is the supported pipx upgrade invocation."""
|
|
269
|
+
return len(command) >= 3 and command[:3] == ["pipx", "upgrade", "specfact-cli"]
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _replay_upgrade_output(output: str) -> None:
|
|
273
|
+
"""Replay captured child-process output without Rich markup parsing."""
|
|
274
|
+
if output:
|
|
275
|
+
console.print(Text(output), end="")
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _filter_pipx_spaced_home_warning(output: str) -> str:
|
|
279
|
+
"""Remove only pipx's known spaced-home warning block from successful output."""
|
|
280
|
+
if not output:
|
|
281
|
+
return output
|
|
282
|
+
warning_markers = (
|
|
283
|
+
"Found a space in the pipx home path",
|
|
284
|
+
"To see your PIPX_HOME dir",
|
|
285
|
+
"Most likely fix on macOS",
|
|
286
|
+
)
|
|
287
|
+
filtered_lines: list[str] = []
|
|
288
|
+
skipping_wrapped_warning = False
|
|
289
|
+
for line in output.splitlines(keepends=True):
|
|
290
|
+
if any(marker in line for marker in warning_markers):
|
|
291
|
+
skipping_wrapped_warning = "Found a space in the pipx home path" in line
|
|
292
|
+
continue
|
|
293
|
+
if skipping_wrapped_warning and line[:1].isspace():
|
|
294
|
+
continue
|
|
295
|
+
skipping_wrapped_warning = False
|
|
296
|
+
filtered_lines.append(line)
|
|
297
|
+
return "".join(filtered_lines)
|
|
298
|
+
|
|
299
|
+
|
|
246
300
|
def _upgrade_log_started(check_only: bool, yes: bool) -> None:
|
|
247
301
|
if is_debug_mode():
|
|
248
302
|
debug_log_operation(
|