specfact-cli 0.37.5__tar.gz → 0.38.1__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.37.5 → specfact_cli-0.38.1}/.gitignore +3 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/PKG-INFO +1 -1
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/pyproject.toml +1 -1
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/__init__.py +1 -1
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/ado.py +30 -16
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/mappers/ado_mapper.py +3 -2
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/cli.py +7 -3
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/module_registry/module-package.yaml +3 -3
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/module_registry/src/commands.py +140 -21
- specfact_cli-0.38.1/src/specfact_cli/registry/alias_manager.py +94 -0
- specfact_cli-0.38.1/src/specfact_cli/registry/custom_registries.py +139 -0
- specfact_cli-0.38.1/src/specfact_cli/registry/dependency_resolver.py +109 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/marketplace_client.py +39 -3
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/module_installer.py +49 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/LICENSE +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/README.md +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/keys/README.md +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/keys/module-signing-public.pem +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/shared/cli-enforcement.md +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.01-import.md +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.02-plan.md +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.03-review.md +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.04-sdd.md +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.05-enforce.md +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.06-sync.md +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.07-contracts.md +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.backlog-add.md +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.backlog-daily.md +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.backlog-refine.md +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.compare.md +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.sync-backlog.md +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/prompts/specfact.validate.md +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/field_mappings/ado_agile.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/field_mappings/ado_default.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/field_mappings/ado_kanban.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/field_mappings/ado_safe.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/field_mappings/ado_scrum.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/frameworks/safe/safe_feature_v1.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/personas/developer/developer_task_v1.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/backlog/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/policies/kanban.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/policies/mixed.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/policies/safe.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/policies/scrum.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/__main__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/backlog_base.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/base.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/github.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/openspec.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/openspec_parser.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/registry.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/adapters/speckit.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/adapters/base.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/adapters/local_yaml_adapter.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/ai_refiner.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/converter.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/filters.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/format_detector.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/formats/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/formats/base.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/formats/markdown_format.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/formats/structured_format.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/mappers/base.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/backlog/template_detector.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/auth.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/backlog_commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/project_cmd.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/update.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/commands/validate.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/contracts/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/contracts/crosshair_props.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/contracts/module_interface.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/persona_exporter.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/backlog_item.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/capabilities.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/change.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/dor_config.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/module_package.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/project.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/models/validation.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/analyze/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/analyze/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/analyze/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/analyze/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/auth/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/auth/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/auth/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/auth/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/adapters/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/adapters/ado.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/adapters/base.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/adapters/github.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/adapters/jira.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/adapters/linear.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/backlog/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/contract/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/contract/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/contract/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/contract/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/drift/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/drift/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/drift/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/drift/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/enforce/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/enforce/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/enforce/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/enforce/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/generate/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/generate/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/generate/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/generate/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/import_cmd/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/import_cmd/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/import_cmd/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/import_cmd/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/init/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/init/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/init/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/init/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/migrate/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/migrate/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/migrate/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/migrate/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/module_io_shim.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/module_registry/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/module_registry/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/patch_mode/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/patch_mode/commands/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/patch_mode/commands/apply.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/patch_mode/pipeline/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/patch_mode/pipeline/applier.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/patch_mode/pipeline/generator.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/patch_mode/src/patch_mode/pipeline/idempotency.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/plan/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/plan/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/plan/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/plan/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/config/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/config/policy_config.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/config/templates.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/engine/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/engine/suggester.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/engine/validator.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/main.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/models/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/models/policy_result.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/policies/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/policies/kanban.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/policies/safe.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/policies/scrum.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/registry/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/policy_engine/src/policy_engine/registry/policy_registry.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/project/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/project/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/project/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/project/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/repro/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/repro/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/repro/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/repro/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/sdd/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/sdd/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/sdd/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/sdd/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/spec/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/spec/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/spec/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/spec/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/sync/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/sync/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/sync/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/sync/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/upgrade/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/upgrade/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/upgrade/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/upgrade/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/validate/module-package.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/validate/src/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/validate/src/app.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/validate/src/commands.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/bootstrap.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/bridge_registry.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/crypto_validator.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/extension_registry.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/help_cache.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/metadata.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/module_discovery.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/module_lifecycle.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/module_packages.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/module_security.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/module_state.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/registry/registry.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/bridge_sync.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/bridge_templates.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/registry.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/auth_tokens.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/bundle_converters.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/code_change_detector.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/content_sanitizer.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/env_manager.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/metadata.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/persona_ownership.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/startup_checks.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/terminal.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/schema.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/models.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.37.5 → specfact_cli-0.38.1}/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.38.1
|
|
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.
|
|
7
|
+
version = "0.38.1"
|
|
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"
|
|
@@ -38,6 +38,8 @@ from specfact_cli.utils.auth_tokens import get_token, set_token
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
_MAX_RESPONSE_BODY_LOG = 2048
|
|
41
|
+
_ADO_STABLE_API_VERSION = "7.1"
|
|
42
|
+
_ADO_COMMENTS_API_VERSION = "7.1-preview.4"
|
|
41
43
|
|
|
42
44
|
console = Console()
|
|
43
45
|
|
|
@@ -203,7 +205,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
203
205
|
"""
|
|
204
206
|
return "dev.azure.com" not in self.base_url.lower()
|
|
205
207
|
|
|
206
|
-
def _build_ado_url(self, path: str, api_version: str =
|
|
208
|
+
def _build_ado_url(self, path: str, api_version: str = _ADO_STABLE_API_VERSION) -> str:
|
|
207
209
|
"""
|
|
208
210
|
Build Azure DevOps API URL with proper formatting.
|
|
209
211
|
|
|
@@ -2474,7 +2476,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
2474
2476
|
seen_tokens: set[str] = set()
|
|
2475
2477
|
|
|
2476
2478
|
while True:
|
|
2477
|
-
params: dict[str, Any] = {"api-version":
|
|
2479
|
+
params: dict[str, Any] = {"api-version": _ADO_COMMENTS_API_VERSION, "$top": 200, "order": "asc"}
|
|
2478
2480
|
if continuation_token:
|
|
2479
2481
|
params["continuationToken"] = continuation_token
|
|
2480
2482
|
|
|
@@ -2536,7 +2538,10 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
2536
2538
|
raise ValueError(msg)
|
|
2537
2539
|
|
|
2538
2540
|
# Azure DevOps API for adding comments to work items
|
|
2539
|
-
url =
|
|
2541
|
+
url = (
|
|
2542
|
+
f"{self.base_url}/{org}/{project}/_apis/wit/workitems/{work_item_id}/comments"
|
|
2543
|
+
f"?api-version={_ADO_COMMENTS_API_VERSION}"
|
|
2544
|
+
)
|
|
2540
2545
|
headers = {
|
|
2541
2546
|
"Content-Type": "application/json",
|
|
2542
2547
|
**self._auth_headers(),
|
|
@@ -2944,16 +2949,21 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
2944
2949
|
]
|
|
2945
2950
|
|
|
2946
2951
|
if filters.iteration:
|
|
2947
|
-
|
|
2948
|
-
if
|
|
2949
|
-
|
|
2950
|
-
if
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2952
|
+
normalized_iteration = BacklogFilters.normalize_filter_value(filters.iteration)
|
|
2953
|
+
if normalized_iteration not in (None, "any"):
|
|
2954
|
+
target_iteration = filters.iteration
|
|
2955
|
+
if normalized_iteration == "current":
|
|
2956
|
+
current_iteration = self._get_current_iteration()
|
|
2957
|
+
if not current_iteration:
|
|
2958
|
+
return []
|
|
2959
|
+
target_iteration = current_iteration
|
|
2960
|
+
|
|
2961
|
+
filtered_items = [
|
|
2962
|
+
item
|
|
2963
|
+
for item in filtered_items
|
|
2964
|
+
if BacklogFilters.normalize_filter_value(item.iteration)
|
|
2965
|
+
== BacklogFilters.normalize_filter_value(target_iteration)
|
|
2966
|
+
]
|
|
2957
2967
|
|
|
2958
2968
|
if filters.sprint:
|
|
2959
2969
|
_, filtered_items = self._resolve_sprint_filter(
|
|
@@ -3698,7 +3708,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3698
3708
|
# Update story points using mapped field name (honors custom mappings)
|
|
3699
3709
|
if update_fields is None or "story_points" in update_fields:
|
|
3700
3710
|
story_points_field = ado_mapper.resolve_write_target_field("story_points", provider_field_names)
|
|
3701
|
-
if story_points_field and item.story_points is not None:
|
|
3711
|
+
if story_points_field and item.story_points is not None and story_points_field in provider_field_names:
|
|
3702
3712
|
operations.append(
|
|
3703
3713
|
{"op": "replace", "path": f"/fields/{story_points_field}", "value": item.story_points}
|
|
3704
3714
|
)
|
|
@@ -3706,7 +3716,11 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3706
3716
|
# Update business value using mapped field name (honors custom mappings)
|
|
3707
3717
|
if update_fields is None or "business_value" in update_fields:
|
|
3708
3718
|
business_value_field = ado_mapper.resolve_write_target_field("business_value", provider_field_names)
|
|
3709
|
-
if
|
|
3719
|
+
if (
|
|
3720
|
+
business_value_field
|
|
3721
|
+
and item.business_value is not None
|
|
3722
|
+
and business_value_field in provider_field_names
|
|
3723
|
+
):
|
|
3710
3724
|
operations.append(
|
|
3711
3725
|
{"op": "replace", "path": f"/fields/{business_value_field}", "value": item.business_value}
|
|
3712
3726
|
)
|
|
@@ -3714,7 +3728,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3714
3728
|
# Update priority using mapped field name (honors custom mappings)
|
|
3715
3729
|
if update_fields is None or "priority" in update_fields:
|
|
3716
3730
|
priority_field = ado_mapper.resolve_write_target_field("priority", provider_field_names)
|
|
3717
|
-
if priority_field and item.priority is not None:
|
|
3731
|
+
if priority_field and item.priority is not None and priority_field in provider_field_names:
|
|
3718
3732
|
operations.append({"op": "replace", "path": f"/fields/{priority_field}", "value": item.priority})
|
|
3719
3733
|
|
|
3720
3734
|
if update_fields is None or "state" in update_fields:
|
|
@@ -171,8 +171,9 @@ class AdoFieldMapper(FieldMapper):
|
|
|
171
171
|
@beartype
|
|
172
172
|
@require(lambda self, canonical_field: isinstance(canonical_field, str), "Canonical field must be str")
|
|
173
173
|
@require(
|
|
174
|
-
lambda self, provider_field_names:
|
|
175
|
-
|
|
174
|
+
lambda self, provider_field_names: (
|
|
175
|
+
provider_field_names is None or isinstance(provider_field_names, (set, frozenset))
|
|
176
|
+
),
|
|
176
177
|
"provider_field_names must be set-like or None",
|
|
177
178
|
)
|
|
178
179
|
@ensure(lambda result: result is None or isinstance(result, str), "Must return str or None")
|
|
@@ -58,6 +58,7 @@ from specfact_cli.modes import OperationalMode, detect_mode
|
|
|
58
58
|
|
|
59
59
|
# Command groups are registered via CommandRegistry (bootstrap); no top-level command imports.
|
|
60
60
|
from specfact_cli.registry import CommandRegistry
|
|
61
|
+
from specfact_cli.registry.alias_manager import resolve_command
|
|
61
62
|
from specfact_cli.registry.bootstrap import register_builtin_commands
|
|
62
63
|
from specfact_cli.registry.metadata import CommandMetadata
|
|
63
64
|
from specfact_cli.runtime import get_configured_console, init_debug_log_file, set_debug_mode
|
|
@@ -348,7 +349,8 @@ class _LazyDelegateGroup(click.Group):
|
|
|
348
349
|
from typer.main import get_command
|
|
349
350
|
|
|
350
351
|
ctx = click.get_current_context()
|
|
351
|
-
|
|
352
|
+
resolved_name = resolve_command(cmd_name)
|
|
353
|
+
real_typer = CommandRegistry.get_typer(resolved_name)
|
|
352
354
|
click_cmd = get_command(real_typer)
|
|
353
355
|
# Build full prog name from root (e.g. "specfact sync") so usage shows "specfact sync bridge", not "sync sync bridge"
|
|
354
356
|
parts: list[str] = []
|
|
@@ -407,7 +409,8 @@ class _LazyDelegateGroup(click.Group):
|
|
|
407
409
|
"""Load and return the real command's Click Group, or None on failure."""
|
|
408
410
|
from typer.main import get_command
|
|
409
411
|
|
|
410
|
-
|
|
412
|
+
resolved_name = resolve_command(self._lazy_cmd_name)
|
|
413
|
+
real_typer = CommandRegistry.get_typer(resolved_name)
|
|
411
414
|
click_cmd = get_command(real_typer)
|
|
412
415
|
if isinstance(click_cmd, click.Group):
|
|
413
416
|
return click_cmd
|
|
@@ -417,7 +420,8 @@ class _LazyDelegateGroup(click.Group):
|
|
|
417
420
|
"""Show the real Typer's Rich help instead of plain Click group help."""
|
|
418
421
|
from typer.main import get_command
|
|
419
422
|
|
|
420
|
-
|
|
423
|
+
resolved_name = resolve_command(self._lazy_cmd_name)
|
|
424
|
+
real_typer = CommandRegistry.get_typer(resolved_name)
|
|
421
425
|
click_cmd = get_command(real_typer)
|
|
422
426
|
prog_name = (
|
|
423
427
|
f"{ctx.parent.command.name} {self._lazy_cmd_name}"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name: module-registry
|
|
2
|
-
version: 0.1.
|
|
2
|
+
version: 0.1.5
|
|
3
3
|
commands:
|
|
4
4
|
- module
|
|
5
5
|
command_help:
|
|
@@ -15,5 +15,5 @@ publisher:
|
|
|
15
15
|
description: 'Manage modules: search, list, show, install, and upgrade.'
|
|
16
16
|
license: Apache-2.0
|
|
17
17
|
integrity:
|
|
18
|
-
checksum: sha256:
|
|
19
|
-
signature:
|
|
18
|
+
checksum: sha256:4837d40c55ebde6eba87b434c3ec3ae3d0d842eb6a6984d4212ffbc6fd26eac2
|
|
19
|
+
signature: m2tJyNfaHOnil3dsT5NxUB93+4nnVJHBaF7QzQf/DC8F/LG7oJJMWHU063HY9x2/d9hFVXLwItf9TNgNjnirDQ==
|
{specfact_cli-0.37.5 → specfact_cli-0.38.1}/src/specfact_cli/modules/module_registry/src/commands.py
RENAMED
|
@@ -12,7 +12,8 @@ from rich.console import Console
|
|
|
12
12
|
from rich.table import Table
|
|
13
13
|
|
|
14
14
|
from specfact_cli.modules import module_io_shim
|
|
15
|
-
from specfact_cli.registry.
|
|
15
|
+
from specfact_cli.registry.alias_manager import create_alias, list_aliases, remove_alias
|
|
16
|
+
from specfact_cli.registry.custom_registries import add_registry, fetch_all_indexes, list_registries, remove_registry
|
|
16
17
|
from specfact_cli.registry.module_discovery import discover_all_modules
|
|
17
18
|
from specfact_cli.registry.module_installer import (
|
|
18
19
|
USER_MODULES_ROOT,
|
|
@@ -87,6 +88,16 @@ def install(
|
|
|
87
88
|
"--trust-non-official",
|
|
88
89
|
help="Trust and persist non-official publisher for this module install",
|
|
89
90
|
),
|
|
91
|
+
skip_deps: bool = typer.Option(
|
|
92
|
+
False,
|
|
93
|
+
"--skip-deps",
|
|
94
|
+
help="Skip dependency resolution before installing (install module only)",
|
|
95
|
+
),
|
|
96
|
+
force: bool = typer.Option(
|
|
97
|
+
False,
|
|
98
|
+
"--force",
|
|
99
|
+
help="Force install even if dependency resolution reports conflicts",
|
|
100
|
+
),
|
|
90
101
|
) -> None:
|
|
91
102
|
"""Install a module from bundled artifacts or marketplace registry."""
|
|
92
103
|
scope_normalized = scope.strip().lower()
|
|
@@ -148,6 +159,8 @@ def install(
|
|
|
148
159
|
install_root=target_root,
|
|
149
160
|
trust_non_official=trust_non_official,
|
|
150
161
|
non_interactive=is_non_interactive(),
|
|
162
|
+
skip_deps=skip_deps,
|
|
163
|
+
force=force,
|
|
151
164
|
)
|
|
152
165
|
except Exception as exc:
|
|
153
166
|
console.print(f"[red]Failed installing {normalized}: {exc}[/red]")
|
|
@@ -243,6 +256,109 @@ def uninstall(
|
|
|
243
256
|
console.print(f"[green]Uninstalled[/green] {normalized}")
|
|
244
257
|
|
|
245
258
|
|
|
259
|
+
alias_app = typer.Typer(help="Manage command aliases (map name to namespaced module)")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@alias_app.command(name="create")
|
|
263
|
+
@beartype
|
|
264
|
+
def alias_create(
|
|
265
|
+
alias_name: str = typer.Argument(..., help="Alias (command name) to map"),
|
|
266
|
+
command_name: str = typer.Argument(..., help="Command name to invoke (e.g. backlog, module)"),
|
|
267
|
+
force: bool = typer.Option(False, "--force", help="Allow alias to shadow built-in command"),
|
|
268
|
+
) -> None:
|
|
269
|
+
"""Create an alias mapping a custom name to a registered command."""
|
|
270
|
+
try:
|
|
271
|
+
create_alias(alias_name.strip(), command_name.strip(), force=force)
|
|
272
|
+
except ValueError as exc:
|
|
273
|
+
console.print(f"[red]{exc}[/red]")
|
|
274
|
+
raise typer.Exit(1) from exc
|
|
275
|
+
console.print(f"[green]Alias[/green] {alias_name!r} -> {command_name!r}")
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@alias_app.command(name="list")
|
|
279
|
+
@beartype
|
|
280
|
+
def alias_list() -> None:
|
|
281
|
+
"""List all configured aliases."""
|
|
282
|
+
aliases = list_aliases()
|
|
283
|
+
if not aliases:
|
|
284
|
+
console.print("[dim]No aliases configured.[/dim]")
|
|
285
|
+
return
|
|
286
|
+
table = Table(title="Aliases")
|
|
287
|
+
table.add_column("Alias", style="cyan")
|
|
288
|
+
table.add_column("Command", style="green")
|
|
289
|
+
for alias, mod in sorted(aliases.items()):
|
|
290
|
+
table.add_row(alias, mod)
|
|
291
|
+
console.print(table)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@alias_app.command(name="remove")
|
|
295
|
+
@beartype
|
|
296
|
+
def alias_remove(
|
|
297
|
+
alias_name: str = typer.Argument(..., help="Alias to remove"),
|
|
298
|
+
) -> None:
|
|
299
|
+
"""Remove an alias."""
|
|
300
|
+
remove_alias(alias_name.strip())
|
|
301
|
+
console.print(f"[green]Removed alias[/green] {alias_name!r}")
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
if app.add_typer is not None:
|
|
305
|
+
app.add_typer(alias_app, name="alias")
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@app.command(name="add-registry")
|
|
309
|
+
@beartype
|
|
310
|
+
def add_registry_cmd(
|
|
311
|
+
url: str = typer.Argument(..., help="Registry index URL (e.g. https://company.com/index.json)"),
|
|
312
|
+
id: str | None = typer.Option(None, "--id", help="Registry id (default: derived from URL)"),
|
|
313
|
+
priority: int | None = typer.Option(None, "--priority", help="Priority (default: next available)"),
|
|
314
|
+
trust: str = typer.Option("prompt", "--trust", help="Trust level: always, prompt, or never"),
|
|
315
|
+
) -> None:
|
|
316
|
+
"""Add a custom registry to the config."""
|
|
317
|
+
if trust not in ("always", "prompt", "never"):
|
|
318
|
+
console.print("[red]trust must be one of: always, prompt, never.[/red]")
|
|
319
|
+
raise typer.Exit(1)
|
|
320
|
+
reg_id = (id or url.strip().rstrip("/").split("/")[-2] or "custom").strip() or "custom"
|
|
321
|
+
try:
|
|
322
|
+
add_registry(reg_id, url.strip(), priority=priority, trust=trust)
|
|
323
|
+
except Exception as exc:
|
|
324
|
+
console.print(f"[red]{exc}[/red]")
|
|
325
|
+
raise typer.Exit(1) from exc
|
|
326
|
+
console.print(f"[green]Added registry[/green] {reg_id!r} -> {url}")
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@app.command(name="list-registries")
|
|
330
|
+
@beartype
|
|
331
|
+
def list_registries_cmd() -> None:
|
|
332
|
+
"""List all configured registries (official + custom)."""
|
|
333
|
+
registries = list_registries()
|
|
334
|
+
if not registries:
|
|
335
|
+
console.print("[dim]No registries configured.[/dim]")
|
|
336
|
+
return
|
|
337
|
+
table = Table(title="Registries")
|
|
338
|
+
table.add_column("Id", style="cyan")
|
|
339
|
+
table.add_column("URL", style="green")
|
|
340
|
+
table.add_column("Priority", style="dim")
|
|
341
|
+
table.add_column("Trust", style="yellow")
|
|
342
|
+
for r in registries:
|
|
343
|
+
table.add_row(
|
|
344
|
+
str(r.get("id", "")),
|
|
345
|
+
str(r.get("url", "")),
|
|
346
|
+
str(r.get("priority", "")),
|
|
347
|
+
str(r.get("trust", "")),
|
|
348
|
+
)
|
|
349
|
+
console.print(table)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@app.command(name="remove-registry")
|
|
353
|
+
@beartype
|
|
354
|
+
def remove_registry_cmd(
|
|
355
|
+
registry_id: str = typer.Argument(..., help="Registry id to remove"),
|
|
356
|
+
) -> None:
|
|
357
|
+
"""Remove a custom registry from the config."""
|
|
358
|
+
remove_registry(registry_id.strip())
|
|
359
|
+
console.print(f"[green]Removed registry[/green] {registry_id!r}")
|
|
360
|
+
|
|
361
|
+
|
|
246
362
|
@app.command()
|
|
247
363
|
@beartype
|
|
248
364
|
def enable(
|
|
@@ -320,25 +436,26 @@ def search(query: str = typer.Argument(..., help="Search query")) -> None:
|
|
|
320
436
|
seen_ids: set[str] = set()
|
|
321
437
|
rows: list[dict[str, str]] = []
|
|
322
438
|
|
|
323
|
-
index
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
439
|
+
for reg_id, index in fetch_all_indexes():
|
|
440
|
+
for entry in index.get("modules", []):
|
|
441
|
+
if not isinstance(entry, dict):
|
|
442
|
+
continue
|
|
443
|
+
module_id = str(entry.get("id", ""))
|
|
444
|
+
description = str(entry.get("description", ""))
|
|
445
|
+
tags = entry.get("tags", [])
|
|
446
|
+
tags_text = " ".join(str(t) for t in tags) if isinstance(tags, list) else ""
|
|
447
|
+
haystack = f"{module_id} {description} {tags_text}".lower()
|
|
448
|
+
if query_l in haystack and module_id not in seen_ids:
|
|
449
|
+
seen_ids.add(module_id)
|
|
450
|
+
rows.append(
|
|
451
|
+
{
|
|
452
|
+
"id": module_id,
|
|
453
|
+
"version": str(entry.get("latest_version", "")),
|
|
454
|
+
"description": description,
|
|
455
|
+
"scope": "marketplace",
|
|
456
|
+
"registry": reg_id,
|
|
457
|
+
}
|
|
458
|
+
)
|
|
342
459
|
|
|
343
460
|
for discovered in discover_all_modules():
|
|
344
461
|
meta = discovered.metadata
|
|
@@ -372,9 +489,11 @@ def search(query: str = typer.Argument(..., help="Search query")) -> None:
|
|
|
372
489
|
table.add_column("ID", style="cyan")
|
|
373
490
|
table.add_column("Version", style="magenta")
|
|
374
491
|
table.add_column("Scope", style="yellow")
|
|
492
|
+
table.add_column("Registry", style="dim")
|
|
375
493
|
table.add_column("Description")
|
|
376
494
|
for row in rows:
|
|
377
|
-
|
|
495
|
+
reg = row.get("registry", "")
|
|
496
|
+
table.add_row(row["id"], row["version"], row["scope"], reg, row["description"])
|
|
378
497
|
console.print(table)
|
|
379
498
|
|
|
380
499
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Alias storage and resolution: alias -> command name."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from beartype import beartype
|
|
9
|
+
from icontract import ensure, require
|
|
10
|
+
|
|
11
|
+
from specfact_cli.common import get_bridge_logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
logger = get_bridge_logger(__name__)
|
|
15
|
+
|
|
16
|
+
_ALIASES_FILENAME = "aliases.json"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_aliases_path() -> Path:
|
|
20
|
+
"""Return path to aliases.json under ~/.specfact/registry/."""
|
|
21
|
+
return Path.home() / ".specfact" / "registry" / _ALIASES_FILENAME
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _builtin_command_names() -> set[str]:
|
|
25
|
+
"""Return set of built-in (default) command names for shadowing check."""
|
|
26
|
+
from specfact_cli.registry.module_packages import CORE_MODULE_ORDER
|
|
27
|
+
|
|
28
|
+
return set(CORE_MODULE_ORDER)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@beartype
|
|
32
|
+
@require(lambda alias: alias.strip() != "", "alias must be non-empty")
|
|
33
|
+
@require(lambda command_name: command_name.strip() != "", "command_name must be non-empty")
|
|
34
|
+
@ensure(lambda: True, "no postcondition on void")
|
|
35
|
+
def create_alias(alias: str, command_name: str, force: bool = False) -> None:
|
|
36
|
+
"""Store alias -> command_name in aliases.json. Warn or raise if alias shadows built-in."""
|
|
37
|
+
alias = alias.strip()
|
|
38
|
+
command_name = command_name.strip()
|
|
39
|
+
path = get_aliases_path()
|
|
40
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
builtin = _builtin_command_names()
|
|
42
|
+
if alias in builtin and not force:
|
|
43
|
+
logger.warning("Alias will shadow built-in module: %s", alias)
|
|
44
|
+
raise ValueError(f'Alias "{alias}" would shadow built-in module. Use --force to proceed.')
|
|
45
|
+
data = {}
|
|
46
|
+
if path.exists():
|
|
47
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
48
|
+
data[alias] = command_name
|
|
49
|
+
path.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
50
|
+
if alias in builtin:
|
|
51
|
+
logger.warning("Alias will shadow built-in module: %s", alias)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@beartype
|
|
55
|
+
@ensure(lambda result: isinstance(result, dict), "returns dict")
|
|
56
|
+
def list_aliases() -> dict[str, str]:
|
|
57
|
+
"""Return all alias -> command name mappings. Empty dict if file missing."""
|
|
58
|
+
path = get_aliases_path()
|
|
59
|
+
if not path.exists():
|
|
60
|
+
return {}
|
|
61
|
+
raw = path.read_text(encoding="utf-8")
|
|
62
|
+
data = json.loads(raw)
|
|
63
|
+
if not isinstance(data, dict):
|
|
64
|
+
return {}
|
|
65
|
+
return {str(k): str(v) for k, v in data.items()}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@beartype
|
|
69
|
+
@require(lambda alias: alias.strip() != "", "alias must be non-empty")
|
|
70
|
+
@ensure(lambda: True, "no postcondition on void")
|
|
71
|
+
def remove_alias(alias: str) -> None:
|
|
72
|
+
"""Remove alias from aliases.json."""
|
|
73
|
+
alias = alias.strip()
|
|
74
|
+
path = get_aliases_path()
|
|
75
|
+
if not path.exists():
|
|
76
|
+
return
|
|
77
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
78
|
+
if alias in data:
|
|
79
|
+
del data[alias]
|
|
80
|
+
if data:
|
|
81
|
+
path.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
82
|
+
else:
|
|
83
|
+
path.unlink()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@beartype
|
|
87
|
+
@require(lambda invoked_name: isinstance(invoked_name, str), "invoked_name must be str")
|
|
88
|
+
@ensure(lambda result: isinstance(result, str) and len(result) > 0, "returns non-empty string")
|
|
89
|
+
def resolve_command(invoked_name: str) -> str:
|
|
90
|
+
"""If invoked_name is an alias, return the stored command name; else return invoked_name."""
|
|
91
|
+
aliases = list_aliases()
|
|
92
|
+
if invoked_name in aliases:
|
|
93
|
+
return aliases[invoked_name]
|
|
94
|
+
return invoked_name
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Custom registry management: YAML config and multi-registry index fetching."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
import yaml
|
|
10
|
+
from beartype import beartype
|
|
11
|
+
from icontract import ensure, require
|
|
12
|
+
|
|
13
|
+
from specfact_cli.common import get_bridge_logger
|
|
14
|
+
from specfact_cli.registry.marketplace_client import REGISTRY_INDEX_URL
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
logger = get_bridge_logger(__name__)
|
|
18
|
+
|
|
19
|
+
_REGISTRIES_FILENAME = "registries.yaml"
|
|
20
|
+
OFFICIAL_REGISTRY_ID = "official"
|
|
21
|
+
TRUST_LEVELS = frozenset({"always", "prompt", "never"})
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_registries_config_path() -> Path:
|
|
25
|
+
"""Return path to registries.yaml under ~/.specfact/config/."""
|
|
26
|
+
return Path.home() / ".specfact" / "config" / _REGISTRIES_FILENAME
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _default_official_entry() -> dict[str, Any]:
|
|
30
|
+
"""Return the built-in official registry entry."""
|
|
31
|
+
return {
|
|
32
|
+
"id": OFFICIAL_REGISTRY_ID,
|
|
33
|
+
"url": REGISTRY_INDEX_URL,
|
|
34
|
+
"priority": 1,
|
|
35
|
+
"trust": "always",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@beartype
|
|
40
|
+
@require(lambda id: id.strip() != "", "id must be non-empty")
|
|
41
|
+
@require(lambda url: url.strip().startswith("http"), "url must be http(s)")
|
|
42
|
+
@require(lambda trust: trust in TRUST_LEVELS, "trust must be always, prompt, or never")
|
|
43
|
+
@ensure(lambda: True, "no postcondition on void")
|
|
44
|
+
def add_registry(
|
|
45
|
+
id: str,
|
|
46
|
+
url: str,
|
|
47
|
+
priority: int | None = None,
|
|
48
|
+
trust: str = "prompt",
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Add a registry to config. Assigns next priority if priority is None."""
|
|
51
|
+
id = id.strip()
|
|
52
|
+
url = url.strip()
|
|
53
|
+
path = get_registries_config_path()
|
|
54
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
registries: list[dict[str, Any]] = []
|
|
56
|
+
if path.exists():
|
|
57
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
58
|
+
registries = list(data.get("registries") or [])
|
|
59
|
+
existing_ids = {r.get("id") for r in registries if isinstance(r, dict) and r.get("id")}
|
|
60
|
+
if id in existing_ids:
|
|
61
|
+
registries = [r for r in registries if isinstance(r, dict) and r.get("id") != id]
|
|
62
|
+
if priority is None:
|
|
63
|
+
priorities = [
|
|
64
|
+
p
|
|
65
|
+
for r in registries
|
|
66
|
+
if isinstance(r, dict) and (p := r.get("priority")) is not None and isinstance(p, (int, float))
|
|
67
|
+
]
|
|
68
|
+
priority = int(max(priorities, default=0)) + 1
|
|
69
|
+
registries.append({"id": id, "url": url, "priority": int(priority), "trust": trust})
|
|
70
|
+
registries.sort(key=lambda r: (r.get("priority", 999), r.get("id", "")))
|
|
71
|
+
path.write_text(yaml.dump({"registries": registries}, default_flow_style=False, sort_keys=False), encoding="utf-8")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@beartype
|
|
75
|
+
@ensure(lambda result: isinstance(result, list), "returns list")
|
|
76
|
+
def list_registries() -> list[dict[str, Any]]:
|
|
77
|
+
"""Return all registries: official first, then custom from config, sorted by priority."""
|
|
78
|
+
result: list[dict[str, Any]] = []
|
|
79
|
+
path = get_registries_config_path()
|
|
80
|
+
if path.exists():
|
|
81
|
+
try:
|
|
82
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
83
|
+
custom = [r for r in (data.get("registries") or []) if isinstance(r, dict) and r.get("id")]
|
|
84
|
+
has_official = any(r.get("id") == OFFICIAL_REGISTRY_ID for r in custom)
|
|
85
|
+
if not has_official:
|
|
86
|
+
result.append(_default_official_entry())
|
|
87
|
+
for r in custom:
|
|
88
|
+
result.append({k: v for k, v in r.items() if k in ("id", "url", "priority", "trust")})
|
|
89
|
+
result.sort(key=lambda r: (r.get("priority", 999), r.get("id", "")))
|
|
90
|
+
except Exception as exc:
|
|
91
|
+
logger.warning("Failed to load registries config: %s", exc)
|
|
92
|
+
result = [_default_official_entry()]
|
|
93
|
+
else:
|
|
94
|
+
result = [_default_official_entry()]
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@beartype
|
|
99
|
+
@require(lambda id: id.strip() != "", "id must be non-empty")
|
|
100
|
+
@ensure(lambda: True, "no postcondition on void")
|
|
101
|
+
def remove_registry(id: str) -> None:
|
|
102
|
+
"""Remove a registry by id from config. Cannot remove official (no-op if official)."""
|
|
103
|
+
id = id.strip()
|
|
104
|
+
if id == OFFICIAL_REGISTRY_ID:
|
|
105
|
+
logger.debug("Cannot remove built-in official registry")
|
|
106
|
+
return
|
|
107
|
+
path = get_registries_config_path()
|
|
108
|
+
if not path.exists():
|
|
109
|
+
return
|
|
110
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
111
|
+
registries = [r for r in (data.get("registries") or []) if isinstance(r, dict) and r.get("id") != id]
|
|
112
|
+
if not registries:
|
|
113
|
+
path.unlink()
|
|
114
|
+
return
|
|
115
|
+
path.write_text(yaml.dump({"registries": registries}, default_flow_style=False, sort_keys=False), encoding="utf-8")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@beartype
|
|
119
|
+
@ensure(lambda result: isinstance(result, list), "returns list")
|
|
120
|
+
def fetch_all_indexes(timeout: float = 10.0) -> list[tuple[str, dict[str, Any]]]:
|
|
121
|
+
"""Fetch index from each registry in priority order. Returns list of (registry_id, index_dict)."""
|
|
122
|
+
registries = list_registries()
|
|
123
|
+
result: list[tuple[str, dict[str, Any]]] = []
|
|
124
|
+
for reg in registries:
|
|
125
|
+
reg_id = str(reg.get("id", ""))
|
|
126
|
+
url = str(reg.get("url", "")).strip()
|
|
127
|
+
if not url:
|
|
128
|
+
continue
|
|
129
|
+
try:
|
|
130
|
+
response = requests.get(url, timeout=timeout)
|
|
131
|
+
response.raise_for_status()
|
|
132
|
+
payload = response.json()
|
|
133
|
+
if isinstance(payload, dict):
|
|
134
|
+
result.append((reg_id, payload))
|
|
135
|
+
else:
|
|
136
|
+
logger.warning("Registry %s returned non-dict index", reg_id)
|
|
137
|
+
except Exception as exc:
|
|
138
|
+
logger.warning("Registry %s unavailable: %s", reg_id, exc)
|
|
139
|
+
return result
|