specfact-cli 0.46.17__tar.gz → 0.46.19__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.17 → specfact_cli-0.46.19}/.gitignore +1 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/PKG-INFO +1 -1
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/pyproject.toml +2 -1
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/__init__.py +1 -1
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/cli.py +14 -2
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modules/init/module-package.yaml +3 -3
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modules/init/src/commands.py +29 -3
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modules/module_registry/module-package.yaml +3 -3
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modules/module_registry/src/commands.py +4 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/module_availability.py +11 -1
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/module_packages.py +137 -10
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/env_manager.py +157 -48
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/ide_setup.py +5 -1
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/LICENSE +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/README.md +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/bundled-module-registry/index.json +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/keys/README.md +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/keys/module-signing-public.pem +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/templates/policies/kanban.yaml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/templates/policies/mixed.yaml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/templates/policies/safe.yaml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/templates/policies/scrum.yaml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/__main__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/adapters/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/adapters/ado.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/adapters/backlog_base.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/adapters/base.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/adapters/github.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/adapters/openspec.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/adapters/openspec_parser.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/adapters/registry.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/adapters/speckit.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/backlog/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/backlog/adapters/base.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/backlog/converter.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/backlog/filters.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/backlog/mappers/base.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/_bundle_shim.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/project_cmd.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/update.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/commands/validate.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/common/bundle_factory.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/contracts/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/contracts/crosshair_props.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/contracts/module_interface.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/generators/persona_exporter.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/groups/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/groups/codebase_group.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/groups/govern_group.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/groups/member_group.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/groups/project_group.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/groups/spec_group.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/backlog_item.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/capabilities.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/change.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/dor_config.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/module_package.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/project.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/models/validation.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modules/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modules/_bundle_import.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modules/init/src/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modules/init/src/app.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modules/init/src/first_run_selection.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modules/module_io_shim.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modules/module_registry/src/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modules/module_registry/src/app.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modules/upgrade/module-package.yaml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modules/upgrade/src/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modules/upgrade/src/app.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/modules/upgrade/src/commands.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/alias_manager.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/bootstrap.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/bridge_registry.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/crypto_validator.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/custom_registries.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/dependency_resolver.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/extension_registry.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/help_cache.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/marketplace_client.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/metadata.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/module_discovery.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/module_grouping.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/module_installer.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/module_lifecycle.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/module_security.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/module_state.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/registry.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/bridge_sync.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/bridge_sync_openspec_md_parse.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/bridge_sync_requirement_from_proposal.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/bridge_sync_requirement_helpers.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/bridge_sync_tasks_from_proposal.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/bridge_sync_what_changes_format.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/bridge_sync_write_openspec_from_proposal.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/templates/registry.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/auth_tokens.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/bundle_converters.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/code_change_detector.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/content_sanitizer.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/contract_predicates.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/icontract_helpers.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/metadata.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/persona_ownership.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/project_artifact_write.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/startup_checks.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/terminal.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validation/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validation/command_audit.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/schema.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/models.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.46.17 → specfact_cli-0.46.19}/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.19
|
|
4
4
|
Summary: The swiss knife CLI for agile DevOps teams. Keep backlog, specs, tests, and code in sync with validation and contract enforcement for new projects and long-lived codebases.
|
|
5
5
|
Project-URL: Homepage, https://github.com/nold-ai/specfact-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/nold-ai/specfact-cli.git
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specfact-cli"
|
|
7
|
-
version = "0.46.
|
|
7
|
+
version = "0.46.19"
|
|
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"
|
|
@@ -283,6 +283,7 @@ verify-removal-gate = [
|
|
|
283
283
|
"hatch run verify-modules-signature",
|
|
284
284
|
]
|
|
285
285
|
export-change-github = "python scripts/export-change-to-github.py {args}"
|
|
286
|
+
runtime-discovery-smoke = "python scripts/runtime_discovery_smoke.py {args}"
|
|
286
287
|
|
|
287
288
|
# Contract-First Smart Test System Scripts
|
|
288
289
|
contract-test = "python tools/contract_first_smart_test.py run --level auto {args}"
|
|
@@ -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
|
|
@@ -610,6 +615,12 @@ def _strip_redundant_single_command_arg(click_cmd: click.Command, args: tuple[st
|
|
|
610
615
|
return args_list
|
|
611
616
|
|
|
612
617
|
|
|
618
|
+
def _lazy_delegate_remaining_args(ctx: click.Context) -> list[str]:
|
|
619
|
+
ctx_state = vars(ctx)
|
|
620
|
+
protected_args = ctx_state.get("_protected_args") or ctx_state.get("protected_args") or ()
|
|
621
|
+
return [str(arg) for arg in (*protected_args, *ctx.args)]
|
|
622
|
+
|
|
623
|
+
|
|
613
624
|
class _LazyDelegateGroup(click.Group):
|
|
614
625
|
"""Click Group that delegates all args to the real command (lazy-loaded)."""
|
|
615
626
|
|
|
@@ -657,9 +668,10 @@ class _LazyDelegateGroup(click.Group):
|
|
|
657
668
|
@require(lambda ctx: ctx is not None, "ctx must not be None")
|
|
658
669
|
@ensure(lambda result: result is None or isinstance(result, int), "result must be None or an exit code")
|
|
659
670
|
def invoke(self, ctx: click.Context) -> Any:
|
|
660
|
-
if ctx.invoked_subcommand is None
|
|
671
|
+
if ctx.invoked_subcommand is None:
|
|
672
|
+
args = _lazy_delegate_remaining_args(ctx)
|
|
661
673
|
ctx.meta["original_prog_name"] = ctx.command_path
|
|
662
|
-
return self._delegate_cmd.main(args=
|
|
674
|
+
return self._delegate_cmd.main(args=args, prog_name=ctx.command_path, standalone_mode=False)
|
|
663
675
|
return super().invoke(ctx)
|
|
664
676
|
|
|
665
677
|
@require(_lazy_delegate_cmd_name_ready, "lazy command name must be set")
|
{specfact_cli-0.46.17 → specfact_cli-0.46.19}/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.24
|
|
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:bc1293efa20676657f440f5c8f21583fcf7c5c2138d754e0262934417c92cfbb
|
|
21
|
+
signature: 7hALhs1hTjtnz3SdYmzKIqoniyvJDk5LyFcauv/wnVJRyZEQyPv42sb0Zk30zXAZ8QPE/g43/g4l78nv0AqNBQ==
|
|
@@ -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
|
|
@@ -187,7 +188,10 @@ def _normalize_project_repo(repo: Path | None) -> Path | None:
|
|
|
187
188
|
if repo is None:
|
|
188
189
|
return None
|
|
189
190
|
repo_path = repo.resolve()
|
|
191
|
+
temp_root = Path(tempfile.gettempdir()).resolve()
|
|
190
192
|
for candidate in [repo_path, *repo_path.parents]:
|
|
193
|
+
if candidate == temp_root and candidate != repo_path:
|
|
194
|
+
return repo_path
|
|
191
195
|
if (candidate / ".git").exists():
|
|
192
196
|
return candidate
|
|
193
197
|
return repo_path
|
{specfact_cli-0.46.17 → specfact_cli-0.46.19}/src/specfact_cli/registry/module_availability.py
RENAMED
|
@@ -14,6 +14,7 @@ from specfact_cli.registry.module_discovery import DiscoveredModule, discover_al
|
|
|
14
14
|
from specfact_cli.registry.module_packages import (
|
|
15
15
|
_check_core_compatibility,
|
|
16
16
|
_validate_module_dependencies,
|
|
17
|
+
get_module_load_failure_reason,
|
|
17
18
|
merge_module_state,
|
|
18
19
|
)
|
|
19
20
|
from specfact_cli.registry.module_state import read_modules_state
|
|
@@ -104,6 +105,9 @@ def _recovery_command(status: ModuleAvailabilityStatus, module_id: str) -> str:
|
|
|
104
105
|
|
|
105
106
|
def _skip_reason(entry: DiscoveredModule, enabled_map: dict[str, bool]) -> str:
|
|
106
107
|
meta = entry.metadata
|
|
108
|
+
load_failure = get_module_load_failure_reason(meta.name, None)
|
|
109
|
+
if load_failure:
|
|
110
|
+
return load_failure
|
|
107
111
|
if not _check_core_compatibility(meta, cli_version):
|
|
108
112
|
return f"requires {meta.core_compatibility}, cli is {cli_version}"
|
|
109
113
|
deps_ok, missing = _validate_module_dependencies(meta, enabled_map)
|
|
@@ -193,7 +197,13 @@ def classify_module_availability(
|
|
|
193
197
|
command_name: str | None = None,
|
|
194
198
|
base_path: Path | None = None,
|
|
195
199
|
) -> ModuleAvailability:
|
|
196
|
-
"""
|
|
200
|
+
"""
|
|
201
|
+
Classify module availability from discovery state and process load failures.
|
|
202
|
+
|
|
203
|
+
Decisions use manifests/modules.json plus the process-scoped lazy-load failure
|
|
204
|
+
registry exposed by get_module_load_failure_reason and backed by
|
|
205
|
+
_MODULE_LOAD_FAILURES.
|
|
206
|
+
"""
|
|
197
207
|
discovered = discover_all_modules_for_project_with_shadowed(base_path)
|
|
198
208
|
matches = _availability_matches(discovered, module_id=module_id, command_name=command_name)
|
|
199
209
|
requested_id = module_id or command_name or ""
|
|
@@ -13,7 +13,12 @@ import ast
|
|
|
13
13
|
import importlib
|
|
14
14
|
import importlib.util
|
|
15
15
|
import os
|
|
16
|
+
import re
|
|
17
|
+
import site
|
|
16
18
|
import sys
|
|
19
|
+
import sysconfig
|
|
20
|
+
import tempfile
|
|
21
|
+
from contextlib import suppress
|
|
17
22
|
from dataclasses import dataclass
|
|
18
23
|
from pathlib import Path
|
|
19
24
|
from typing import Any, cast
|
|
@@ -106,6 +111,8 @@ PROTOCOL_METHODS: dict[str, str] = {
|
|
|
106
111
|
PROTOCOL_INTERFACE_BINDINGS: tuple[str, ...] = ("runtime_interface", "commands_interface", "commands")
|
|
107
112
|
BRIDGE_REGISTRY = BridgeRegistry()
|
|
108
113
|
BUILTIN_MODULES_ROOT = (Path(__file__).resolve().parents[1] / "modules").resolve()
|
|
114
|
+
_ACTIVE_MODULE_SRC_DIRS: list[Path] = []
|
|
115
|
+
_MODULE_LOAD_FAILURES: dict[tuple[str, str], str] = {}
|
|
109
116
|
|
|
110
117
|
|
|
111
118
|
def _normalized_module_name(package_name: str) -> str:
|
|
@@ -141,6 +148,51 @@ def _is_builtin_module_package(package_dir: Path) -> bool:
|
|
|
141
148
|
return False
|
|
142
149
|
|
|
143
150
|
|
|
151
|
+
def _is_under_directory(path: Path, parent: Path) -> bool:
|
|
152
|
+
try:
|
|
153
|
+
path.resolve().relative_to(parent.resolve())
|
|
154
|
+
return True
|
|
155
|
+
except ValueError:
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _installed_package_search_roots() -> tuple[Path, ...]:
|
|
160
|
+
"""Return interpreter-managed roots that can contain installed packages."""
|
|
161
|
+
roots: list[Path] = []
|
|
162
|
+
for key in ("purelib", "platlib"):
|
|
163
|
+
raw_path = sysconfig.get_paths().get(key)
|
|
164
|
+
if raw_path:
|
|
165
|
+
roots.append(Path(raw_path))
|
|
166
|
+
with suppress(AttributeError):
|
|
167
|
+
roots.extend(Path(path) for path in site.getsitepackages())
|
|
168
|
+
return tuple(dict.fromkeys(root.resolve() for root in roots if root.exists()))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _has_installed_distribution_metadata(package_dir: Path) -> bool:
|
|
172
|
+
"""Return True when package_dir has adjacent Python distribution metadata."""
|
|
173
|
+
parent = package_dir.parent
|
|
174
|
+
normalized = re.sub(r"[-_.]+", "_", package_dir.name).lower()
|
|
175
|
+
metadata_dirs = [*parent.glob("*.dist-info"), *parent.glob("*.egg-info")]
|
|
176
|
+
return any(re.sub(r"[-_.]+", "_", path.name).lower().startswith(f"{normalized}_") for path in metadata_dirs)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _is_managed_specfact_module_package(package_dir: Path) -> bool:
|
|
180
|
+
"""Return True for modules installed under a SpecFact-managed modules root."""
|
|
181
|
+
parts = package_dir.resolve().parts
|
|
182
|
+
return ".specfact" in parts and "modules" in parts
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _is_installed_module_package(package_dir: Path) -> bool:
|
|
186
|
+
"""Return True when package_dir represents an installed package, not a source checkout."""
|
|
187
|
+
if _is_builtin_module_package(package_dir):
|
|
188
|
+
return False
|
|
189
|
+
if _is_managed_specfact_module_package(package_dir):
|
|
190
|
+
return True
|
|
191
|
+
if _has_installed_distribution_metadata(package_dir):
|
|
192
|
+
return True
|
|
193
|
+
return any(_is_under_directory(package_dir, root) for root in _installed_package_search_roots())
|
|
194
|
+
|
|
195
|
+
|
|
144
196
|
@beartype
|
|
145
197
|
@ensure(lambda result: isinstance(result, list), "Must return a list of paths")
|
|
146
198
|
def get_modules_roots() -> list[Path]:
|
|
@@ -180,16 +232,16 @@ def get_modules_roots() -> list[Path]:
|
|
|
180
232
|
def get_workspace_modules_root(base_path: Path | None = None) -> Path | None:
|
|
181
233
|
"""Return nearest workspace-local .specfact/modules root from base path upward."""
|
|
182
234
|
start = base_path.resolve() if base_path is not None else Path.cwd().resolve()
|
|
235
|
+
temp_root = Path(tempfile.gettempdir()).resolve()
|
|
183
236
|
for candidate in [start, *start.parents]:
|
|
237
|
+
if candidate == temp_root and candidate != start:
|
|
238
|
+
return None
|
|
239
|
+
workspace_modules_root = candidate / ".specfact" / "modules"
|
|
240
|
+
if workspace_modules_root.exists():
|
|
241
|
+
return workspace_modules_root
|
|
184
242
|
git_dir = candidate / ".git"
|
|
185
243
|
if git_dir.exists():
|
|
186
|
-
workspace_modules_root = candidate / ".specfact" / "modules"
|
|
187
|
-
if workspace_modules_root.exists():
|
|
188
|
-
return workspace_modules_root
|
|
189
244
|
return None
|
|
190
|
-
workspace_modules_root = start / ".specfact" / "modules"
|
|
191
|
-
if workspace_modules_root.exists():
|
|
192
|
-
return workspace_modules_root
|
|
193
245
|
return None
|
|
194
246
|
|
|
195
247
|
|
|
@@ -601,10 +653,77 @@ def _resolve_command_loader_path(
|
|
|
601
653
|
return load_path, submodule_locations
|
|
602
654
|
|
|
603
655
|
|
|
656
|
+
def _remember_active_module_src(package_dir: Path) -> None:
|
|
657
|
+
"""Remember an eligible installed module source root for lazy cross-module imports."""
|
|
658
|
+
if not _is_installed_module_package(package_dir):
|
|
659
|
+
return
|
|
660
|
+
src_dir = package_dir / "src"
|
|
661
|
+
if not src_dir.is_dir():
|
|
662
|
+
return
|
|
663
|
+
resolved = src_dir.resolve()
|
|
664
|
+
if resolved not in _ACTIVE_MODULE_SRC_DIRS:
|
|
665
|
+
_ACTIVE_MODULE_SRC_DIRS.insert(0, resolved)
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def _prepend_active_module_src_roots() -> None:
|
|
669
|
+
"""Prepend eligible installed module source roots before loading a command app."""
|
|
670
|
+
for src_dir in reversed(_ACTIVE_MODULE_SRC_DIRS):
|
|
671
|
+
src = str(src_dir)
|
|
672
|
+
if src not in sys.path:
|
|
673
|
+
sys.path.insert(0, src)
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def _record_module_load_failure(package_name: str, command_name: str, reason: str) -> None:
|
|
677
|
+
_MODULE_LOAD_FAILURES[(package_name, command_name)] = reason
|
|
678
|
+
_MODULE_LOAD_FAILURES[(package_name, "*")] = reason
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def _clear_module_load_failure(package_name: str, command_name: str) -> None:
|
|
682
|
+
_MODULE_LOAD_FAILURES.pop((package_name, command_name), None)
|
|
683
|
+
has_remaining_failure = any(
|
|
684
|
+
registered_package == package_name and registered_command != "*"
|
|
685
|
+
for registered_package, registered_command in _MODULE_LOAD_FAILURES
|
|
686
|
+
)
|
|
687
|
+
if not has_remaining_failure:
|
|
688
|
+
_MODULE_LOAD_FAILURES.pop((package_name, "*"), None)
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def _clear_active_module_src_dirs() -> None:
|
|
692
|
+
for src_dir in _ACTIVE_MODULE_SRC_DIRS:
|
|
693
|
+
src = str(src_dir)
|
|
694
|
+
while src in sys.path:
|
|
695
|
+
sys.path.remove(src)
|
|
696
|
+
_ACTIVE_MODULE_SRC_DIRS.clear()
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
@beartype
|
|
700
|
+
@ensure(lambda result: result is None, "must return None")
|
|
701
|
+
def clear_module_load_failures() -> None:
|
|
702
|
+
"""Clear process-scoped lazy module load failure diagnostics."""
|
|
703
|
+
_MODULE_LOAD_FAILURES.clear()
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def _package_name_non_empty(package_name: str) -> bool:
|
|
707
|
+
return bool(package_name.strip())
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
@beartype
|
|
711
|
+
@require(_package_name_non_empty, "package name must be non-empty")
|
|
712
|
+
@ensure(lambda result: result is None or isinstance(result, str), "result must be a string or None")
|
|
713
|
+
def get_module_load_failure_reason(package_name: str, command_name: str | None = None) -> str | None:
|
|
714
|
+
"""Return the latest lazy-load failure for a module, if one was captured."""
|
|
715
|
+
if command_name is not None:
|
|
716
|
+
specific = _MODULE_LOAD_FAILURES.get((package_name, command_name))
|
|
717
|
+
if specific:
|
|
718
|
+
return specific
|
|
719
|
+
return _MODULE_LOAD_FAILURES.get((package_name, "*"))
|
|
720
|
+
|
|
721
|
+
|
|
604
722
|
def _make_package_loader(package_dir: Path, package_name: str, command_name: str) -> Any:
|
|
605
723
|
"""Return a callable that loads the package's app (from src/app.py or src/<name>/__init__.py)."""
|
|
606
724
|
|
|
607
725
|
def loader() -> Any:
|
|
726
|
+
_prepend_active_module_src_roots()
|
|
608
727
|
src_dir = package_dir / "src"
|
|
609
728
|
if str(src_dir) not in sys.path:
|
|
610
729
|
sys.path.insert(0, str(src_dir))
|
|
@@ -621,18 +740,23 @@ def _make_package_loader(package_dir: Path, package_name: str, command_name: str
|
|
|
621
740
|
sys.modules[spec.name] = mod
|
|
622
741
|
try:
|
|
623
742
|
spec.loader.exec_module(mod)
|
|
624
|
-
except
|
|
625
|
-
|
|
743
|
+
except Exception as exc:
|
|
744
|
+
message = (
|
|
626
745
|
"Runtime compatibility error while loading "
|
|
627
746
|
f"module '{package_name}' command '{command_name}' from {package_dir}: {exc}. "
|
|
628
747
|
f"Reinstall the module and run SpecFact with the same Python interpreter ({sys.executable})."
|
|
629
|
-
)
|
|
748
|
+
)
|
|
749
|
+
_record_module_load_failure(package_name, command_name, message)
|
|
750
|
+
raise ValueError(message) from exc
|
|
630
751
|
command_attr = f"{_normalized_module_name(command_name)}_app"
|
|
631
752
|
app = getattr(mod, command_attr, None)
|
|
632
753
|
if app is None:
|
|
633
754
|
app = getattr(mod, "app", None)
|
|
634
755
|
if app is None:
|
|
635
|
-
|
|
756
|
+
message = f"Package {package_dir.name} has no '{command_attr}' or 'app' attribute"
|
|
757
|
+
_record_module_load_failure(package_name, command_name, message)
|
|
758
|
+
raise ValueError(message)
|
|
759
|
+
_clear_module_load_failure(package_name, command_name)
|
|
636
760
|
return app
|
|
637
761
|
|
|
638
762
|
return loader
|
|
@@ -1331,6 +1455,7 @@ def _register_one_package_if_eligible(package_dir: Path, meta: Any, reg: _Packag
|
|
|
1331
1455
|
_register_schema_extensions_safe(meta, reg.logger)
|
|
1332
1456
|
_register_service_bridges_safe(meta, reg.bridge_owner_map, reg.logger)
|
|
1333
1457
|
_record_protocol_compliance_result(package_dir, meta, reg.logger, reg.counters)
|
|
1458
|
+
_remember_active_module_src(package_dir)
|
|
1334
1459
|
_register_commands_for_package(package_dir, meta, reg.category_grouping_enabled, reg.logger)
|
|
1335
1460
|
|
|
1336
1461
|
|
|
@@ -1382,6 +1507,8 @@ def register_module_package_commands(
|
|
|
1382
1507
|
disable_ids = disable_ids or []
|
|
1383
1508
|
if allow_unsigned is None:
|
|
1384
1509
|
allow_unsigned = os.environ.get("SPECFACT_ALLOW_UNSIGNED", "").strip().lower() in ("1", "true", "yes")
|
|
1510
|
+
_clear_active_module_src_dirs()
|
|
1511
|
+
_MODULE_LOAD_FAILURES.clear()
|
|
1385
1512
|
is_test_mode = os.environ.get("TEST_MODE") == "true" or os.environ.get("PYTEST_CURRENT_TEST") is not None
|
|
1386
1513
|
packages = discover_all_package_metadata()
|
|
1387
1514
|
packages = sorted(packages, key=_package_sort_key)
|