specfact-cli 0.46.28__tar.gz → 0.47.3__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.28 → specfact_cli-0.47.3}/PKG-INFO +6 -1
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/README.md +5 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/pyproject.toml +5 -2
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/bundled-module-registry/index.json +6 -6
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/templates/pr-template.md.j2 +1 -2
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/__init__.py +1 -1
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/__init__.py +31 -1
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/cli.py +176 -30
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/init/module-package.yaml +3 -3
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/init/src/commands.py +3 -4
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/upgrade/module-package.yaml +3 -3
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/upgrade/src/commands.py +83 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/bundle_loader.py +1 -1
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/env_manager.py +18 -4
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/github_annotations.py +2 -2
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/ide_setup.py +159 -2
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/progressive_disclosure.py +144 -14
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/structure.py +4 -4
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/suggestions.py +9 -9
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/.gitignore +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/LICENSE +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/keys/README.md +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/keys/module-signing-public.pem +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/templates/policies/kanban.yaml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/templates/policies/mixed.yaml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/templates/policies/safe.yaml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/templates/policies/scrum.yaml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/__main__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/adapters/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/adapters/ado.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/adapters/backlog_base.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/adapters/base.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/adapters/github.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/adapters/openspec.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/adapters/openspec_parser.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/adapters/registry.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/adapters/speckit.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/backlog/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/backlog/adapters/base.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/backlog/converter.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/backlog/filters.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/backlog/mappers/base.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/_bundle_shim.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/project_cmd.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/update.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/commands/validate.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/common/bundle_factory.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/contracts/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/contracts/crosshair_props.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/contracts/module_interface.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/generators/persona_exporter.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/groups/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/groups/codebase_group.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/groups/govern_group.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/groups/member_group.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/groups/project_group.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/groups/spec_group.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/backlog_item.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/capabilities.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/change.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/dor_config.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/module_package.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/project.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/models/validation.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/_bundle_import.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/init/src/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/init/src/app.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/init/src/first_run_selection.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/module_io_shim.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/module_registry/module-package.yaml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/module_registry/src/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/module_registry/src/app.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/module_registry/src/commands.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/upgrade/src/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/upgrade/src/app.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/alias_manager.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/bootstrap.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/bridge_registry.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/crypto_validator.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/custom_registries.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/dependency_resolver.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/extension_registry.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/help_cache.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/marketplace_client.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/metadata.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/module_availability.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/module_discovery.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/module_grouping.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/module_installer.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/module_lifecycle.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/module_packages.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/module_security.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/module_state.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/registry/registry.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_sync.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_sync_openspec_md_parse.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_sync_requirement_from_proposal.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_sync_requirement_helpers.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_sync_tasks_from_proposal.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_sync_what_changes_format.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_sync_write_openspec_from_proposal.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/templates/registry.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/auth_tokens.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/bundle_converters.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/code_change_detector.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/content_sanitizer.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/contract_predicates.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/icontract_helpers.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/metadata.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/persona_ownership.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/project_artifact_write.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/startup_checks.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/terminal.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validation/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validation/command_audit.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/schema.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/models.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/versioning/analyzer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specfact-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.47.3
|
|
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
|
|
@@ -299,6 +299,11 @@ Description-Content-Type: text/markdown
|
|
|
299
299
|
|
|
300
300
|
</div>
|
|
301
301
|
|
|
302
|
+
## Command Overview
|
|
303
|
+
|
|
304
|
+
- [Generated command overview for humans](docs/reference/commands.generated.md)
|
|
305
|
+
- [AI-agent command overview](llms.txt)
|
|
306
|
+
|
|
302
307
|
## Try it in 60 seconds
|
|
303
308
|
|
|
304
309
|
```bash
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specfact-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.47.3"
|
|
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"
|
|
@@ -247,12 +247,15 @@ yaml-check-all = "bash scripts/yaml-tools.sh check-all {args}"
|
|
|
247
247
|
# Docs validation (docs-12): command examples vs CLI; modules.specfact.io URLs in docs
|
|
248
248
|
check-docs-commands = "python scripts/check-docs-commands.py"
|
|
249
249
|
check-cross-site-links = "python scripts/check-cross-site-links.py"
|
|
250
|
+
generate-command-overview = "python scripts/generate-command-overview.py --write"
|
|
251
|
+
check-command-overview = "python scripts/generate-command-overview.py --check"
|
|
252
|
+
check-command-contract = "python scripts/check-command-contract.py"
|
|
250
253
|
doc-frontmatter-check = "python scripts/check_doc_frontmatter.py"
|
|
251
254
|
validate-agent-rule-signals = "python scripts/validate_agent_rule_applies_when.py"
|
|
252
255
|
check-version-sources = "python scripts/check_version_sources.py"
|
|
253
256
|
check-pypi-ahead = "python scripts/check_local_version_ahead_of_pypi.py"
|
|
254
257
|
release = "python scripts/check_local_version_ahead_of_pypi.py && python scripts/check_version_sources.py"
|
|
255
|
-
docs-validate = "python scripts/check-docs-commands.py && python scripts/check-cross-site-links.py --warn-only && python scripts/check_doc_frontmatter.py && python scripts/validate_agent_rule_applies_when.py"
|
|
258
|
+
docs-validate = "python scripts/generate-command-overview.py --check && python scripts/check-command-contract.py && python scripts/check-docs-commands.py && python scripts/check-cross-site-links.py --warn-only && python scripts/check_doc_frontmatter.py && python scripts/validate_agent_rule_applies_when.py"
|
|
256
259
|
|
|
257
260
|
# Legacy entry (kept for compatibility); prefer `workflows-lint` above
|
|
258
261
|
lint-workflows = "bash scripts/run_actionlint.sh {args}"
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"modules": [
|
|
3
3
|
{
|
|
4
|
-
"checksum_sha256": "
|
|
5
|
-
"download_url": "https://github.com/nold-ai/specfact-cli/releases/download/init-0.1.
|
|
4
|
+
"checksum_sha256": "3a563335d7a5e16e6e9ec975d6e5c77ff550d8880474eca03713b43e97d39a4a",
|
|
5
|
+
"download_url": "https://github.com/nold-ai/specfact-cli/releases/download/init-0.1.37.tar.gz",
|
|
6
6
|
"id": "init",
|
|
7
|
-
"latest_version": "0.1.
|
|
7
|
+
"latest_version": "0.1.37"
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
|
-
"checksum_sha256": "
|
|
11
|
-
"download_url": "https://github.com/nold-ai/specfact-cli/releases/download/upgrade-0.1.
|
|
10
|
+
"checksum_sha256": "35c592374b209224af9a826c21efa66947cc16bf680b20ee0463f44bee4f9b59",
|
|
11
|
+
"download_url": "https://github.com/nold-ai/specfact-cli/releases/download/upgrade-0.1.21.tar.gz",
|
|
12
12
|
"id": "upgrade",
|
|
13
|
-
"latest_version": "0.1.
|
|
13
|
+
"latest_version": "0.1.21"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"checksum_sha256": "79359da27cb1c734a928a453a786b626e7b06654f515f32ead0cf96fa8e70bc2",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
- **Non-interactive / CI** must bootstrap workflow bundles before `specfact code …` or `specfact project …`:
|
|
21
21
|
- `specfact init --profile solo-developer --repo .` (or another profile / `specfact init --install …`), **or**
|
|
22
22
|
- `specfact module install nold-ai/specfact-codebase` (and other bundles as needed).
|
|
23
|
-
- Contract repro in CI uses **`specfact code repro
|
|
23
|
+
- Contract repro in CI uses **`specfact code repro`**. Optional: `specfact code repro setup` for CrossHair config.
|
|
24
24
|
- Optional `specfact project version check` needs a project under `.specfact/projects/<bundle>/` and `--bundle <name>`.
|
|
25
25
|
|
|
26
26
|
**SpecFact CLI Validation Results:**
|
|
@@ -64,4 +64,3 @@
|
|
|
64
64
|
## 📝 Notes
|
|
65
65
|
|
|
66
66
|
{{ notes | default("Add any additional notes here") }}
|
|
67
|
-
|
|
@@ -13,6 +13,7 @@ to ``sys.path`` so local development can load marketplace packages without insta
|
|
|
13
13
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
+
import importlib.util
|
|
16
17
|
import os
|
|
17
18
|
import sys
|
|
18
19
|
from pathlib import Path
|
|
@@ -25,6 +26,12 @@ def _candidate_modules_repo_roots() -> list[Path]:
|
|
|
25
26
|
roots.append(Path(configured).expanduser())
|
|
26
27
|
|
|
27
28
|
this_file = Path(__file__).resolve()
|
|
29
|
+
parts = this_file.parts
|
|
30
|
+
if "specfact-cli-worktrees" in parts:
|
|
31
|
+
marker_index = parts.index("specfact-cli-worktrees")
|
|
32
|
+
base = Path(*parts[:marker_index])
|
|
33
|
+
suffix = Path(*parts[marker_index + 1 : -3])
|
|
34
|
+
roots.append(base / "specfact-cli-modules-worktrees" / suffix)
|
|
28
35
|
for base in (this_file.parent.parent.parent, *this_file.parents):
|
|
29
36
|
roots.append(base / "specfact-cli-modules")
|
|
30
37
|
roots.append(base.parent / "specfact-cli-modules")
|
|
@@ -45,6 +52,29 @@ def _bootstrap_bundle_paths() -> None:
|
|
|
45
52
|
|
|
46
53
|
_bootstrap_bundle_paths()
|
|
47
54
|
|
|
48
|
-
|
|
55
|
+
|
|
56
|
+
def _install_progressive_disclosure() -> None:
|
|
57
|
+
module_name = "_specfact_progressive_disclosure_bootstrap"
|
|
58
|
+
if module_name in sys.modules:
|
|
59
|
+
return
|
|
60
|
+
module_path = Path(__file__).resolve().parent / "utils" / "progressive_disclosure.py"
|
|
61
|
+
spec = importlib.util.spec_from_file_location(module_name, module_path)
|
|
62
|
+
if spec is None or spec.loader is None:
|
|
63
|
+
return
|
|
64
|
+
module = importlib.util.module_from_spec(spec)
|
|
65
|
+
sys.modules[module_name] = module
|
|
66
|
+
try:
|
|
67
|
+
spec.loader.exec_module(module)
|
|
68
|
+
except Exception:
|
|
69
|
+
sys.modules.pop(module_name, None)
|
|
70
|
+
raise
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Install the shared Click/Typer usage-error contract as soon as core is imported.
|
|
74
|
+
# Module packages import specfact_cli before constructing direct Typer apps, so this
|
|
75
|
+
# keeps missing-command and missing-parameter UX consistent outside the root CLI too.
|
|
76
|
+
_install_progressive_disclosure()
|
|
77
|
+
|
|
78
|
+
__version__ = "0.47.3"
|
|
49
79
|
|
|
50
80
|
__all__ = ["__version__"]
|
|
@@ -11,6 +11,7 @@ import inspect
|
|
|
11
11
|
import os
|
|
12
12
|
import sys
|
|
13
13
|
from collections.abc import Callable, Mapping
|
|
14
|
+
from contextlib import suppress
|
|
14
15
|
from dataclasses import dataclass
|
|
15
16
|
from datetime import datetime
|
|
16
17
|
from pathlib import Path
|
|
@@ -72,6 +73,12 @@ from specfact_cli.utils.progressive_disclosure import ProgressiveDisclosureGroup
|
|
|
72
73
|
from specfact_cli.utils.structured_io import StructuredFormat
|
|
73
74
|
|
|
74
75
|
|
|
76
|
+
try:
|
|
77
|
+
from typer._click.exceptions import UsageError as TyperUsageError
|
|
78
|
+
except ImportError: # pragma: no cover - older Typer delegates directly to Click
|
|
79
|
+
TyperUsageError = click.UsageError # type: ignore[assignment,misc]
|
|
80
|
+
|
|
81
|
+
|
|
75
82
|
# Names of commands that come from installable bundles; when not registered, show actionable error.
|
|
76
83
|
KNOWN_BUNDLE_GROUP_OR_SHIM_NAMES: frozenset[str] = frozenset(
|
|
77
84
|
{
|
|
@@ -165,9 +172,7 @@ class _RootCLIGroup(ProgressiveDisclosureGroup):
|
|
|
165
172
|
"""Root group that shows actionable error when an unknown command is a known bundle group/shim."""
|
|
166
173
|
|
|
167
174
|
@ensure(lambda result: isinstance(result, tuple) and len(result) == 3, "result must be a 3-tuple")
|
|
168
|
-
def resolve_command(
|
|
169
|
-
self, ctx: click.Context, args: list[str]
|
|
170
|
-
) -> tuple[str | None, click.Command | None, list[str]]:
|
|
175
|
+
def resolve_command(self, ctx: Any, args: list[str]) -> Any:
|
|
171
176
|
if not args:
|
|
172
177
|
return super().resolve_command(ctx, args)
|
|
173
178
|
invoked = args[0]
|
|
@@ -567,6 +572,10 @@ def _raise_lazy_delegate_click_exception(exc: Exception) -> NoReturn:
|
|
|
567
572
|
raise click.ClickException(str(exc)) from exc
|
|
568
573
|
|
|
569
574
|
|
|
575
|
+
def _is_group_like(command: object) -> bool:
|
|
576
|
+
return hasattr(command, "list_commands") and hasattr(command, "get_command")
|
|
577
|
+
|
|
578
|
+
|
|
570
579
|
def _load_lazy_delegate_typer(cmd_name: str) -> typer.Typer:
|
|
571
580
|
resolved_name = resolve_command(cmd_name)
|
|
572
581
|
try:
|
|
@@ -583,7 +592,7 @@ def _build_lazy_delegate_click_command(cmd_name: str, args: tuple[str, ...], rea
|
|
|
583
592
|
from typer.main import get_command
|
|
584
593
|
|
|
585
594
|
try:
|
|
586
|
-
return get_command(real_typer)
|
|
595
|
+
return cast(click.Command, get_command(real_typer))
|
|
587
596
|
except (RuntimeError, ValueError) as exc:
|
|
588
597
|
if _args_request_help(args):
|
|
589
598
|
_print_lazy_help_fallback(cmd_name, args)
|
|
@@ -593,6 +602,15 @@ def _build_lazy_delegate_click_command(cmd_name: str, args: tuple[str, ...], rea
|
|
|
593
602
|
|
|
594
603
|
|
|
595
604
|
def _lazy_delegate_prog_name(ctx: click.Context, cmd_name: str) -> str:
|
|
605
|
+
original_prog_name = ctx.meta.get("original_prog_name")
|
|
606
|
+
if not isinstance(original_prog_name, str) and ctx.parent is not None:
|
|
607
|
+
original_prog_name = ctx.parent.meta.get("original_prog_name")
|
|
608
|
+
if isinstance(original_prog_name, str) and original_prog_name:
|
|
609
|
+
return original_prog_name
|
|
610
|
+
if isinstance(ctx.info_name, str) and " " in ctx.info_name:
|
|
611
|
+
return ctx.info_name
|
|
612
|
+
if isinstance(ctx.command_path, str) and " " in ctx.command_path:
|
|
613
|
+
return ctx.command_path
|
|
596
614
|
parts: list[str] = []
|
|
597
615
|
parent = ctx.parent
|
|
598
616
|
while parent and getattr(parent, "command", None):
|
|
@@ -601,20 +619,46 @@ def _lazy_delegate_prog_name(ctx: click.Context, cmd_name: str) -> str:
|
|
|
601
619
|
parts.append(name)
|
|
602
620
|
parent = getattr(parent, "parent", None)
|
|
603
621
|
if parts:
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
return
|
|
622
|
+
prog_name = " ".join(reversed(parts))
|
|
623
|
+
if prog_name == cmd_name:
|
|
624
|
+
return f"specfact {cmd_name}"
|
|
625
|
+
return prog_name
|
|
608
626
|
return cmd_name
|
|
609
627
|
|
|
610
628
|
|
|
611
629
|
def _strip_redundant_single_command_arg(click_cmd: click.Command, args: tuple[str, ...]) -> list[str]:
|
|
612
630
|
args_list = list(args)
|
|
613
|
-
if not
|
|
631
|
+
if not _is_group_like(click_cmd) and args_list and args_list[0] == getattr(click_cmd, "name", None):
|
|
614
632
|
return args_list[1:]
|
|
615
633
|
return args_list
|
|
616
634
|
|
|
617
635
|
|
|
636
|
+
def _help_args_before_first_option(args: list[str]) -> list[str]:
|
|
637
|
+
help_args: list[str] = []
|
|
638
|
+
for arg in args:
|
|
639
|
+
if arg.startswith("-"):
|
|
640
|
+
break
|
|
641
|
+
help_args.append(arg)
|
|
642
|
+
return help_args
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def _lazy_usage_error_message(exc: Exception, command: click.Command, ctx: click.Context | None) -> str:
|
|
646
|
+
message = str(getattr(exc, "format_message", lambda: str(exc))())
|
|
647
|
+
if not message.strip().lower().startswith("missing command"):
|
|
648
|
+
return message
|
|
649
|
+
names: list[str] = []
|
|
650
|
+
group = ctx.command if ctx is not None else command
|
|
651
|
+
if _is_group_like(group):
|
|
652
|
+
try:
|
|
653
|
+
command_ctx = ctx or click.Context(cast(click.Command, group))
|
|
654
|
+
names = [str(name) for name in group.list_commands(command_ctx)]
|
|
655
|
+
except Exception:
|
|
656
|
+
names = []
|
|
657
|
+
if names:
|
|
658
|
+
return f"Missing subcommand. Choose one of: {', '.join(names)}."
|
|
659
|
+
return "Missing subcommand."
|
|
660
|
+
|
|
661
|
+
|
|
618
662
|
def _lazy_delegate_remaining_args(ctx: click.Context) -> list[str]:
|
|
619
663
|
ctx_state = vars(ctx)
|
|
620
664
|
protected_args = ctx_state.get("_protected_args") or ctx_state.get("protected_args") or ()
|
|
@@ -632,7 +676,11 @@ class _LazyDelegateGroup(click.Group):
|
|
|
632
676
|
super().__init__(
|
|
633
677
|
name=name or cmd_name,
|
|
634
678
|
help=help or help_str,
|
|
635
|
-
context_settings={
|
|
679
|
+
context_settings={
|
|
680
|
+
"ignore_unknown_options": True,
|
|
681
|
+
"allow_extra_args": True,
|
|
682
|
+
"allow_interspersed_args": False,
|
|
683
|
+
},
|
|
636
684
|
invoke_without_command=True,
|
|
637
685
|
no_args_is_help=False,
|
|
638
686
|
)
|
|
@@ -641,37 +689,75 @@ class _LazyDelegateGroup(click.Group):
|
|
|
641
689
|
self._delegate_cmd = self._make_delegate_command()
|
|
642
690
|
|
|
643
691
|
def _make_delegate_command(self) -> click.Command:
|
|
644
|
-
cmd_name = self._lazy_cmd_name
|
|
645
|
-
|
|
646
692
|
def _invoke(args: tuple[str, ...]) -> None:
|
|
647
|
-
|
|
648
|
-
real_typer = _load_lazy_delegate_typer(cmd_name)
|
|
649
|
-
click_cmd = _build_lazy_delegate_click_command(cmd_name, args, real_typer)
|
|
650
|
-
# Build full prog name from root (e.g. "specfact sync") so usage shows "specfact sync bridge", not "sync sync bridge"
|
|
651
|
-
prog_name = _lazy_delegate_prog_name(ctx, cmd_name)
|
|
652
|
-
# When the real app is a single command (e.g. drift has only "detect"), Typer
|
|
653
|
-
# builds a TyperCommand, not a Group. Then args are ["detect", "bundle", "--repo", ...]
|
|
654
|
-
# and the command expects ["bundle", "--repo", ...] (no leading "detect").
|
|
655
|
-
args_list = _strip_redundant_single_command_arg(click_cmd, args)
|
|
656
|
-
exit_code = click_cmd.main(args=args_list, prog_name=prog_name, standalone_mode=False)
|
|
657
|
-
if exit_code and exit_code != 0:
|
|
658
|
-
raise SystemExit(exit_code)
|
|
693
|
+
self._invoke_real_command(click.get_current_context(), args)
|
|
659
694
|
|
|
660
695
|
return click.Command(
|
|
661
696
|
"__delegate__",
|
|
662
697
|
callback=_invoke,
|
|
663
698
|
params=[click.Argument(["args"], nargs=-1, type=click.UNPROCESSED)],
|
|
664
|
-
context_settings={
|
|
699
|
+
context_settings={
|
|
700
|
+
"ignore_unknown_options": True,
|
|
701
|
+
"allow_extra_args": True,
|
|
702
|
+
"allow_interspersed_args": False,
|
|
703
|
+
},
|
|
665
704
|
add_help_option=False, # Pass --help through to real Typer so "specfact backlog daily ado --help" shows correct usage
|
|
666
705
|
)
|
|
667
706
|
|
|
707
|
+
def _invoke_real_command(self, ctx: click.Context, args: tuple[str, ...] | list[str]) -> None:
|
|
708
|
+
cmd_name = self._lazy_cmd_name
|
|
709
|
+
real_typer = _load_lazy_delegate_typer(cmd_name)
|
|
710
|
+
args_tuple = tuple(args)
|
|
711
|
+
click_cmd = _build_lazy_delegate_click_command(cmd_name, args_tuple, real_typer)
|
|
712
|
+
prog_name = _lazy_delegate_prog_name(ctx, cmd_name)
|
|
713
|
+
args_list = _strip_redundant_single_command_arg(click_cmd, args_tuple)
|
|
714
|
+
try:
|
|
715
|
+
exit_code = click_cmd.main(args=args_list, prog_name=prog_name, standalone_mode=False)
|
|
716
|
+
except (click.UsageError, TyperUsageError) as exc:
|
|
717
|
+
if exc.ctx is None:
|
|
718
|
+
help_args = _help_args_before_first_option(args_list)
|
|
719
|
+
try:
|
|
720
|
+
click_cmd.main(args=[*help_args, "--help"], prog_name=prog_name, standalone_mode=False)
|
|
721
|
+
except BaseException as help_exit:
|
|
722
|
+
if not help_exit.__class__.__name__.endswith("Exit"):
|
|
723
|
+
raise
|
|
724
|
+
click.echo(f"Error: {_lazy_usage_error_message(exc, click_cmd, None)}", file=sys.stderr)
|
|
725
|
+
else:
|
|
726
|
+
# Help rendering is best-effort; the primary usage error is emitted below.
|
|
727
|
+
with suppress(Exception):
|
|
728
|
+
click.echo(exc.ctx.get_help(), file=sys.stderr)
|
|
729
|
+
click.echo("", file=sys.stderr)
|
|
730
|
+
click.echo(f"Error: {_lazy_usage_error_message(exc, click_cmd, exc.ctx)}", file=sys.stderr)
|
|
731
|
+
raise SystemExit(exc.exit_code) from None
|
|
732
|
+
except SystemExit as exc:
|
|
733
|
+
code = exc.code if isinstance(exc.code, int) else 1 if exc.code else 0
|
|
734
|
+
raise SystemExit(code) from None
|
|
735
|
+
except BaseException as exc:
|
|
736
|
+
if exc.__class__.__name__.endswith("Exit"):
|
|
737
|
+
raise SystemExit(getattr(exc, "exit_code", getattr(exc, "code", 0))) from None
|
|
738
|
+
raise
|
|
739
|
+
if exit_code and exit_code != 0:
|
|
740
|
+
raise SystemExit(exit_code)
|
|
741
|
+
|
|
742
|
+
def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
|
|
743
|
+
if _args_request_help(args):
|
|
744
|
+
try:
|
|
745
|
+
return super().parse_args(ctx, args)
|
|
746
|
+
except click.exceptions.Exit as exc:
|
|
747
|
+
raise SystemExit(exc.exit_code) from None
|
|
748
|
+
ctx.args = list(args)
|
|
749
|
+
return []
|
|
750
|
+
|
|
668
751
|
@require(lambda ctx: ctx is not None, "ctx must not be None")
|
|
669
752
|
@ensure(lambda result: result is None or isinstance(result, int), "result must be None or an exit code")
|
|
670
753
|
def invoke(self, ctx: click.Context) -> Any:
|
|
671
754
|
if ctx.invoked_subcommand is None:
|
|
672
755
|
args = _lazy_delegate_remaining_args(ctx)
|
|
673
|
-
|
|
674
|
-
|
|
756
|
+
command_path = ctx.command_path
|
|
757
|
+
if command_path == self._lazy_cmd_name:
|
|
758
|
+
command_path = f"specfact {self._lazy_cmd_name}"
|
|
759
|
+
ctx.meta["original_prog_name"] = command_path
|
|
760
|
+
return self._invoke_real_command(ctx, args)
|
|
675
761
|
return super().invoke(ctx)
|
|
676
762
|
|
|
677
763
|
@require(_lazy_delegate_cmd_name_ready, "lazy command name must be set")
|
|
@@ -712,8 +798,8 @@ class _LazyDelegateGroup(click.Group):
|
|
|
712
798
|
click_cmd = get_command(real_typer)
|
|
713
799
|
except (RuntimeError, ValueError):
|
|
714
800
|
return None
|
|
715
|
-
if
|
|
716
|
-
return click_cmd
|
|
801
|
+
if _is_group_like(click_cmd):
|
|
802
|
+
return cast(click.Group, click_cmd)
|
|
717
803
|
return None
|
|
718
804
|
|
|
719
805
|
@require(_lazy_delegate_cmd_name_ready, "lazy command name must be set before formatting help")
|
|
@@ -736,6 +822,10 @@ class _LazyDelegateGroup(click.Group):
|
|
|
736
822
|
click_cmd.main(args=["-h"], prog_name=prog_name, standalone_mode=False)
|
|
737
823
|
except SystemExit:
|
|
738
824
|
raise # Re-raise so process exits (help was already printed with Rich)
|
|
825
|
+
except BaseException as exc:
|
|
826
|
+
if exc.__class__.__name__.endswith("Exit"):
|
|
827
|
+
raise SystemExit(getattr(exc, "exit_code", 0)) from None
|
|
828
|
+
raise
|
|
739
829
|
# main() returned without exiting; Rich help was already printed, skip default formatter
|
|
740
830
|
return
|
|
741
831
|
|
|
@@ -748,7 +838,7 @@ def _build_lazy_delegate_group(cmd_name: str, help_str: str) -> click.Group:
|
|
|
748
838
|
def _flatten_specfact_nested_subgroup(result: click.Group, flatten_name: str) -> None:
|
|
749
839
|
"""Merge a nested subgroup named `flatten_name` into its parent and re-sort command order."""
|
|
750
840
|
redundant = result.commands.pop(flatten_name)
|
|
751
|
-
if
|
|
841
|
+
if _is_group_like(redundant):
|
|
752
842
|
for cmd_name, cmd in redundant.commands.items():
|
|
753
843
|
result.add_command(cmd, name=cmd_name)
|
|
754
844
|
if result.commands:
|
|
@@ -814,6 +904,7 @@ def _get_group_from_info_wrapper(
|
|
|
814
904
|
_typer_get_group_from_info_original: Callable[..., click.Group] | None = None
|
|
815
905
|
_typer_get_command_original: Callable[[typer.Typer], click.Command] | None = None
|
|
816
906
|
_typer_get_params_original: Callable[..., Any] | None = None
|
|
907
|
+
_typer_get_params_convertors_original: Callable[..., Any] | None = None
|
|
817
908
|
|
|
818
909
|
|
|
819
910
|
def _specfact_get_params_from_function(func: Callable[..., Any]) -> Any:
|
|
@@ -838,6 +929,55 @@ def _specfact_get_params_from_function(func: Callable[..., Any]) -> Any:
|
|
|
838
929
|
return orig(func)
|
|
839
930
|
|
|
840
931
|
|
|
932
|
+
def _is_context_annotation(annotation: object) -> bool:
|
|
933
|
+
if annotation in (click.Context, typer.Context):
|
|
934
|
+
return True
|
|
935
|
+
return getattr(annotation, "__name__", "") == "Context" and "click" in getattr(annotation, "__module__", "")
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
def _specfact_get_params_convertors_ctx_param_name_from_function(callback: Callable[..., Any] | None) -> Any:
|
|
939
|
+
"""Treat both Click context classes as Typer context parameters across Typer releases."""
|
|
940
|
+
assert _typer_get_params_convertors_original is not None
|
|
941
|
+
original_error: RuntimeError | None = None
|
|
942
|
+
try:
|
|
943
|
+
return _typer_get_params_convertors_original(callback)
|
|
944
|
+
except RuntimeError as exc:
|
|
945
|
+
if callback is None or "click.core.Context" not in str(exc):
|
|
946
|
+
raise
|
|
947
|
+
original_error = exc
|
|
948
|
+
import typer.utils as typer_utils
|
|
949
|
+
|
|
950
|
+
params = _specfact_get_params_from_function(callback)
|
|
951
|
+
ctx_param_name: str | None = None
|
|
952
|
+
filtered_params: dict[str, Any] = {}
|
|
953
|
+
for name, param in params.items():
|
|
954
|
+
if ctx_param_name is None and _is_context_annotation(getattr(param, "annotation", None)):
|
|
955
|
+
ctx_param_name = name
|
|
956
|
+
continue
|
|
957
|
+
filtered_params[name] = param
|
|
958
|
+
|
|
959
|
+
if ctx_param_name is None:
|
|
960
|
+
raise RuntimeError("Unable to identify Click context parameter") from original_error
|
|
961
|
+
|
|
962
|
+
typer_main = cast(Any, importlib.import_module("typer.main"))
|
|
963
|
+
previous_main_get_params = typer_main.get_params_from_function
|
|
964
|
+
previous_utils_get_params = typer_utils.get_params_from_function
|
|
965
|
+
|
|
966
|
+
def _filtered_get_params(func: Callable[..., Any]) -> Any:
|
|
967
|
+
if func is callback:
|
|
968
|
+
return filtered_params
|
|
969
|
+
return previous_main_get_params(func)
|
|
970
|
+
|
|
971
|
+
try:
|
|
972
|
+
typer_main.get_params_from_function = _filtered_get_params
|
|
973
|
+
typer_utils.get_params_from_function = _filtered_get_params
|
|
974
|
+
click_params, convertors, _ignored_ctx = _typer_get_params_convertors_original(callback)
|
|
975
|
+
finally:
|
|
976
|
+
typer_main.get_params_from_function = previous_main_get_params
|
|
977
|
+
typer_utils.get_params_from_function = previous_utils_get_params
|
|
978
|
+
return click_params, convertors, ctx_param_name
|
|
979
|
+
|
|
980
|
+
|
|
841
981
|
# Patch so root app build uses our delegate group for lazy typers (built via get_group_from_info).
|
|
842
982
|
def _patch_typer_build() -> None:
|
|
843
983
|
import typer.utils as typer_utils
|
|
@@ -845,6 +985,7 @@ def _patch_typer_build() -> None:
|
|
|
845
985
|
typer_main = cast(Any, importlib.import_module("typer.main"))
|
|
846
986
|
|
|
847
987
|
global _typer_get_group_from_info_original, _typer_get_command_original, _typer_get_params_original
|
|
988
|
+
global _typer_get_params_convertors_original
|
|
848
989
|
# Save originals only on first patch; avoid overwriting with our wrapper when cli is re-imported (e.g. by plan module).
|
|
849
990
|
if _typer_get_group_from_info_original is None:
|
|
850
991
|
_typer_get_group_from_info_original = typer_main.get_group_from_info
|
|
@@ -852,9 +993,14 @@ def _patch_typer_build() -> None:
|
|
|
852
993
|
_typer_get_command_original = typer_main.get_command
|
|
853
994
|
if _typer_get_params_original is None:
|
|
854
995
|
_typer_get_params_original = typer_utils.get_params_from_function
|
|
996
|
+
if _typer_get_params_convertors_original is None:
|
|
997
|
+
_typer_get_params_convertors_original = typer_main.get_params_convertors_ctx_param_name_from_function
|
|
855
998
|
typer_utils.get_params_from_function = _specfact_get_params_from_function
|
|
856
999
|
# typer.main may have bound get_params_from_function at import time; keep in sync.
|
|
857
1000
|
typer_main.get_params_from_function = _specfact_get_params_from_function
|
|
1001
|
+
typer_main.get_params_convertors_ctx_param_name_from_function = (
|
|
1002
|
+
_specfact_get_params_convertors_ctx_param_name_from_function
|
|
1003
|
+
)
|
|
858
1004
|
typer_main.get_command = _get_command
|
|
859
1005
|
typer_main.get_group_from_info = _get_group_from_info_wrapper
|
|
860
1006
|
|
{specfact_cli-0.46.28 → specfact_cli-0.47.3}/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.37
|
|
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:8741e8fa3408bd38e7d65d0784a25466369955dd9f16f1981a52c0e1550afbe0
|
|
21
|
+
signature: kPAMdqpcAr92gr+DwxWibXfD0mgCLSb78EwGzoXH3fHct7xss8zt/SFvJLVZ0qkYHA8/4Nu6ls0iihRN9DXhCw==
|
|
@@ -7,7 +7,6 @@ import subprocess
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any, cast
|
|
9
9
|
|
|
10
|
-
import click
|
|
11
10
|
import typer
|
|
12
11
|
from beartype import beartype
|
|
13
12
|
from icontract import ensure, require
|
|
@@ -189,6 +188,7 @@ import_to_bundle = module_io_shim.import_to_bundle
|
|
|
189
188
|
export_from_bundle = module_io_shim.export_from_bundle
|
|
190
189
|
sync_with_bundle = module_io_shim.sync_with_bundle
|
|
191
190
|
validate_bundle = module_io_shim.validate_bundle
|
|
191
|
+
IDE_TARGETS_HELP = ", ".join([*sorted(IDE_CONFIG.keys()), "auto"])
|
|
192
192
|
|
|
193
193
|
|
|
194
194
|
def _install_contract_enhancement_dependencies(repo_path: Path, env_info: EnvManagerInfo) -> None:
|
|
@@ -614,7 +614,7 @@ def init_ide(
|
|
|
614
614
|
ide: str | None = typer.Option(
|
|
615
615
|
None,
|
|
616
616
|
"--ide",
|
|
617
|
-
help="IDE
|
|
617
|
+
help=f"IDE/agent target ({IDE_TARGETS_HELP})",
|
|
618
618
|
),
|
|
619
619
|
env_manager: EnvManager = typer.Option(
|
|
620
620
|
EnvManager.AUTO,
|
|
@@ -712,9 +712,8 @@ def init_ide(
|
|
|
712
712
|
@app.callback(invoke_without_command=True)
|
|
713
713
|
@require(lambda repo: _is_valid_repo_path(repo), "Repo path must exist and be directory")
|
|
714
714
|
@ensure(lambda result: result is None, "Command should return None")
|
|
715
|
-
@beartype
|
|
716
715
|
def init(
|
|
717
|
-
ctx:
|
|
716
|
+
ctx: typer.Context,
|
|
718
717
|
repo: Path = typer.Option(
|
|
719
718
|
Path("."),
|
|
720
719
|
"--repo",
|
{specfact_cli-0.46.28 → specfact_cli-0.47.3}/src/specfact_cli/modules/upgrade/module-package.yaml
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name: upgrade
|
|
2
|
-
version: 0.1.
|
|
2
|
+
version: 0.1.21
|
|
3
3
|
commands:
|
|
4
4
|
- upgrade
|
|
5
5
|
category: core
|
|
@@ -17,5 +17,5 @@ publisher:
|
|
|
17
17
|
description: Check and apply SpecFact CLI version upgrades.
|
|
18
18
|
license: Apache-2.0
|
|
19
19
|
integrity:
|
|
20
|
-
checksum: sha256:
|
|
21
|
-
signature:
|
|
20
|
+
checksum: sha256:eae81fc948d038dc857827de5c9c5363c963dea13af9e78395b71aeb5411742b
|
|
21
|
+
signature: H8A/ySECBGR+f1iCNepv434cQ7MazJr1RMRKoDCPS9IuIoMmL3nm4HNOSBFWPTqeFVgTbMWlISPUSjFaF4AoDg==
|