specfact-cli 0.23.1__tar.gz → 0.24.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.23.1 → specfact_cli-0.24.0}/PKG-INFO +15 -2
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/README.md +14 -1
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/pyproject.toml +1 -1
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/run_sidecar.sh +124 -35
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/__init__.py +1 -1
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/cli.py +2 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/__init__.py +2 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/init.py +3 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/repro.py +45 -0
- specfact_cli-0.24.0/src/specfact_cli/commands/validate.py +158 -0
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/__init__.py +11 -0
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/contract_populator.py +145 -0
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/crosshair_runner.py +98 -0
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/crosshair_summary.py +164 -0
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/framework_detector.py +184 -0
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/frameworks/__init__.py +12 -0
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/frameworks/base.py +84 -0
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/frameworks/django.py +322 -0
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/frameworks/drf.py +95 -0
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +225 -0
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/harness_generator.py +148 -0
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/models.py +184 -0
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/orchestrator.py +357 -0
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/specmatic_runner.py +106 -0
- specfact_cli-0.24.0/src/specfact_cli/validators/sidecar/unannotated_detector.py +140 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/.gitignore +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/LICENSE.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/shared/cli-enforcement.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.01-import.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.02-plan.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.03-review.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.04-sdd.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.05-enforce.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.06-sync.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.07-contracts.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.compare.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.sync-backlog.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/prompts/specfact.validate.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/STRUCTURE.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/README.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/adapters.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/bindings.yaml.example +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/crosshair_plugin.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/generate_harness.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/harness_contracts.py.example +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/populate_contracts.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/sidecar-init.sh +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/frameworks/django/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/frameworks/django/crosshair_django_wrapper.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/frameworks/django/django_form_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/frameworks/django/django_url_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/frameworks/drf/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/frameworks/drf/drf_serializer_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/frameworks/fastapi/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/frameworks/fastapi/fastapi_route_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/adapters/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/adapters/base.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/adapters/github.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/adapters/openspec.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/adapters/openspec_parser.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/adapters/registry.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/adapters/speckit.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/project_cmd.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/persona_exporter.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/capabilities.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/change.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/project.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/bridge_sync.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/templates/bridge_templates.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/code_change_detector.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/content_sanitizer.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/env_manager.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/terminal.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/validators/schema.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.0}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.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.24.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
|
|
@@ -368,6 +368,10 @@ specfact init --ide cursor --install-deps
|
|
|
368
368
|
```bash
|
|
369
369
|
# Analyze legacy codebase (most common use case)
|
|
370
370
|
specfact import from-code my-project --repo .
|
|
371
|
+
|
|
372
|
+
# Or validate external codebase without modifying source (sidecar validation)
|
|
373
|
+
specfact validate sidecar init my-project /path/to/repo
|
|
374
|
+
specfact validate sidecar run my-project /path/to/repo
|
|
371
375
|
```
|
|
372
376
|
|
|
373
377
|
**⏱️ Timing:** Analysis typically takes **10-15 minutes** for typical repositories (e.g., `specfact-cli` itself with several hundred features & contracts). Smaller codebases may complete in 2-5 minutes. Large codebases (3000+ features) may take 15-30 minutes, but progress reporting shows real-time status. The analysis performs AST parsing, Semgrep pattern detection, and Specmatic integration.
|
|
@@ -432,8 +436,10 @@ specfact import from-code my-project --repo .
|
|
|
432
436
|
- **Prevent regressions** with runtime contract validation
|
|
433
437
|
- **CI/CD integration** - Block bad code from merging
|
|
434
438
|
- **Works offline** - No cloud required
|
|
439
|
+
- **Sidecar validation** - Validate external codebases without modifying source code
|
|
435
440
|
|
|
436
|
-
👉 **[Command Reference](docs/reference/commands.md)** - All enforcement commands
|
|
441
|
+
👉 **[Command Reference](docs/reference/commands.md)** - All enforcement commands
|
|
442
|
+
👉 **[Sidecar Validation Guide](docs/guides/sidecar-validation.md)** - Validate external codebases
|
|
437
443
|
|
|
438
444
|
### 👥 Team Collaboration
|
|
439
445
|
|
|
@@ -463,6 +469,12 @@ specfact import from-code my-project --repo .
|
|
|
463
469
|
|
|
464
470
|
👉 **[Brownfield Modernization Guide](docs/guides/brownfield-engineer.md)** - Complete walkthrough
|
|
465
471
|
|
|
472
|
+
### 1.5. Validating External Codebases (Sidecar Validation) 🆕
|
|
473
|
+
|
|
474
|
+
**Problem:** Need to validate third-party libraries or legacy codebases without modifying source code
|
|
475
|
+
|
|
476
|
+
👉 **[Sidecar Validation Guide](docs/guides/sidecar-validation.md)** - Validate external codebases with contract testing
|
|
477
|
+
|
|
466
478
|
### 2. Working with a Team
|
|
467
479
|
|
|
468
480
|
**Problem:** Need team collaboration with role-based workflows
|
|
@@ -502,6 +514,7 @@ specfact import from-code my-project --repo .
|
|
|
502
514
|
- **[AI IDE Workflow](docs/guides/ai-ide-workflow.md)** ⭐ **NEW** - AI-assisted development
|
|
503
515
|
- **[Agile/Scrum Workflows](docs/guides/agile-scrum-workflows.md)** ⭐ - Team collaboration
|
|
504
516
|
- **[Integrations Overview](docs/guides/integrations-overview.md)** ⭐ **NEW** - All integrations
|
|
517
|
+
- **[Sidecar Validation](docs/guides/sidecar-validation.md)** 🆕 - Validate external codebases without modifying source
|
|
505
518
|
- **[Use Cases](docs/guides/use-cases.md)** - Common scenarios
|
|
506
519
|
|
|
507
520
|
### Integration Guides
|
|
@@ -93,6 +93,10 @@ specfact init --ide cursor --install-deps
|
|
|
93
93
|
```bash
|
|
94
94
|
# Analyze legacy codebase (most common use case)
|
|
95
95
|
specfact import from-code my-project --repo .
|
|
96
|
+
|
|
97
|
+
# Or validate external codebase without modifying source (sidecar validation)
|
|
98
|
+
specfact validate sidecar init my-project /path/to/repo
|
|
99
|
+
specfact validate sidecar run my-project /path/to/repo
|
|
96
100
|
```
|
|
97
101
|
|
|
98
102
|
**⏱️ Timing:** Analysis typically takes **10-15 minutes** for typical repositories (e.g., `specfact-cli` itself with several hundred features & contracts). Smaller codebases may complete in 2-5 minutes. Large codebases (3000+ features) may take 15-30 minutes, but progress reporting shows real-time status. The analysis performs AST parsing, Semgrep pattern detection, and Specmatic integration.
|
|
@@ -157,8 +161,10 @@ specfact import from-code my-project --repo .
|
|
|
157
161
|
- **Prevent regressions** with runtime contract validation
|
|
158
162
|
- **CI/CD integration** - Block bad code from merging
|
|
159
163
|
- **Works offline** - No cloud required
|
|
164
|
+
- **Sidecar validation** - Validate external codebases without modifying source code
|
|
160
165
|
|
|
161
|
-
👉 **[Command Reference](docs/reference/commands.md)** - All enforcement commands
|
|
166
|
+
👉 **[Command Reference](docs/reference/commands.md)** - All enforcement commands
|
|
167
|
+
👉 **[Sidecar Validation Guide](docs/guides/sidecar-validation.md)** - Validate external codebases
|
|
162
168
|
|
|
163
169
|
### 👥 Team Collaboration
|
|
164
170
|
|
|
@@ -188,6 +194,12 @@ specfact import from-code my-project --repo .
|
|
|
188
194
|
|
|
189
195
|
👉 **[Brownfield Modernization Guide](docs/guides/brownfield-engineer.md)** - Complete walkthrough
|
|
190
196
|
|
|
197
|
+
### 1.5. Validating External Codebases (Sidecar Validation) 🆕
|
|
198
|
+
|
|
199
|
+
**Problem:** Need to validate third-party libraries or legacy codebases without modifying source code
|
|
200
|
+
|
|
201
|
+
👉 **[Sidecar Validation Guide](docs/guides/sidecar-validation.md)** - Validate external codebases with contract testing
|
|
202
|
+
|
|
191
203
|
### 2. Working with a Team
|
|
192
204
|
|
|
193
205
|
**Problem:** Need team collaboration with role-based workflows
|
|
@@ -227,6 +239,7 @@ specfact import from-code my-project --repo .
|
|
|
227
239
|
- **[AI IDE Workflow](docs/guides/ai-ide-workflow.md)** ⭐ **NEW** - AI-assisted development
|
|
228
240
|
- **[Agile/Scrum Workflows](docs/guides/agile-scrum-workflows.md)** ⭐ - Team collaboration
|
|
229
241
|
- **[Integrations Overview](docs/guides/integrations-overview.md)** ⭐ **NEW** - All integrations
|
|
242
|
+
- **[Sidecar Validation](docs/guides/sidecar-validation.md)** 🆕 - Validate external codebases without modifying source
|
|
230
243
|
- **[Use Cases](docs/guides/use-cases.md)** - Common scenarios
|
|
231
244
|
|
|
232
245
|
### Integration Guides
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specfact-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.24.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"
|
{specfact_cli-0.23.1 → specfact_cli-0.24.0}/resources/templates/sidecar/common/run_sidecar.sh
RENAMED
|
@@ -148,6 +148,54 @@ _filter_crosshair_dirs() {
|
|
|
148
148
|
echo "${filtered[@]}"
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
# Convert source directory paths to Python module names for CrossHair
|
|
152
|
+
# CrossHair expects module names (e.g., "sqlalchemy") not paths (e.g., "lib/sqlalchemy")
|
|
153
|
+
# This function extracts the module name and ensures PYTHONPATH includes the parent directory
|
|
154
|
+
_path_to_module() {
|
|
155
|
+
local source_path="$1"
|
|
156
|
+
local repo_path="${2:-${REPO_PATH}}"
|
|
157
|
+
|
|
158
|
+
# Remove trailing slash
|
|
159
|
+
source_path="${source_path%/}"
|
|
160
|
+
|
|
161
|
+
# If it's an absolute path, make it relative to repo
|
|
162
|
+
if [[ "$source_path" == /* ]]; then
|
|
163
|
+
source_path="${source_path#${repo_path}/}"
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
# Handle common patterns: lib/pkg, src/pkg, backend/app, pkg
|
|
167
|
+
# Extract the module name (last component that's a valid Python package)
|
|
168
|
+
local module_name=""
|
|
169
|
+
local parent_dir=""
|
|
170
|
+
|
|
171
|
+
if [[ "$source_path" == *"/"* ]]; then
|
|
172
|
+
# Path has directory structure (e.g., lib/sqlalchemy, src/mypackage)
|
|
173
|
+
parent_dir="${source_path%/*}" # Everything before last /
|
|
174
|
+
module_name="${source_path##*/}" # Last component
|
|
175
|
+
|
|
176
|
+
# Check if the module directory has __init__.py (is a package)
|
|
177
|
+
local full_module_path="${repo_path}/${source_path}"
|
|
178
|
+
if [[ -f "${full_module_path}/__init__.py" ]]; then
|
|
179
|
+
# It's a package - return module name and parent dir
|
|
180
|
+
echo "${module_name}|${repo_path}/${parent_dir}"
|
|
181
|
+
return 0
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
# Check subdirectories for packages (e.g., lib/sqlalchemy where sqlalchemy is the package)
|
|
185
|
+
for subdir in "${full_module_path}"/*; do
|
|
186
|
+
if [[ -d "$subdir" ]] && [[ -f "${subdir}/__init__.py" ]]; then
|
|
187
|
+
# Found a package subdirectory
|
|
188
|
+
echo "$(basename "$subdir")|${full_module_path}"
|
|
189
|
+
return 0
|
|
190
|
+
fi
|
|
191
|
+
done
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
# No directory structure or no package found - use the path as-is
|
|
195
|
+
# This handles cases like "mypackage" where PYTHONPATH is already set correctly
|
|
196
|
+
echo "${source_path}|"
|
|
197
|
+
}
|
|
198
|
+
|
|
151
199
|
run_with_timeout() {
|
|
152
200
|
local timeout_secs="$1"
|
|
153
201
|
shift
|
|
@@ -427,36 +475,68 @@ if [[ "${RUN_CROSSHAIR}" == "1" ]] && command -v crosshair >/dev/null 2>&1; then
|
|
|
427
475
|
if [[ -z "${CROSSHAIR_FILTERED_DIRS}" ]]; then
|
|
428
476
|
echo "[sidecar] warning: all source directories filtered out (contain tests), skipping source code analysis"
|
|
429
477
|
else
|
|
430
|
-
|
|
431
|
-
#
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
478
|
+
# Convert source paths to module names for CrossHair
|
|
479
|
+
# CrossHair expects module names (e.g., "sqlalchemy") not paths (e.g., "lib/sqlalchemy")
|
|
480
|
+
CROSSHAIR_MODULES=""
|
|
481
|
+
CROSSHAIR_EXTRA_PYTHONPATH=""
|
|
482
|
+
for src_dir in ${CROSSHAIR_FILTERED_DIRS}; do
|
|
483
|
+
MODULE_INFO=$(_path_to_module "$src_dir" "${REPO_PATH}")
|
|
484
|
+
MODULE_NAME="${MODULE_INFO%%|*}"
|
|
485
|
+
MODULE_PARENT="${MODULE_INFO##*|}"
|
|
486
|
+
|
|
487
|
+
if [[ -n "$MODULE_NAME" ]]; then
|
|
488
|
+
CROSSHAIR_MODULES="${CROSSHAIR_MODULES} ${MODULE_NAME}"
|
|
489
|
+
if [[ -n "$MODULE_PARENT" ]] && [[ ":${CROSSHAIR_EXTRA_PYTHONPATH}:" != *":${MODULE_PARENT}:"* ]]; then
|
|
490
|
+
CROSSHAIR_EXTRA_PYTHONPATH="${CROSSHAIR_EXTRA_PYTHONPATH}:${MODULE_PARENT}"
|
|
491
|
+
fi
|
|
439
492
|
fi
|
|
440
|
-
|
|
441
|
-
|
|
493
|
+
done
|
|
494
|
+
CROSSHAIR_MODULES="${CROSSHAIR_MODULES# }" # Trim leading space
|
|
495
|
+
CROSSHAIR_EXTRA_PYTHONPATH="${CROSSHAIR_EXTRA_PYTHONPATH#:}" # Trim leading colon
|
|
496
|
+
|
|
497
|
+
if [[ -z "${CROSSHAIR_MODULES}" ]]; then
|
|
498
|
+
echo "[sidecar] warning: could not convert source directories to modules, skipping source code analysis"
|
|
499
|
+
else
|
|
500
|
+
echo "[sidecar] analyzing modules: ${CROSSHAIR_MODULES}"
|
|
501
|
+
if [[ -n "${CROSSHAIR_EXTRA_PYTHONPATH}" ]]; then
|
|
502
|
+
echo "[sidecar] extra PYTHONPATH: ${CROSSHAIR_EXTRA_PYTHONPATH}"
|
|
442
503
|
fi
|
|
443
|
-
|
|
444
|
-
|
|
504
|
+
|
|
505
|
+
# Build PYTHONPATH for CrossHair (include extra paths for module resolution)
|
|
506
|
+
CROSSHAIR_PYTHONPATH="${PYTHONPATH:-}"
|
|
507
|
+
if [[ -n "${CROSSHAIR_EXTRA_PYTHONPATH}" ]]; then
|
|
508
|
+
CROSSHAIR_PYTHONPATH="${CROSSHAIR_EXTRA_PYTHONPATH}:${CROSSHAIR_PYTHONPATH}"
|
|
509
|
+
fi
|
|
510
|
+
|
|
511
|
+
if [[ "${FRAMEWORK_TYPE}" == "django" ]]; then
|
|
512
|
+
# Use Django-aware wrapper for source code analysis
|
|
513
|
+
CROSSHAIR_WRAPPER="${SIDECAR_DIR}/../frameworks/django/crosshair_django_wrapper.py"
|
|
514
|
+
if [[ -f "${CROSSHAIR_WRAPPER}" ]]; then
|
|
515
|
+
echo "[sidecar] using Django-aware CrossHair wrapper for source analysis"
|
|
516
|
+
# Export environment variables for Django initialization
|
|
517
|
+
CROSSHAIR_ENV=""
|
|
518
|
+
if [[ -n "${DJANGO_SETTINGS_MODULE:-}" ]]; then
|
|
519
|
+
CROSSHAIR_ENV="DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE} "
|
|
520
|
+
fi
|
|
521
|
+
if [[ -n "${REPO_PATH:-}" ]]; then
|
|
522
|
+
CROSSHAIR_ENV="${CROSSHAIR_ENV}REPO_PATH=${REPO_PATH} "
|
|
523
|
+
fi
|
|
524
|
+
CROSSHAIR_ENV="${CROSSHAIR_ENV}PYTHONPATH=${CROSSHAIR_PYTHONPATH} "
|
|
525
|
+
run_and_log "${TIMEOUT_CROSSHAIR}" \
|
|
526
|
+
"${SIDECAR_REPORTS_DIR}/${TIMESTAMP}-crosshair-source.log" \
|
|
527
|
+
env ${CROSSHAIR_ENV}"${PYTHON_CMD}" "${CROSSHAIR_WRAPPER}" check "${CROSSHAIR_ARGS[@]}" ${CROSSHAIR_MODULES}
|
|
528
|
+
else
|
|
529
|
+
echo "[sidecar] warning: Django wrapper not found, using standard CrossHair (may fail)"
|
|
530
|
+
run_and_log "${TIMEOUT_CROSSHAIR}" \
|
|
531
|
+
"${SIDECAR_REPORTS_DIR}/${TIMESTAMP}-crosshair-source.log" \
|
|
532
|
+
env PYTHONPATH="${CROSSHAIR_PYTHONPATH}" "${PYTHON_CMD}" -m crosshair check "${CROSSHAIR_ARGS[@]}" ${CROSSHAIR_MODULES}
|
|
533
|
+
fi
|
|
534
|
+
else
|
|
535
|
+
# Standard CrossHair for non-Django projects
|
|
536
|
+
run_and_log "${TIMEOUT_CROSSHAIR}" \
|
|
537
|
+
"${SIDECAR_REPORTS_DIR}/${TIMESTAMP}-crosshair-source.log" \
|
|
538
|
+
env PYTHONPATH="${CROSSHAIR_PYTHONPATH}" "${PYTHON_CMD}" -m crosshair check "${CROSSHAIR_ARGS[@]}" ${CROSSHAIR_MODULES}
|
|
445
539
|
fi
|
|
446
|
-
run_and_log "${TIMEOUT_CROSSHAIR}" \
|
|
447
|
-
"${SIDECAR_REPORTS_DIR}/${TIMESTAMP}-crosshair-source.log" \
|
|
448
|
-
env ${CROSSHAIR_ENV}"${PYTHON_CMD}" "${CROSSHAIR_WRAPPER}" check "${CROSSHAIR_ARGS[@]}" ${CROSSHAIR_FILTERED_DIRS}
|
|
449
|
-
else
|
|
450
|
-
echo "[sidecar] warning: Django wrapper not found, using standard CrossHair (may fail)"
|
|
451
|
-
run_and_log "${TIMEOUT_CROSSHAIR}" \
|
|
452
|
-
"${SIDECAR_REPORTS_DIR}/${TIMESTAMP}-crosshair-source.log" \
|
|
453
|
-
"${PYTHON_CMD}" -m crosshair check "${CROSSHAIR_ARGS[@]}" ${CROSSHAIR_FILTERED_DIRS}
|
|
454
|
-
fi
|
|
455
|
-
else
|
|
456
|
-
# Standard CrossHair for non-Django projects
|
|
457
|
-
run_and_log "${TIMEOUT_CROSSHAIR}" \
|
|
458
|
-
"${SIDECAR_REPORTS_DIR}/${TIMESTAMP}-crosshair-source.log" \
|
|
459
|
-
"${PYTHON_CMD}" -m crosshair check "${CROSSHAIR_ARGS[@]}" ${CROSSHAIR_FILTERED_DIRS}
|
|
460
540
|
fi
|
|
461
541
|
fi
|
|
462
542
|
fi
|
|
@@ -466,21 +546,30 @@ if [[ "${RUN_CROSSHAIR}" == "1" ]] && command -v crosshair >/dev/null 2>&1; then
|
|
|
466
546
|
# This is the primary analysis method for frameworks without decorators (Django, etc.)
|
|
467
547
|
if [[ -f "${HARNESS_PATH}" ]]; then
|
|
468
548
|
echo "[sidecar] crosshair (harness - external contracts)..."
|
|
549
|
+
|
|
550
|
+
# Build PYTHONPATH for harness analysis:
|
|
551
|
+
# 1. Sidecar directory (for harness imports like 'common.adapters')
|
|
552
|
+
# 2. Original PYTHONPATH (for repo modules)
|
|
553
|
+
HARNESS_DIR="$(cd "$(dirname "${HARNESS_PATH}")" && pwd)"
|
|
554
|
+
HARNESS_FILE="$(basename "${HARNESS_PATH}")"
|
|
555
|
+
HARNESS_MODULE="${HARNESS_FILE%.py}" # Remove .py extension
|
|
556
|
+
|
|
557
|
+
# Build PYTHONPATH: sidecar dir + original PYTHONPATH
|
|
558
|
+
HARNESS_PYTHONPATH="${HARNESS_DIR}"
|
|
559
|
+
if [[ -n "${PYTHONPATH:-}" ]]; then
|
|
560
|
+
HARNESS_PYTHONPATH="${HARNESS_PYTHONPATH}:${PYTHONPATH}"
|
|
561
|
+
fi
|
|
562
|
+
|
|
469
563
|
# Export environment variables for CrossHair subprocess
|
|
470
|
-
CROSSHAIR_ENV=""
|
|
564
|
+
CROSSHAIR_ENV="PYTHONPATH=${HARNESS_PYTHONPATH} "
|
|
471
565
|
if [[ -n "${DJANGO_SETTINGS_MODULE:-}" ]]; then
|
|
472
|
-
CROSSHAIR_ENV="DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE} "
|
|
566
|
+
CROSSHAIR_ENV="${CROSSHAIR_ENV}DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE} "
|
|
473
567
|
fi
|
|
474
568
|
if [[ -n "${REPO_PATH:-}" ]]; then
|
|
475
569
|
CROSSHAIR_ENV="${CROSSHAIR_ENV}REPO_PATH=${REPO_PATH} "
|
|
476
570
|
fi
|
|
477
|
-
|
|
478
|
-
CROSSHAIR_ENV="${CROSSHAIR_ENV}PYTHONPATH=${PYTHONPATH} "
|
|
479
|
-
fi
|
|
571
|
+
|
|
480
572
|
# Change to harness directory to ensure valid module name (avoids hyphenated directory names in module path)
|
|
481
|
-
HARNESS_DIR="$(dirname "${HARNESS_PATH}")"
|
|
482
|
-
HARNESS_FILE="$(basename "${HARNESS_PATH}")"
|
|
483
|
-
HARNESS_MODULE="${HARNESS_FILE%.py}" # Remove .py extension
|
|
484
573
|
run_and_log "${TIMEOUT_CROSSHAIR}" \
|
|
485
574
|
"${SIDECAR_REPORTS_DIR}/${TIMESTAMP}-crosshair-harness.log" \
|
|
486
575
|
bash -c "cd '${HARNESS_DIR}' && env ${CROSSHAIR_ENV}${PYTHON_CMD} -m crosshair check ${CROSSHAIR_ARGS[*]} ${HARNESS_MODULE}"
|
|
@@ -68,6 +68,7 @@ from specfact_cli.commands import (
|
|
|
68
68
|
sdd,
|
|
69
69
|
spec,
|
|
70
70
|
sync,
|
|
71
|
+
validate,
|
|
71
72
|
)
|
|
72
73
|
from specfact_cli.modes import OperationalMode, detect_mode
|
|
73
74
|
from specfact_cli.runtime import get_configured_console
|
|
@@ -350,6 +351,7 @@ app.add_typer(drift.app, name="drift", help="Detect drift between code and speci
|
|
|
350
351
|
|
|
351
352
|
# 11.6. Analysis
|
|
352
353
|
app.add_typer(analyze.app, name="analyze", help="Analyze codebase for contract coverage and quality")
|
|
354
|
+
app.add_typer(validate.app, name="validate", help="Validation commands including sidecar validation")
|
|
353
355
|
|
|
354
356
|
|
|
355
357
|
def cli_main() -> None:
|
|
@@ -146,6 +146,9 @@ def init(
|
|
|
146
146
|
"icontract>=2.7.1",
|
|
147
147
|
"crosshair-tool>=0.0.97",
|
|
148
148
|
"pytest>=8.4.2",
|
|
149
|
+
# Sidecar validation tools
|
|
150
|
+
# Note: specmatic may need separate installation (Java-based tool)
|
|
151
|
+
# Users may need to install specmatic separately: https://specmatic.in/documentation/getting_started.html
|
|
149
152
|
]
|
|
150
153
|
console.print("[dim]Installing packages for contract enhancement:[/dim]")
|
|
151
154
|
for package in required_packages:
|
|
@@ -167,6 +167,16 @@ def main(
|
|
|
167
167
|
help="Time budget in seconds (must be > 0)",
|
|
168
168
|
hidden=True, # Hidden by default, shown with --help-advanced
|
|
169
169
|
),
|
|
170
|
+
sidecar: bool = typer.Option(
|
|
171
|
+
False,
|
|
172
|
+
"--sidecar",
|
|
173
|
+
help="Run sidecar validation for unannotated code (no-edit path)",
|
|
174
|
+
),
|
|
175
|
+
sidecar_bundle: str | None = typer.Option(
|
|
176
|
+
None,
|
|
177
|
+
"--sidecar-bundle",
|
|
178
|
+
help="Bundle name for sidecar validation (required if --sidecar is used)",
|
|
179
|
+
),
|
|
170
180
|
) -> None:
|
|
171
181
|
"""
|
|
172
182
|
Run full validation suite for reproducibility.
|
|
@@ -182,6 +192,7 @@ def main(
|
|
|
182
192
|
- Contract exploration (CrossHair) - optional
|
|
183
193
|
- Property tests (pytest tests/contracts/) - optional, only if directory exists
|
|
184
194
|
- Smoke tests (pytest tests/smoke/) - optional, only if directory exists
|
|
195
|
+
- Sidecar validation (--sidecar) - optional, for unannotated code validation
|
|
185
196
|
|
|
186
197
|
Works on external repositories without requiring SpecFact CLI adoption.
|
|
187
198
|
|
|
@@ -189,6 +200,7 @@ def main(
|
|
|
189
200
|
specfact repro --verbose --budget 120
|
|
190
201
|
specfact repro --repo /path/to/external/repo --verbose
|
|
191
202
|
specfact repro --fix --budget 120
|
|
203
|
+
specfact repro --sidecar --sidecar-bundle legacy-api --repo /path/to/repo
|
|
192
204
|
"""
|
|
193
205
|
# If a subcommand was invoked, don't run the main validation
|
|
194
206
|
if ctx.invoked_subcommand is not None:
|
|
@@ -201,6 +213,8 @@ def main(
|
|
|
201
213
|
raise typer.BadParameter("Budget must be positive")
|
|
202
214
|
if not _is_valid_output_path(out):
|
|
203
215
|
raise typer.BadParameter("Output path must exist if provided")
|
|
216
|
+
if sidecar and not sidecar_bundle:
|
|
217
|
+
raise typer.BadParameter("--sidecar-bundle is required when --sidecar is used")
|
|
204
218
|
|
|
205
219
|
from specfact_cli.utils.yaml_utils import dump_yaml
|
|
206
220
|
|
|
@@ -319,6 +333,37 @@ def main(
|
|
|
319
333
|
dump_yaml(report.to_dict(), out)
|
|
320
334
|
console.print(f"\n[dim]Report written to: {out}[/dim]")
|
|
321
335
|
|
|
336
|
+
# Run sidecar validation if requested (after main checks)
|
|
337
|
+
if sidecar and sidecar_bundle:
|
|
338
|
+
from specfact_cli.validators.sidecar.models import SidecarConfig
|
|
339
|
+
from specfact_cli.validators.sidecar.orchestrator import run_sidecar_validation
|
|
340
|
+
from specfact_cli.validators.sidecar.unannotated_detector import detect_unannotated_in_repo
|
|
341
|
+
|
|
342
|
+
console.print("\n[bold cyan]Running sidecar validation for unannotated code...[/bold cyan]")
|
|
343
|
+
|
|
344
|
+
# Detect unannotated code
|
|
345
|
+
unannotated = detect_unannotated_in_repo(repo)
|
|
346
|
+
if unannotated:
|
|
347
|
+
console.print(f"[dim]Found {len(unannotated)} unannotated functions[/dim]")
|
|
348
|
+
# Store unannotated functions info for harness generation
|
|
349
|
+
sidecar_config = SidecarConfig.create(sidecar_bundle, repo)
|
|
350
|
+
# Pass unannotated info to orchestrator (via results dict)
|
|
351
|
+
else:
|
|
352
|
+
console.print("[dim]No unannotated functions detected (all functions have contracts)[/dim]")
|
|
353
|
+
sidecar_config = SidecarConfig.create(sidecar_bundle, repo)
|
|
354
|
+
|
|
355
|
+
# Run sidecar validation (harness will be generated for unannotated code)
|
|
356
|
+
sidecar_results = run_sidecar_validation(sidecar_config, console=console)
|
|
357
|
+
|
|
358
|
+
# Display sidecar results
|
|
359
|
+
if sidecar_results.get("crosshair_summary"):
|
|
360
|
+
summary = sidecar_results["crosshair_summary"]
|
|
361
|
+
console.print(
|
|
362
|
+
f"[dim]Sidecar CrossHair: {summary.get('confirmed', 0)} confirmed, "
|
|
363
|
+
f"{summary.get('not_confirmed', 0)} not confirmed, "
|
|
364
|
+
f"{summary.get('violations', 0)} violations[/dim]"
|
|
365
|
+
)
|
|
366
|
+
|
|
322
367
|
# Exit with appropriate code
|
|
323
368
|
exit_code = report.get_exit_code()
|
|
324
369
|
if exit_code == 0:
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Validate command group for SpecFact CLI.
|
|
3
|
+
|
|
4
|
+
This module provides validation commands including sidecar validation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
from beartype import beartype
|
|
13
|
+
from icontract import require
|
|
14
|
+
|
|
15
|
+
from specfact_cli.runtime import get_configured_console
|
|
16
|
+
from specfact_cli.validators.sidecar.crosshair_summary import format_summary_line
|
|
17
|
+
from specfact_cli.validators.sidecar.models import SidecarConfig
|
|
18
|
+
from specfact_cli.validators.sidecar.orchestrator import initialize_sidecar_workspace, run_sidecar_validation
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
app = typer.Typer(name="validate", help="Validation commands", suggest_commands=False)
|
|
22
|
+
console = get_configured_console()
|
|
23
|
+
|
|
24
|
+
# Create sidecar subcommand group
|
|
25
|
+
sidecar_app = typer.Typer(name="sidecar", help="Sidecar validation commands", suggest_commands=False)
|
|
26
|
+
app.add_typer(sidecar_app)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@sidecar_app.command()
|
|
30
|
+
@beartype
|
|
31
|
+
@require(lambda bundle_name: bundle_name and len(bundle_name.strip()) > 0, "Bundle name must be non-empty")
|
|
32
|
+
@require(lambda repo_path: repo_path.exists(), "Repository path must exist")
|
|
33
|
+
def init(
|
|
34
|
+
bundle_name: str = typer.Argument(..., help="Project bundle name (e.g., 'legacy-api')"),
|
|
35
|
+
repo_path: Path = typer.Argument(..., help="Path to repository root directory"),
|
|
36
|
+
) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Initialize sidecar workspace for validation.
|
|
39
|
+
|
|
40
|
+
Creates sidecar workspace directory structure and configuration for contract-based
|
|
41
|
+
validation of external codebases without modifying source code.
|
|
42
|
+
|
|
43
|
+
**What it does:**
|
|
44
|
+
- Detects framework type (Django, FastAPI, DRF, pure-python)
|
|
45
|
+
- Creates sidecar workspace directory structure
|
|
46
|
+
- Generates configuration files
|
|
47
|
+
- Detects Python environment (venv, poetry, uv, pip)
|
|
48
|
+
- Sets up framework-specific configuration (e.g., DJANGO_SETTINGS_MODULE)
|
|
49
|
+
|
|
50
|
+
**Example:**
|
|
51
|
+
```bash
|
|
52
|
+
specfact validate sidecar init legacy-api /path/to/repo
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Next steps:**
|
|
56
|
+
After initialization, run `specfact validate sidecar run` to execute validation.
|
|
57
|
+
"""
|
|
58
|
+
config = SidecarConfig.create(bundle_name, repo_path)
|
|
59
|
+
|
|
60
|
+
console.print(f"[bold]Initializing sidecar workspace for bundle: {bundle_name}[/bold]")
|
|
61
|
+
|
|
62
|
+
if initialize_sidecar_workspace(config):
|
|
63
|
+
console.print("[green]✓[/green] Sidecar workspace initialized successfully")
|
|
64
|
+
console.print(f" Framework detected: {config.framework_type}")
|
|
65
|
+
if config.django_settings_module:
|
|
66
|
+
console.print(f" Django settings: {config.django_settings_module}")
|
|
67
|
+
else:
|
|
68
|
+
console.print("[red]✗[/red] Failed to initialize sidecar workspace")
|
|
69
|
+
raise typer.Exit(1)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@sidecar_app.command()
|
|
73
|
+
@beartype
|
|
74
|
+
@require(lambda bundle_name: bundle_name and len(bundle_name.strip()) > 0, "Bundle name must be non-empty")
|
|
75
|
+
@require(lambda repo_path: repo_path.exists(), "Repository path must exist")
|
|
76
|
+
def run(
|
|
77
|
+
bundle_name: str = typer.Argument(..., help="Project bundle name (e.g., 'legacy-api')"),
|
|
78
|
+
repo_path: Path = typer.Argument(..., help="Path to repository root directory"),
|
|
79
|
+
run_crosshair: bool = typer.Option(
|
|
80
|
+
True, "--run-crosshair/--no-run-crosshair", help="Run CrossHair symbolic execution analysis"
|
|
81
|
+
),
|
|
82
|
+
run_specmatic: bool = typer.Option(
|
|
83
|
+
True, "--run-specmatic/--no-run-specmatic", help="Run Specmatic contract testing validation"
|
|
84
|
+
),
|
|
85
|
+
) -> None:
|
|
86
|
+
"""
|
|
87
|
+
Run sidecar validation workflow.
|
|
88
|
+
|
|
89
|
+
Executes complete sidecar validation workflow including framework detection,
|
|
90
|
+
route extraction, contract population, harness generation, and validation tools.
|
|
91
|
+
|
|
92
|
+
**Workflow steps:**
|
|
93
|
+
1. **Framework Detection**: Automatically detects Django, FastAPI, DRF, or pure-python
|
|
94
|
+
2. **Route Extraction**: Extracts routes and schemas from framework-specific patterns
|
|
95
|
+
3. **Contract Population**: Populates OpenAPI contracts with extracted routes/schemas
|
|
96
|
+
4. **Harness Generation**: Generates CrossHair harness from populated contracts
|
|
97
|
+
5. **CrossHair Analysis**: Runs symbolic execution on source code and harness (if enabled)
|
|
98
|
+
6. **Specmatic Validation**: Runs contract testing against API endpoints (if enabled)
|
|
99
|
+
|
|
100
|
+
**Example:**
|
|
101
|
+
```bash
|
|
102
|
+
# Run full validation (CrossHair + Specmatic)
|
|
103
|
+
specfact validate sidecar run legacy-api /path/to/repo
|
|
104
|
+
|
|
105
|
+
# Run only CrossHair analysis
|
|
106
|
+
specfact validate sidecar run legacy-api /path/to/repo --no-run-specmatic
|
|
107
|
+
|
|
108
|
+
# Run only Specmatic validation
|
|
109
|
+
specfact validate sidecar run legacy-api /path/to/repo --no-run-crosshair
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Output:**
|
|
113
|
+
- Validation results displayed in console
|
|
114
|
+
- Reports saved to `.specfact/projects/<bundle>/reports/sidecar/`
|
|
115
|
+
- Progress indicators for long-running operations
|
|
116
|
+
"""
|
|
117
|
+
config = SidecarConfig.create(bundle_name, repo_path)
|
|
118
|
+
config.tools.run_crosshair = run_crosshair
|
|
119
|
+
config.tools.run_specmatic = run_specmatic
|
|
120
|
+
|
|
121
|
+
console.print(f"[bold]Running sidecar validation for bundle: {bundle_name}[/bold]")
|
|
122
|
+
|
|
123
|
+
results = run_sidecar_validation(config, console=console)
|
|
124
|
+
|
|
125
|
+
# Display results
|
|
126
|
+
console.print("\n[bold]Validation Results:[/bold]")
|
|
127
|
+
console.print(f" Framework: {results.get('framework_detected', 'unknown')}")
|
|
128
|
+
console.print(f" Routes extracted: {results.get('routes_extracted', 0)}")
|
|
129
|
+
console.print(f" Contracts populated: {results.get('contracts_populated', 0)}")
|
|
130
|
+
console.print(f" Harness generated: {results.get('harness_generated', False)}")
|
|
131
|
+
|
|
132
|
+
if results.get("crosshair_results"):
|
|
133
|
+
console.print("\n[bold]CrossHair Results:[/bold]")
|
|
134
|
+
for key, value in results["crosshair_results"].items():
|
|
135
|
+
success = value.get("success", False)
|
|
136
|
+
status = "[green]✓[/green]" if success else "[red]✗[/red]"
|
|
137
|
+
console.print(f" {status} {key}")
|
|
138
|
+
|
|
139
|
+
# Display summary if available
|
|
140
|
+
if results.get("crosshair_summary"):
|
|
141
|
+
summary = results["crosshair_summary"]
|
|
142
|
+
summary_line = format_summary_line(summary)
|
|
143
|
+
console.print(f" {summary_line}")
|
|
144
|
+
|
|
145
|
+
# Show summary file location if generated
|
|
146
|
+
if results.get("crosshair_summary_file"):
|
|
147
|
+
console.print(f" Summary file: {results['crosshair_summary_file']}")
|
|
148
|
+
|
|
149
|
+
if results.get("specmatic_skipped"):
|
|
150
|
+
console.print(
|
|
151
|
+
f"\n[yellow]⚠ Specmatic skipped: {results.get('specmatic_skip_reason', 'Unknown reason')}[/yellow]"
|
|
152
|
+
)
|
|
153
|
+
elif results.get("specmatic_results"):
|
|
154
|
+
console.print("\n[bold]Specmatic Results:[/bold]")
|
|
155
|
+
for key, value in results["specmatic_results"].items():
|
|
156
|
+
success = value.get("success", False)
|
|
157
|
+
status = "[green]✓[/green]" if success else "[red]✗[/red]"
|
|
158
|
+
console.print(f" {status} {key}")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sidecar validation package.
|
|
3
|
+
|
|
4
|
+
This package provides native CLI integration for sidecar validation workflow,
|
|
5
|
+
enabling contract-based validation of external codebases without modifying source code.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = []
|