specfact-cli 0.24.0__tar.gz → 0.25.0__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.24.0 → specfact_cli-0.25.0}/.gitignore +2 -1
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/PKG-INFO +1 -1
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/pyproject.toml +1 -1
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/__init__.py +1 -1
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/__init__.py +1 -1
- specfact_cli-0.25.0/src/specfact_cli/adapters/backlog_base.py +272 -0
- specfact_cli-0.25.0/src/specfact_cli/adapters/github.py +2100 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/import_cmd.py +17 -6
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/sync.py +17 -7
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/validate.py +117 -2
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/integrations/specmatic.py +6 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/bridge_sync.py +288 -122
- specfact_cli-0.25.0/src/specfact_cli/validators/change_proposal_integration.py +412 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/contract_populator.py +37 -31
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/crosshair_runner.py +15 -4
- specfact_cli-0.25.0/src/specfact_cli/validators/sidecar/crosshair_summary.py +268 -0
- specfact_cli-0.25.0/src/specfact_cli/validators/sidecar/dependency_installer.py +234 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/framework_detector.py +2 -2
- specfact_cli-0.25.0/src/specfact_cli/validators/sidecar/frameworks/flask.py +298 -0
- specfact_cli-0.25.0/src/specfact_cli/validators/sidecar/harness_generator.py +698 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/models.py +15 -3
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/orchestrator.py +95 -5
- specfact_cli-0.24.0/src/specfact_cli/adapters/github.py +0 -792
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -164
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/harness_generator.py +0 -148
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/LICENSE.md +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/README.md +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/shared/cli-enforcement.md +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.01-import.md +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.02-plan.md +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.03-review.md +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.04-sdd.md +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.05-enforce.md +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.06-sync.md +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.07-contracts.md +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.compare.md +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.sync-backlog.md +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/prompts/specfact.validate.md +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/STRUCTURE.md +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/README.md +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/adapters.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/bindings.yaml.example +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/crosshair_plugin.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/generate_harness.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/harness_contracts.py.example +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/populate_contracts.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/run_sidecar.sh +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/common/sidecar-init.sh +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/frameworks/django/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/frameworks/django/crosshair_django_wrapper.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/frameworks/django/django_form_extractor.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/frameworks/django/django_url_extractor.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/frameworks/drf/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/frameworks/drf/drf_serializer_extractor.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/frameworks/fastapi/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/sidecar/frameworks/fastapi/fastapi_route_extractor.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/adapters/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/adapters/base.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/adapters/openspec.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/adapters/openspec_parser.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/adapters/registry.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/adapters/speckit.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/cli.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/project_cmd.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/persona_exporter.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/capabilities.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/change.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/project.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/templates/bridge_templates.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/code_change_detector.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/content_sanitizer.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/env_manager.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/terminal.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/schema.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.24.0 → specfact_cli-0.25.0}/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.25.0
|
|
4
4
|
Summary: Brownfield-first CLI: Reverse engineer legacy Python → specs → enforced contracts. Automate legacy code documentation and prevent modernization regressions.
|
|
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.25.0"
|
|
8
8
|
description = "Brownfield-first CLI: Reverse engineer legacy Python → specs → enforced contracts. Automate legacy code documentation and prevent modernization regressions."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes and utilities for backlog adapters.
|
|
3
|
+
|
|
4
|
+
This module provides reusable patterns and abstractions for implementing backlog
|
|
5
|
+
adapters (GitHub, Azure DevOps, Jira, Linear, etc.) that support bidirectional
|
|
6
|
+
sync between backlog management tools and OpenSpec change proposals.
|
|
7
|
+
|
|
8
|
+
All backlog adapters should inherit from BacklogAdapterMixin to get common
|
|
9
|
+
functionality for status mapping, metadata extraction, and conflict resolution.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
from datetime import UTC, datetime
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from beartype import beartype
|
|
19
|
+
from icontract import ensure, require
|
|
20
|
+
|
|
21
|
+
from specfact_cli.models.change import ChangeProposal
|
|
22
|
+
from specfact_cli.models.source_tracking import SourceTracking
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BacklogAdapterMixin(ABC):
|
|
26
|
+
"""
|
|
27
|
+
Mixin class providing common functionality for backlog adapters.
|
|
28
|
+
|
|
29
|
+
This mixin provides tool-agnostic patterns for:
|
|
30
|
+
- Status mapping (backlog status ↔ OpenSpec status)
|
|
31
|
+
- Metadata extraction (backlog item → change proposal)
|
|
32
|
+
- Conflict resolution (when status differs)
|
|
33
|
+
|
|
34
|
+
Future backlog adapters (ADO, Jira, Linear) should inherit from this mixin
|
|
35
|
+
and implement the abstract methods to provide tool-specific implementations.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
@beartype
|
|
40
|
+
@require(lambda status: isinstance(status, str) and len(status) > 0, "Status must be non-empty string")
|
|
41
|
+
@ensure(lambda result: isinstance(result, str) and len(result) > 0, "Must return non-empty status string")
|
|
42
|
+
def map_backlog_status_to_openspec(self, status: str) -> str:
|
|
43
|
+
"""
|
|
44
|
+
Map backlog tool status to OpenSpec change status.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
status: Backlog tool status (e.g., GitHub label, ADO state, Jira status, Linear state)
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
OpenSpec change status (proposed, in-progress, applied, deprecated, discarded)
|
|
51
|
+
|
|
52
|
+
Note:
|
|
53
|
+
This method must be implemented by each backlog adapter to provide
|
|
54
|
+
tool-specific status mapping logic.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
@beartype
|
|
59
|
+
@require(lambda status: isinstance(status, str) and len(status) > 0, "Status must be non-empty string")
|
|
60
|
+
@ensure(lambda result: isinstance(result, (str, list)), "Must return status string or list of status strings")
|
|
61
|
+
def map_openspec_status_to_backlog(self, status: str) -> str | list[str]:
|
|
62
|
+
"""
|
|
63
|
+
Map OpenSpec change status to backlog tool status.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
status: OpenSpec change status (proposed, in-progress, applied, deprecated, discarded)
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Backlog tool status (e.g., GitHub label, ADO state, Jira status, Linear state)
|
|
70
|
+
or list of status strings for tools that support multiple status indicators
|
|
71
|
+
|
|
72
|
+
Note:
|
|
73
|
+
This method must be implemented by each backlog adapter to provide
|
|
74
|
+
tool-specific status mapping logic.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
@abstractmethod
|
|
78
|
+
@beartype
|
|
79
|
+
@require(lambda item_data: isinstance(item_data, dict), "Item data must be dict")
|
|
80
|
+
@ensure(lambda result: isinstance(result, dict), "Must return dict with extracted fields")
|
|
81
|
+
def extract_change_proposal_data(self, item_data: dict[str, Any]) -> dict[str, Any]:
|
|
82
|
+
"""
|
|
83
|
+
Extract change proposal data from backlog item.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
item_data: Backlog item data (e.g., GitHub issue dict, ADO work item dict, Jira issue dict, Linear issue dict)
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Dict with change proposal fields:
|
|
90
|
+
- title: str
|
|
91
|
+
- description: str (What Changes section)
|
|
92
|
+
- rationale: str (Why section)
|
|
93
|
+
- status: str (mapped to OpenSpec status)
|
|
94
|
+
- Other optional fields (timeline, owner, stakeholders, dependencies)
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
ValueError: If required fields are missing or data is malformed
|
|
98
|
+
|
|
99
|
+
Note:
|
|
100
|
+
This method must be implemented by each backlog adapter to parse
|
|
101
|
+
tool-specific data formats (GitHub issue body, ADO work item fields, etc.).
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
@beartype
|
|
105
|
+
@require(lambda item_data: isinstance(item_data, dict), "Item data must be dict")
|
|
106
|
+
@require(lambda tool_name: isinstance(tool_name, str) and len(tool_name) > 0, "Tool name must be non-empty")
|
|
107
|
+
@ensure(lambda result: isinstance(result, SourceTracking), "Must return SourceTracking")
|
|
108
|
+
def create_source_tracking(
|
|
109
|
+
self, item_data: dict[str, Any], tool_name: str, bridge_config: Any = None
|
|
110
|
+
) -> SourceTracking:
|
|
111
|
+
"""
|
|
112
|
+
Create SourceTracking from backlog item metadata.
|
|
113
|
+
|
|
114
|
+
This is a reusable utility method that all backlog adapters can use
|
|
115
|
+
to store tool-specific metadata in source_tracking.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
item_data: Backlog item data with metadata (ID, URL, status, assignees, etc.)
|
|
119
|
+
tool_name: Tool identifier (e.g., "github", "ado", "jira", "linear")
|
|
120
|
+
bridge_config: Optional bridge configuration (for cross-repo support)
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
SourceTracking instance with tool-specific metadata stored in source_metadata
|
|
124
|
+
|
|
125
|
+
Note:
|
|
126
|
+
This method provides a common pattern for storing backlog item metadata.
|
|
127
|
+
Each adapter should call this method and add tool-specific fields to source_metadata.
|
|
128
|
+
"""
|
|
129
|
+
source_metadata: dict[str, Any] = {}
|
|
130
|
+
|
|
131
|
+
# Extract common fields (ID, URL) if present
|
|
132
|
+
if "id" in item_data or "number" in item_data:
|
|
133
|
+
source_metadata["source_id"] = item_data.get("id") or item_data.get("number")
|
|
134
|
+
# Prefer html_url (user-friendly) over url (API URL)
|
|
135
|
+
if "html_url" in item_data:
|
|
136
|
+
source_metadata["source_url"] = item_data.get("html_url")
|
|
137
|
+
elif "url" in item_data:
|
|
138
|
+
source_metadata["source_url"] = item_data.get("url")
|
|
139
|
+
if "state" in item_data:
|
|
140
|
+
source_metadata["source_state"] = item_data.get("state")
|
|
141
|
+
if "assignees" in item_data or "assignee" in item_data:
|
|
142
|
+
assignees = item_data.get("assignees", [])
|
|
143
|
+
if not assignees and "assignee" in item_data:
|
|
144
|
+
assignees = [item_data["assignee"]] if item_data["assignee"] else []
|
|
145
|
+
source_metadata["assignees"] = assignees
|
|
146
|
+
|
|
147
|
+
# Add cross-repo support if bridge_config has external_base_path
|
|
148
|
+
if bridge_config and hasattr(bridge_config, "external_base_path") and bridge_config.external_base_path:
|
|
149
|
+
source_metadata["external_base_path"] = str(bridge_config.external_base_path)
|
|
150
|
+
|
|
151
|
+
return SourceTracking(tool=tool_name, source_metadata=source_metadata)
|
|
152
|
+
|
|
153
|
+
@beartype
|
|
154
|
+
@require(
|
|
155
|
+
lambda openspec_status: isinstance(openspec_status, str) and len(openspec_status) > 0,
|
|
156
|
+
"Status must be non-empty",
|
|
157
|
+
)
|
|
158
|
+
@require(
|
|
159
|
+
lambda backlog_status: isinstance(backlog_status, str) and len(backlog_status) > 0, "Status must be non-empty"
|
|
160
|
+
)
|
|
161
|
+
@ensure(lambda result: isinstance(result, str), "Must return conflict resolution strategy name")
|
|
162
|
+
def resolve_status_conflict(
|
|
163
|
+
self, openspec_status: str, backlog_status: str, strategy: str = "prefer_openspec"
|
|
164
|
+
) -> str:
|
|
165
|
+
"""
|
|
166
|
+
Resolve status conflict when OpenSpec and backlog status differ.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
openspec_status: OpenSpec change status
|
|
170
|
+
backlog_status: Backlog tool status (mapped to OpenSpec format)
|
|
171
|
+
strategy: Conflict resolution strategy:
|
|
172
|
+
- "prefer_openspec": Use OpenSpec status (default)
|
|
173
|
+
- "prefer_backlog": Use backlog status
|
|
174
|
+
- "merge": Use most advanced status (in-progress > proposed, applied > in-progress)
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Resolved status (OpenSpec format)
|
|
178
|
+
|
|
179
|
+
Note:
|
|
180
|
+
This provides a reusable conflict resolution pattern that all backlog
|
|
181
|
+
adapters can use. The default strategy prefers OpenSpec as the source of truth.
|
|
182
|
+
"""
|
|
183
|
+
if openspec_status == backlog_status:
|
|
184
|
+
return openspec_status
|
|
185
|
+
|
|
186
|
+
if strategy == "prefer_openspec":
|
|
187
|
+
return openspec_status
|
|
188
|
+
if strategy == "prefer_backlog":
|
|
189
|
+
return backlog_status
|
|
190
|
+
if strategy == "merge":
|
|
191
|
+
# Status priority: applied > in-progress > proposed > deprecated > discarded
|
|
192
|
+
status_priority = {
|
|
193
|
+
"applied": 5,
|
|
194
|
+
"in-progress": 4,
|
|
195
|
+
"proposed": 3,
|
|
196
|
+
"deprecated": 2,
|
|
197
|
+
"discarded": 1,
|
|
198
|
+
}
|
|
199
|
+
openspec_priority = status_priority.get(openspec_status, 0)
|
|
200
|
+
backlog_priority = status_priority.get(backlog_status, 0)
|
|
201
|
+
return openspec_status if openspec_priority >= backlog_priority else backlog_status
|
|
202
|
+
|
|
203
|
+
# Default: prefer OpenSpec
|
|
204
|
+
return openspec_status
|
|
205
|
+
|
|
206
|
+
@beartype
|
|
207
|
+
@require(lambda item_data: isinstance(item_data, dict), "Item data must be dict")
|
|
208
|
+
@require(lambda tool_name: isinstance(tool_name, str) and len(tool_name) > 0, "Tool name must be non-empty")
|
|
209
|
+
@ensure(lambda result: isinstance(result, ChangeProposal) or result is None, "Must return ChangeProposal or None")
|
|
210
|
+
def import_backlog_item_as_proposal(
|
|
211
|
+
self, item_data: dict[str, Any], tool_name: str, bridge_config: Any = None
|
|
212
|
+
) -> ChangeProposal | None:
|
|
213
|
+
"""
|
|
214
|
+
Import backlog item as OpenSpec change proposal (reusable pattern).
|
|
215
|
+
|
|
216
|
+
This method provides a common workflow that all backlog adapters can use:
|
|
217
|
+
1. Extract change proposal data from backlog item
|
|
218
|
+
2. Map backlog status to OpenSpec status
|
|
219
|
+
3. Create SourceTracking with tool-specific metadata
|
|
220
|
+
4. Create ChangeProposal instance
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
item_data: Backlog item data (tool-specific format)
|
|
224
|
+
tool_name: Tool identifier (e.g., "github", "ado", "jira", "linear")
|
|
225
|
+
bridge_config: Optional bridge configuration (for cross-repo support)
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
ChangeProposal instance if successful, None if data is invalid
|
|
229
|
+
|
|
230
|
+
Raises:
|
|
231
|
+
ValueError: If required fields are missing or data is malformed
|
|
232
|
+
|
|
233
|
+
Note:
|
|
234
|
+
This method implements the common import pattern. Each backlog adapter
|
|
235
|
+
should call this method after implementing extract_change_proposal_data()
|
|
236
|
+
and map_backlog_status_to_openspec().
|
|
237
|
+
"""
|
|
238
|
+
try:
|
|
239
|
+
# Extract change proposal data (tool-specific parsing)
|
|
240
|
+
proposal_data = self.extract_change_proposal_data(item_data)
|
|
241
|
+
|
|
242
|
+
# Get status from extracted data or map from backlog item
|
|
243
|
+
if "status" in proposal_data:
|
|
244
|
+
openspec_status = proposal_data["status"]
|
|
245
|
+
else:
|
|
246
|
+
# Map backlog status to OpenSpec status
|
|
247
|
+
backlog_status = item_data.get("state") or item_data.get("status") or "open"
|
|
248
|
+
openspec_status = self.map_backlog_status_to_openspec(backlog_status)
|
|
249
|
+
|
|
250
|
+
# Create source tracking
|
|
251
|
+
source_tracking = self.create_source_tracking(item_data, tool_name, bridge_config)
|
|
252
|
+
|
|
253
|
+
# Create change proposal
|
|
254
|
+
change_id = proposal_data.get("change_id") or item_data.get("id") or item_data.get("number") or "unknown"
|
|
255
|
+
return ChangeProposal(
|
|
256
|
+
name=change_id,
|
|
257
|
+
title=proposal_data.get("title", "Untitled Change Proposal"),
|
|
258
|
+
description=proposal_data.get("description", ""),
|
|
259
|
+
rationale=proposal_data.get("rationale", ""),
|
|
260
|
+
timeline=proposal_data.get("timeline"),
|
|
261
|
+
owner=proposal_data.get("owner"),
|
|
262
|
+
stakeholders=proposal_data.get("stakeholders", []),
|
|
263
|
+
dependencies=proposal_data.get("dependencies", []),
|
|
264
|
+
status=openspec_status,
|
|
265
|
+
created_at=proposal_data.get("created_at") or datetime.now(UTC).isoformat(),
|
|
266
|
+
applied_at=proposal_data.get("applied_at"),
|
|
267
|
+
archived_at=proposal_data.get("archived_at"),
|
|
268
|
+
source_tracking=source_tracking,
|
|
269
|
+
)
|
|
270
|
+
except (KeyError, ValueError, TypeError) as e:
|
|
271
|
+
msg = f"Failed to import backlog item as change proposal: {e}"
|
|
272
|
+
raise ValueError(msg) from e
|