specfact-cli 0.23.1__tar.gz → 0.24.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.23.1 → specfact_cli-0.24.1}/.gitignore +2 -1
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/PKG-INFO +15 -2
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/README.md +14 -1
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/pyproject.toml +1 -1
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/common/run_sidecar.sh +124 -35
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/__init__.py +1 -1
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/cli.py +2 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/commands/__init__.py +2 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/commands/init.py +3 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/commands/repro.py +45 -0
- specfact_cli-0.24.1/src/specfact_cli/commands/validate.py +273 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/__init__.py +11 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/contract_populator.py +151 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/crosshair_runner.py +109 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/crosshair_summary.py +268 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/dependency_installer.py +234 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/framework_detector.py +184 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/frameworks/__init__.py +12 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/frameworks/base.py +84 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/frameworks/django.py +322 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/frameworks/drf.py +95 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +225 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/frameworks/flask.py +298 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/harness_generator.py +698 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/models.py +196 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/orchestrator.py +447 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/specmatic_runner.py +106 -0
- specfact_cli-0.24.1/src/specfact_cli/validators/sidecar/unannotated_detector.py +140 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/LICENSE.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/prompts/shared/cli-enforcement.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/prompts/specfact.01-import.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/prompts/specfact.02-plan.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/prompts/specfact.03-review.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/prompts/specfact.04-sdd.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/prompts/specfact.05-enforce.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/prompts/specfact.06-sync.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/prompts/specfact.07-contracts.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/prompts/specfact.compare.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/prompts/specfact.sync-backlog.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/prompts/specfact.validate.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/STRUCTURE.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/common/README.md +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/common/adapters.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/common/bindings.yaml.example +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/common/crosshair_plugin.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/common/generate_harness.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/common/harness_contracts.py.example +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/common/populate_contracts.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/common/sidecar-init.sh +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/frameworks/django/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/frameworks/django/crosshair_django_wrapper.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/frameworks/django/django_form_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/frameworks/django/django_url_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/frameworks/drf/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/frameworks/drf/drf_serializer_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/frameworks/fastapi/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/sidecar/frameworks/fastapi/fastapi_route_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/adapters/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/adapters/base.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/adapters/github.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/adapters/openspec.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/adapters/openspec_parser.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/adapters/registry.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/adapters/speckit.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/commands/project_cmd.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/generators/persona_exporter.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/models/capabilities.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/models/change.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/models/project.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/sync/bridge_sync.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/templates/bridge_templates.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/code_change_detector.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/content_sanitizer.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/env_manager.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/terminal.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/validators/schema.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.1}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.23.1 → specfact_cli-0.24.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.24.1
|
|
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.1"
|
|
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.1}/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:
|