specfact-cli 0.20.0__tar.gz → 0.20.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.20.0 → specfact_cli-0.20.1}/PKG-INFO +1 -1
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/pyproject.toml +1 -1
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/__init__.py +1 -1
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/generate.py +63 -68
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/init.py +95 -9
- specfact_cli-0.20.1/src/specfact_cli/commands/repro.py +459 -0
- specfact_cli-0.20.1/src/specfact_cli/utils/env_manager.py +443 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/validators/repro_checker.py +154 -52
- specfact_cli-0.20.0/src/specfact_cli/commands/repro.py +0 -226
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/.gitignore +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/LICENSE.md +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/README.md +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/shared/cli-enforcement.md +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.01-import.md +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.02-plan.md +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.03-review.md +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.04-sdd.md +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.05-enforce.md +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.06-sync.md +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.07-contracts.md +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.compare.md +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/prompts/specfact.validate.md +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/cli.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/bridge.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/implement.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/project_cmd.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/persona_exporter.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/project.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/bridge_sync.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/speckit_sync.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/templates/bridge_templates.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/validators/schema.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.1}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.20.0 → specfact_cli-0.20.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.20.
|
|
3
|
+
Version: 0.20.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
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specfact-cli"
|
|
7
|
-
version = "0.20.
|
|
7
|
+
version = "0.20.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"
|
|
@@ -19,6 +19,12 @@ from specfact_cli.models.sdd import SDDManifest
|
|
|
19
19
|
from specfact_cli.models.task import TaskList, TaskPhase
|
|
20
20
|
from specfact_cli.telemetry import telemetry
|
|
21
21
|
from specfact_cli.utils import print_error, print_info, print_success, print_warning
|
|
22
|
+
from specfact_cli.utils.env_manager import (
|
|
23
|
+
build_tool_command,
|
|
24
|
+
detect_env_manager,
|
|
25
|
+
detect_source_directories,
|
|
26
|
+
find_test_files_for_source,
|
|
27
|
+
)
|
|
22
28
|
from specfact_cli.utils.optional_deps import check_cli_tool_available
|
|
23
29
|
from specfact_cli.utils.structured_io import load_structured_file
|
|
24
30
|
|
|
@@ -931,12 +937,24 @@ def apply_enhanced_contracts(
|
|
|
931
937
|
parts = enhanced_stem.split("-")
|
|
932
938
|
if len(parts) >= 2:
|
|
933
939
|
original_name = parts[1] # Get the original file name
|
|
934
|
-
#
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
+
# Detect source directories dynamically
|
|
941
|
+
source_dirs = detect_source_directories(repo_path)
|
|
942
|
+
# Build possible paths based on detected source directories
|
|
943
|
+
possible_paths: list[Path] = []
|
|
944
|
+
# Add root-level file
|
|
945
|
+
possible_paths.append(repo_path / f"{original_name}.py")
|
|
946
|
+
# Add paths based on detected source directories
|
|
947
|
+
for src_dir in source_dirs:
|
|
948
|
+
# Remove trailing slash if present
|
|
949
|
+
src_dir_clean = src_dir.rstrip("/")
|
|
950
|
+
possible_paths.append(repo_path / src_dir_clean / f"{original_name}.py")
|
|
951
|
+
# Also try common patterns as fallback
|
|
952
|
+
possible_paths.extend(
|
|
953
|
+
[
|
|
954
|
+
repo_path / f"src/{original_name}.py",
|
|
955
|
+
repo_path / f"lib/{original_name}.py",
|
|
956
|
+
]
|
|
957
|
+
)
|
|
940
958
|
for path in possible_paths:
|
|
941
959
|
if path.exists():
|
|
942
960
|
original_file = path
|
|
@@ -980,11 +998,16 @@ def apply_enhanced_contracts(
|
|
|
980
998
|
console.print("\n[bold cyan]Step 2/6: Validating enhanced code syntax...[/bold cyan]")
|
|
981
999
|
syntax_errors: list[str] = []
|
|
982
1000
|
try:
|
|
1001
|
+
# Detect environment manager and build appropriate command
|
|
1002
|
+
env_info = detect_env_manager(repo_path)
|
|
1003
|
+
python_command = ["python", "-m", "py_compile", str(enhanced_file)]
|
|
1004
|
+
compile_command = build_tool_command(env_info, python_command)
|
|
983
1005
|
result = subprocess.run(
|
|
984
|
-
|
|
1006
|
+
compile_command,
|
|
985
1007
|
capture_output=True,
|
|
986
1008
|
text=True,
|
|
987
1009
|
timeout=10,
|
|
1010
|
+
cwd=str(repo_path),
|
|
988
1011
|
)
|
|
989
1012
|
if result.returncode != 0:
|
|
990
1013
|
error_output = result.stderr.strip()
|
|
@@ -1104,6 +1127,9 @@ def apply_enhanced_contracts(
|
|
|
1104
1127
|
tools_checked = 0
|
|
1105
1128
|
tools_passed = 0
|
|
1106
1129
|
|
|
1130
|
+
# Detect environment manager for building commands
|
|
1131
|
+
env_info = detect_env_manager(repo_path)
|
|
1132
|
+
|
|
1107
1133
|
# List of common linting/formatting tools to check
|
|
1108
1134
|
linting_tools = [
|
|
1109
1135
|
("ruff", ["ruff", "check", str(enhanced_file)], "Ruff linting"),
|
|
@@ -1122,8 +1148,10 @@ def apply_enhanced_contracts(
|
|
|
1122
1148
|
console.print(f"[dim]Running {description}...[/dim]")
|
|
1123
1149
|
|
|
1124
1150
|
try:
|
|
1151
|
+
# Build command with environment manager prefix if needed
|
|
1152
|
+
command_full = build_tool_command(env_info, command)
|
|
1125
1153
|
result = subprocess.run(
|
|
1126
|
-
|
|
1154
|
+
command_full,
|
|
1127
1155
|
capture_output=True,
|
|
1128
1156
|
text=True,
|
|
1129
1157
|
timeout=30, # 30 seconds per tool
|
|
@@ -1180,56 +1208,10 @@ def apply_enhanced_contracts(
|
|
|
1180
1208
|
# Determine the source file we're testing (original or enhanced)
|
|
1181
1209
|
source_file_rel = original_file_rel if original_file_rel else enhanced_file_rel
|
|
1182
1210
|
|
|
1183
|
-
#
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
# Remove 'src/' prefix if present
|
|
1189
|
-
test_rel_path = str(source_file_rel)
|
|
1190
|
-
if test_rel_path.startswith("src/"):
|
|
1191
|
-
test_rel_path = test_rel_path[4:] # Remove 'src/'
|
|
1192
|
-
elif test_rel_path.startswith("tools/"):
|
|
1193
|
-
test_rel_path = test_rel_path[6:] # Remove 'tools/'
|
|
1194
|
-
|
|
1195
|
-
# Get directory and filename
|
|
1196
|
-
test_file_dir = Path(test_rel_path).parent
|
|
1197
|
-
test_file_name = Path(test_rel_path).stem # e.g., "telemetry" from "telemetry.py"
|
|
1198
|
-
|
|
1199
|
-
# Try common test file patterns
|
|
1200
|
-
test_file_patterns = [
|
|
1201
|
-
f"test_{test_file_name}.py",
|
|
1202
|
-
f"{test_file_name}_test.py",
|
|
1203
|
-
]
|
|
1204
|
-
|
|
1205
|
-
# Try common test directory structures
|
|
1206
|
-
test_dirs = [
|
|
1207
|
-
repo_path / "tests" / "unit" / test_file_dir,
|
|
1208
|
-
repo_path / "tests" / test_file_dir,
|
|
1209
|
-
repo_path / "tests" / "unit",
|
|
1210
|
-
repo_path / "tests",
|
|
1211
|
-
]
|
|
1212
|
-
|
|
1213
|
-
# Build list of possible test file paths
|
|
1214
|
-
for test_dir in test_dirs:
|
|
1215
|
-
if test_dir.exists():
|
|
1216
|
-
for pattern in test_file_patterns:
|
|
1217
|
-
test_path = test_dir / pattern
|
|
1218
|
-
if test_path.exists():
|
|
1219
|
-
test_paths.append(test_path)
|
|
1220
|
-
|
|
1221
|
-
# Also try E2E tests if unit tests not found
|
|
1222
|
-
if not test_paths:
|
|
1223
|
-
e2e_test_dirs = [
|
|
1224
|
-
repo_path / "tests" / "e2e" / test_file_dir,
|
|
1225
|
-
repo_path / "tests" / "e2e",
|
|
1226
|
-
]
|
|
1227
|
-
for test_dir in e2e_test_dirs:
|
|
1228
|
-
if test_dir.exists():
|
|
1229
|
-
for pattern in test_file_patterns:
|
|
1230
|
-
test_path = test_dir / pattern
|
|
1231
|
-
if test_path.exists():
|
|
1232
|
-
test_paths.append(test_path)
|
|
1211
|
+
# Use utility function to find test files dynamically
|
|
1212
|
+
test_paths = find_test_files_for_source(
|
|
1213
|
+
repo_path, source_file_rel if source_file_rel.is_absolute() else repo_path / source_file_rel
|
|
1214
|
+
)
|
|
1233
1215
|
|
|
1234
1216
|
# If we found specific test files, run them
|
|
1235
1217
|
if test_paths:
|
|
@@ -1238,8 +1220,13 @@ def apply_enhanced_contracts(
|
|
|
1238
1220
|
console.print(f"[dim]Found test file: {test_path.relative_to(repo_path)}[/dim]")
|
|
1239
1221
|
console.print("[dim]Running pytest on specific test file (fast, scoped validation)...[/dim]")
|
|
1240
1222
|
|
|
1223
|
+
# Detect environment manager and build appropriate command
|
|
1224
|
+
env_info = detect_env_manager(repo_path)
|
|
1225
|
+
pytest_command = ["pytest", str(test_path), "-v", "--tb=short"]
|
|
1226
|
+
pytest_command_full = build_tool_command(env_info, pytest_command)
|
|
1227
|
+
|
|
1241
1228
|
result = subprocess.run(
|
|
1242
|
-
|
|
1229
|
+
pytest_command_full,
|
|
1243
1230
|
capture_output=True,
|
|
1244
1231
|
text=True,
|
|
1245
1232
|
timeout=60, # 1 minute should be enough for a single test file
|
|
@@ -2030,10 +2017,21 @@ def generate_test_prompt(
|
|
|
2030
2017
|
console.print("\n[bold cyan]Files that may need tests:[/bold cyan]\n")
|
|
2031
2018
|
|
|
2032
2019
|
# Find Python files without corresponding test files
|
|
2020
|
+
# Use dynamic source directory detection
|
|
2021
|
+
source_dirs = detect_source_directories(repo_path)
|
|
2033
2022
|
src_files: list[Path] = []
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2023
|
+
# If no source dirs detected, check common patterns
|
|
2024
|
+
if not source_dirs:
|
|
2025
|
+
for src_dir in [repo_path / "src", repo_path / "lib", repo_path]:
|
|
2026
|
+
if src_dir.exists():
|
|
2027
|
+
src_files.extend(src_dir.rglob("*.py"))
|
|
2028
|
+
else:
|
|
2029
|
+
# Use detected source directories
|
|
2030
|
+
for src_dir_str in source_dirs:
|
|
2031
|
+
src_dir_clean = src_dir_str.rstrip("/")
|
|
2032
|
+
src_dir_path = repo_path / src_dir_clean
|
|
2033
|
+
if src_dir_path.exists():
|
|
2034
|
+
src_files.extend(src_dir_path.rglob("*.py"))
|
|
2037
2035
|
|
|
2038
2036
|
files_without_tests: list[tuple[Path, str]] = []
|
|
2039
2037
|
for src_file in src_files:
|
|
@@ -2042,12 +2040,9 @@ def generate_test_prompt(
|
|
|
2042
2040
|
if src_file.name.startswith("__"):
|
|
2043
2041
|
continue
|
|
2044
2042
|
|
|
2045
|
-
# Check for corresponding test file
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
repo_path / "tests" / f"test_{src_file.stem}.py",
|
|
2049
|
-
]
|
|
2050
|
-
has_test = any(tp.exists() for tp in test_patterns)
|
|
2043
|
+
# Check for corresponding test file using dynamic detection
|
|
2044
|
+
test_files = find_test_files_for_source(repo_path, src_file)
|
|
2045
|
+
has_test = len(test_files) > 0
|
|
2051
2046
|
if not has_test:
|
|
2052
2047
|
rel_path = src_file.relative_to(repo_path) if src_file.is_relative_to(repo_path) else src_file
|
|
2053
2048
|
files_without_tests.append((src_file, str(rel_path)))
|
|
@@ -18,6 +18,7 @@ from rich.console import Console
|
|
|
18
18
|
from rich.panel import Panel
|
|
19
19
|
|
|
20
20
|
from specfact_cli.telemetry import telemetry
|
|
21
|
+
from specfact_cli.utils.env_manager import EnvManager, build_tool_command, detect_env_manager
|
|
21
22
|
from specfact_cli.utils.ide_setup import (
|
|
22
23
|
IDE_CONFIG,
|
|
23
24
|
copy_templates_to_ide,
|
|
@@ -60,7 +61,7 @@ def init(
|
|
|
60
61
|
install_deps: bool = typer.Option(
|
|
61
62
|
False,
|
|
62
63
|
"--install-deps",
|
|
63
|
-
help="Install required packages for contract enhancement (beartype, icontract, crosshair-tool, pytest)
|
|
64
|
+
help="Install required packages for contract enhancement (beartype, icontract, crosshair-tool, pytest) using detected environment manager",
|
|
64
65
|
),
|
|
65
66
|
# Advanced/Configuration
|
|
66
67
|
ide: str = typer.Option(
|
|
@@ -105,10 +106,41 @@ def init(
|
|
|
105
106
|
console.print(f"[cyan]IDE:[/cyan] {ide_name} ({detected_ide})")
|
|
106
107
|
console.print()
|
|
107
108
|
|
|
109
|
+
# Check for environment manager
|
|
110
|
+
env_info = detect_env_manager(repo_path)
|
|
111
|
+
if env_info.manager == EnvManager.UNKNOWN:
|
|
112
|
+
console.print()
|
|
113
|
+
console.print(
|
|
114
|
+
Panel(
|
|
115
|
+
"[bold yellow]⚠ No Compatible Environment Manager Detected[/bold yellow]",
|
|
116
|
+
border_style="yellow",
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
console.print(
|
|
120
|
+
"[yellow]SpecFact CLI works best with projects using standard Python project management tools.[/yellow]"
|
|
121
|
+
)
|
|
122
|
+
console.print()
|
|
123
|
+
console.print("[dim]Supported tools:[/dim]")
|
|
124
|
+
console.print(" - hatch (detected from [tool.hatch] in pyproject.toml)")
|
|
125
|
+
console.print(" - poetry (detected from [tool.poetry] in pyproject.toml or poetry.lock)")
|
|
126
|
+
console.print(" - uv (detected from [tool.uv] in pyproject.toml, uv.lock, or uv.toml)")
|
|
127
|
+
console.print(" - pip (detected from requirements.txt or setup.py)")
|
|
128
|
+
console.print()
|
|
129
|
+
console.print(
|
|
130
|
+
"[dim]Note: SpecFact CLI will still work, but commands like 'specfact repro' may use direct tool invocation.[/dim]"
|
|
131
|
+
)
|
|
132
|
+
console.print(
|
|
133
|
+
"[dim]Consider adding a pyproject.toml with [tool.hatch], [tool.poetry], or [tool.uv] for better integration.[/dim]"
|
|
134
|
+
)
|
|
135
|
+
console.print()
|
|
136
|
+
|
|
108
137
|
# Install dependencies if requested
|
|
109
138
|
if install_deps:
|
|
110
139
|
console.print()
|
|
111
140
|
console.print(Panel("[bold cyan]Installing Required Packages[/bold cyan]", border_style="cyan"))
|
|
141
|
+
if env_info.message:
|
|
142
|
+
console.print(f"[dim]{env_info.message}[/dim]")
|
|
143
|
+
|
|
112
144
|
required_packages = [
|
|
113
145
|
"beartype>=0.22.4",
|
|
114
146
|
"icontract>=2.7.1",
|
|
@@ -119,19 +151,32 @@ def init(
|
|
|
119
151
|
for package in required_packages:
|
|
120
152
|
console.print(f" - {package}")
|
|
121
153
|
|
|
154
|
+
# Build install command using environment manager detection
|
|
155
|
+
install_cmd = ["pip", "install", "-U", *required_packages]
|
|
156
|
+
install_cmd = build_tool_command(env_info, install_cmd)
|
|
157
|
+
|
|
158
|
+
console.print(f"[dim]Using command: {' '.join(install_cmd)}[/dim]")
|
|
159
|
+
|
|
122
160
|
try:
|
|
123
|
-
# Use pip to install packages
|
|
124
161
|
result = subprocess.run(
|
|
125
|
-
|
|
162
|
+
install_cmd,
|
|
126
163
|
capture_output=True,
|
|
127
164
|
text=True,
|
|
128
165
|
check=False,
|
|
166
|
+
cwd=str(repo_path),
|
|
167
|
+
timeout=300, # 5 minute timeout
|
|
129
168
|
)
|
|
130
169
|
|
|
131
170
|
if result.returncode == 0:
|
|
132
171
|
console.print()
|
|
133
172
|
console.print("[green]✓[/green] All required packages installed successfully")
|
|
134
|
-
record(
|
|
173
|
+
record(
|
|
174
|
+
{
|
|
175
|
+
"deps_installed": True,
|
|
176
|
+
"packages_count": len(required_packages),
|
|
177
|
+
"env_manager": env_info.manager.value,
|
|
178
|
+
}
|
|
179
|
+
)
|
|
135
180
|
else:
|
|
136
181
|
console.print()
|
|
137
182
|
console.print("[yellow]⚠[/yellow] Some packages failed to install")
|
|
@@ -142,19 +187,60 @@ def init(
|
|
|
142
187
|
console.print(result.stderr)
|
|
143
188
|
console.print()
|
|
144
189
|
console.print("[yellow]You may need to install packages manually:[/yellow]")
|
|
190
|
+
# Provide environment-specific guidance
|
|
191
|
+
if env_info.manager == EnvManager.HATCH:
|
|
192
|
+
console.print(f" hatch run pip install {' '.join(required_packages)}")
|
|
193
|
+
elif env_info.manager == EnvManager.POETRY:
|
|
194
|
+
console.print(f" poetry add --dev {' '.join(required_packages)}")
|
|
195
|
+
elif env_info.manager == EnvManager.UV:
|
|
196
|
+
console.print(f" uv pip install {' '.join(required_packages)}")
|
|
197
|
+
else:
|
|
198
|
+
console.print(f" pip install {' '.join(required_packages)}")
|
|
199
|
+
record(
|
|
200
|
+
{
|
|
201
|
+
"deps_installed": False,
|
|
202
|
+
"error": result.stderr[:200] if result.stderr else "Unknown error",
|
|
203
|
+
"env_manager": env_info.manager.value,
|
|
204
|
+
}
|
|
205
|
+
)
|
|
206
|
+
except subprocess.TimeoutExpired:
|
|
207
|
+
console.print()
|
|
208
|
+
console.print("[red]Error:[/red] Installation timed out after 5 minutes")
|
|
209
|
+
console.print("[yellow]You may need to install packages manually:[/yellow]")
|
|
210
|
+
if env_info.manager == EnvManager.HATCH:
|
|
211
|
+
console.print(f" hatch run pip install {' '.join(required_packages)}")
|
|
212
|
+
elif env_info.manager == EnvManager.POETRY:
|
|
213
|
+
console.print(f" poetry add --dev {' '.join(required_packages)}")
|
|
214
|
+
elif env_info.manager == EnvManager.UV:
|
|
215
|
+
console.print(f" uv pip install {' '.join(required_packages)}")
|
|
216
|
+
else:
|
|
145
217
|
console.print(f" pip install {' '.join(required_packages)}")
|
|
146
|
-
|
|
218
|
+
record({"deps_installed": False, "error": "timeout", "env_manager": env_info.manager.value})
|
|
147
219
|
except FileNotFoundError:
|
|
148
220
|
console.print()
|
|
149
221
|
console.print("[red]Error:[/red] pip not found. Please install packages manually:")
|
|
150
|
-
|
|
151
|
-
|
|
222
|
+
if env_info.manager == EnvManager.HATCH:
|
|
223
|
+
console.print(f" hatch run pip install {' '.join(required_packages)}")
|
|
224
|
+
elif env_info.manager == EnvManager.POETRY:
|
|
225
|
+
console.print(f" poetry add --dev {' '.join(required_packages)}")
|
|
226
|
+
elif env_info.manager == EnvManager.UV:
|
|
227
|
+
console.print(f" uv pip install {' '.join(required_packages)}")
|
|
228
|
+
else:
|
|
229
|
+
console.print(f" pip install {' '.join(required_packages)}")
|
|
230
|
+
record({"deps_installed": False, "error": "pip not found", "env_manager": env_info.manager.value})
|
|
152
231
|
except Exception as e:
|
|
153
232
|
console.print()
|
|
154
233
|
console.print(f"[red]Error:[/red] Failed to install packages: {e}")
|
|
155
234
|
console.print("[yellow]You may need to install packages manually:[/yellow]")
|
|
156
|
-
|
|
157
|
-
|
|
235
|
+
if env_info.manager == EnvManager.HATCH:
|
|
236
|
+
console.print(f" hatch run pip install {' '.join(required_packages)}")
|
|
237
|
+
elif env_info.manager == EnvManager.POETRY:
|
|
238
|
+
console.print(f" poetry add --dev {' '.join(required_packages)}")
|
|
239
|
+
elif env_info.manager == EnvManager.UV:
|
|
240
|
+
console.print(f" uv pip install {' '.join(required_packages)}")
|
|
241
|
+
else:
|
|
242
|
+
console.print(f" pip install {' '.join(required_packages)}")
|
|
243
|
+
record({"deps_installed": False, "error": str(e), "env_manager": env_info.manager.value})
|
|
158
244
|
console.print()
|
|
159
245
|
|
|
160
246
|
# Find templates directory
|