specfact-cli 0.46.18__tar.gz → 0.46.25__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.18 → specfact_cli-0.46.25}/.gitignore +1 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/PKG-INFO +6 -4
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/pyproject.toml +9 -6
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/__init__.py +1 -1
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/adapters/ado.py +13 -11
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/adapters/github.py +21 -17
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/cli.py +7 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/project.py +7 -1
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/init/module-package.yaml +3 -3
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/init/src/commands.py +29 -3
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/module_registry/module-package.yaml +3 -3
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/module_registry/src/commands.py +111 -1
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/module_availability.py +11 -1
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/module_installer.py +91 -17
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/module_packages.py +247 -17
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/module_state.py +11 -2
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/bridge_sync.py +2 -2
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/env_manager.py +157 -48
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/ide_setup.py +5 -1
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/yaml_utils.py +23 -9
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/change_proposal_integration.py +3 -3
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/LICENSE +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/README.md +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/bundled-module-registry/index.json +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/keys/README.md +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/keys/module-signing-public.pem +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/templates/policies/kanban.yaml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/templates/policies/mixed.yaml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/templates/policies/safe.yaml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/templates/policies/scrum.yaml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/__main__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/adapters/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/adapters/backlog_base.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/adapters/base.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/adapters/openspec.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/adapters/openspec_parser.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/adapters/registry.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/adapters/speckit.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/backlog/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/backlog/adapters/base.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/backlog/converter.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/backlog/filters.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/backlog/mappers/base.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/_bundle_shim.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/project_cmd.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/update.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/commands/validate.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/common/bundle_factory.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/contracts/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/contracts/crosshair_props.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/contracts/module_interface.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/generators/persona_exporter.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/groups/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/groups/codebase_group.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/groups/govern_group.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/groups/member_group.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/groups/project_group.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/groups/spec_group.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/backlog_item.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/capabilities.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/change.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/dor_config.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/module_package.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/models/validation.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/_bundle_import.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/init/src/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/init/src/app.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/init/src/first_run_selection.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/module_io_shim.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/module_registry/src/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/module_registry/src/app.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/upgrade/module-package.yaml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/upgrade/src/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/upgrade/src/app.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/upgrade/src/commands.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/alias_manager.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/bootstrap.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/bridge_registry.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/crypto_validator.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/custom_registries.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/dependency_resolver.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/extension_registry.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/help_cache.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/marketplace_client.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/metadata.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/module_discovery.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/module_grouping.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/module_lifecycle.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/module_security.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/registry/registry.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/bridge_sync_openspec_md_parse.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/bridge_sync_requirement_from_proposal.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/bridge_sync_requirement_helpers.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/bridge_sync_tasks_from_proposal.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/bridge_sync_what_changes_format.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/bridge_sync_write_openspec_from_proposal.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/templates/registry.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/auth_tokens.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/bundle_converters.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/code_change_detector.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/content_sanitizer.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/contract_predicates.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/icontract_helpers.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/metadata.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/persona_ownership.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/project_artifact_write.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/startup_checks.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/utils/terminal.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validation/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validation/command_audit.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/schema.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/models.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.46.18 → specfact_cli-0.46.25}/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.25
|
|
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
|
|
@@ -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.25"
|
|
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",
|
|
@@ -283,6 +285,7 @@ verify-removal-gate = [
|
|
|
283
285
|
"hatch run verify-modules-signature",
|
|
284
286
|
]
|
|
285
287
|
export-change-github = "python scripts/export-change-to-github.py {args}"
|
|
288
|
+
runtime-discovery-smoke = "python scripts/runtime_discovery_smoke.py {args}"
|
|
286
289
|
|
|
287
290
|
# Contract-First Smart Test System Scripts
|
|
288
291
|
contract-test = "python tools/contract_first_smart_test.py run --level auto {args}"
|
|
@@ -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
|
}
|
|
@@ -178,6 +178,11 @@ class _RootCLIGroup(ProgressiveDisclosureGroup):
|
|
|
178
178
|
_print_missing_bundle_command_help(invoked)
|
|
179
179
|
raise SystemExit(1) from None
|
|
180
180
|
raise
|
|
181
|
+
except ValueError as exc:
|
|
182
|
+
if invoked in KNOWN_BUNDLE_GROUP_OR_SHIM_NAMES:
|
|
183
|
+
_print_missing_bundle_command_help(invoked)
|
|
184
|
+
raise SystemExit(1) from exc
|
|
185
|
+
raise
|
|
181
186
|
_name, cmd, remaining = result
|
|
182
187
|
if cmd is not None or not remaining:
|
|
183
188
|
return result
|
|
@@ -693,6 +698,8 @@ class _LazyDelegateGroup(click.Group):
|
|
|
693
698
|
real_group = self._get_real_click_group()
|
|
694
699
|
if real_group is not None:
|
|
695
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
|
|
696
703
|
return None
|
|
697
704
|
|
|
698
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")
|
{specfact_cli-0.46.18 → specfact_cli-0.46.25}/src/specfact_cli/modules/init/module-package.yaml
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name: init
|
|
2
|
-
version: 0.1.
|
|
2
|
+
version: 0.1.33
|
|
3
3
|
commands:
|
|
4
4
|
- init
|
|
5
5
|
category: core
|
|
@@ -17,5 +17,5 @@ publisher:
|
|
|
17
17
|
description: Initialize SpecFact workspace and bootstrap local configuration.
|
|
18
18
|
license: Apache-2.0
|
|
19
19
|
integrity:
|
|
20
|
-
checksum: sha256:
|
|
21
|
-
signature:
|
|
20
|
+
checksum: sha256:0086a4fa3e1f8744deee21640e73e6e2f24daf2b4a680b9157feed5487afc208
|
|
21
|
+
signature: L7DtVqvg+zqc/CgJu/mOkvxYpfH9HuddbLS3EK5Uvnf6K/j/BofqopamZDdHVlevOGAb+dbsk06kskNOIJfkDQ==
|
|
@@ -26,7 +26,13 @@ from specfact_cli.registry.module_state import write_modules_state
|
|
|
26
26
|
from specfact_cli.runtime import debug_print, is_non_interactive
|
|
27
27
|
from specfact_cli.telemetry import telemetry
|
|
28
28
|
from specfact_cli.utils.contract_predicates import repo_path_exists, repo_path_is_dir
|
|
29
|
-
from specfact_cli.utils.env_manager import
|
|
29
|
+
from specfact_cli.utils.env_manager import (
|
|
30
|
+
EnvManager,
|
|
31
|
+
EnvManagerInfo,
|
|
32
|
+
build_tool_command,
|
|
33
|
+
detect_env_manager,
|
|
34
|
+
env_info_from_tool_choice,
|
|
35
|
+
)
|
|
30
36
|
from specfact_cli.utils.ide_setup import (
|
|
31
37
|
IDE_CONFIG,
|
|
32
38
|
PROMPT_SOURCE_CORE,
|
|
@@ -610,6 +616,11 @@ def init_ide(
|
|
|
610
616
|
"--ide",
|
|
611
617
|
help="IDE type (cursor, vscode, copilot, claude, gemini, qwen, opencode, windsurf, kilocode, auggie, roo, codebuddy, amp, q, auto)",
|
|
612
618
|
),
|
|
619
|
+
env_manager: EnvManager = typer.Option(
|
|
620
|
+
EnvManager.AUTO,
|
|
621
|
+
"--env-manager",
|
|
622
|
+
help="Environment manager override: auto, uv, hatch, poetry, or pip",
|
|
623
|
+
),
|
|
613
624
|
prompts: str | None = typer.Option(
|
|
614
625
|
None,
|
|
615
626
|
"--prompts",
|
|
@@ -638,7 +649,17 @@ def init_ide(
|
|
|
638
649
|
console.print(f"[cyan]IDE:[/cyan] {ide_name} ({selected_ide})")
|
|
639
650
|
console.print()
|
|
640
651
|
|
|
641
|
-
env_info =
|
|
652
|
+
env_info = (
|
|
653
|
+
detect_env_manager(repo_path)
|
|
654
|
+
if env_manager is EnvManager.AUTO
|
|
655
|
+
else env_info_from_tool_choice(env_manager, repo_path)
|
|
656
|
+
)
|
|
657
|
+
if env_manager is not EnvManager.AUTO and not env_info.available:
|
|
658
|
+
console.print(Panel(f"[bold red]{env_info.message}[/bold red]", border_style="red"))
|
|
659
|
+
raise typer.Exit(1)
|
|
660
|
+
if env_info.manager is not EnvManager.UNKNOWN:
|
|
661
|
+
console.print(f"[cyan]Environment manager:[/cyan] {env_info.manager.value}")
|
|
662
|
+
console.print()
|
|
642
663
|
if env_info.manager == EnvManager.UNKNOWN:
|
|
643
664
|
console.print(
|
|
644
665
|
Panel(
|
|
@@ -673,7 +694,12 @@ def init_ide(
|
|
|
673
694
|
copied_files, settings_path = copy_templates_to_ide(
|
|
674
695
|
repo_path, selected_ide, force, prompts_by_source=selected_catalog
|
|
675
696
|
)
|
|
676
|
-
write_ide_prompt_export_state(
|
|
697
|
+
write_ide_prompt_export_state(
|
|
698
|
+
repo_path,
|
|
699
|
+
selected_ide,
|
|
700
|
+
sorted(selected_catalog.keys()),
|
|
701
|
+
env_manager=env_info.manager.value,
|
|
702
|
+
)
|
|
677
703
|
_copy_backlog_field_mapping_templates(repo_path, force, console)
|
|
678
704
|
|
|
679
705
|
console.print()
|
|
@@ -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==
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import inspect
|
|
6
6
|
import os
|
|
7
7
|
import shutil
|
|
8
|
+
import tempfile
|
|
8
9
|
from collections.abc import Callable, Iterator
|
|
9
10
|
from contextlib import contextmanager
|
|
10
11
|
from dataclasses import dataclass
|
|
@@ -27,7 +28,12 @@ from specfact_cli.registry.alias_manager import create_alias, list_aliases, remo
|
|
|
27
28
|
from specfact_cli.registry.custom_registries import add_registry, fetch_all_indexes, list_registries, remove_registry
|
|
28
29
|
from specfact_cli.registry.help_cache import run_discovery_and_write_cache
|
|
29
30
|
from specfact_cli.registry.marketplace_client import fetch_registry_index
|
|
30
|
-
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
|
+
)
|
|
31
37
|
from specfact_cli.registry.module_installer import (
|
|
32
38
|
REGISTRY_ID_FILE,
|
|
33
39
|
USER_MODULES_ROOT,
|
|
@@ -187,7 +193,10 @@ def _normalize_project_repo(repo: Path | None) -> Path | None:
|
|
|
187
193
|
if repo is None:
|
|
188
194
|
return None
|
|
189
195
|
repo_path = repo.resolve()
|
|
196
|
+
temp_root = Path(tempfile.gettempdir()).resolve()
|
|
190
197
|
for candidate in [repo_path, *repo_path.parents]:
|
|
198
|
+
if candidate == temp_root and candidate != repo_path:
|
|
199
|
+
return repo_path
|
|
191
200
|
if (candidate / ".git").exists():
|
|
192
201
|
return candidate
|
|
193
202
|
return repo_path
|
|
@@ -1062,6 +1071,107 @@ def _print_bundled_available_table(available: list[ModulePackageMetadata]) -> No
|
|
|
1062
1071
|
console.print("[dim]Install bundled modules into project scope: specfact module init --scope project[/dim]")
|
|
1063
1072
|
|
|
1064
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
|
+
|
|
1065
1175
|
@app.command(name="list")
|
|
1066
1176
|
@beartype
|
|
1067
1177
|
@require(
|