specfact-cli 0.12.1__tar.gz → 0.13.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.12.1 → specfact_cli-0.13.0}/PKG-INFO +49 -37
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/README.md +48 -36
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/pyproject.toml +1 -1
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/analyze.py +53 -143
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/enforce.py +46 -31
- specfact_cli-0.13.0/src/specfact_cli/commands/generate.py +1391 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/import_cmd.py +86 -5
- specfact_cli-0.13.0/src/specfact_cli/commands/spec.py +861 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/sync.py +69 -0
- specfact_cli-0.12.1/src/specfact_cli/commands/generate.py +0 -515
- specfact_cli-0.12.1/src/specfact_cli/commands/spec.py +0 -407
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/.gitignore +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/LICENSE.md +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/shared/cli-enforcement.md +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/specfact.01-import.md +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/specfact.02-plan.md +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/specfact.03-review.md +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/specfact.04-sdd.md +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/specfact.05-enforce.md +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/specfact.06-sync.md +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/specfact.compare.md +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/specfact.validate.md +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/__init__.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/cli.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/bridge.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/implement.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/run.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/project.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/bridge_sync.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/speckit_sync.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/templates/bridge_templates.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/validators/schema.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specfact-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.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
|
|
@@ -275,20 +275,58 @@ Description-Content-Type: text/markdown
|
|
|
275
275
|
|
|
276
276
|
# SpecFact CLI
|
|
277
277
|
|
|
278
|
-
> **
|
|
279
|
-
>
|
|
278
|
+
> **Stop vibe coding. Start shipping quality code with contracts.**
|
|
279
|
+
> Brownfield-first CLI: Reverse engineer legacy Python → specs → enforced contracts
|
|
280
280
|
|
|
281
|
+
[](https://pypi.org/project/specfact-cli/)
|
|
282
|
+
[](https://pypi.org/project/specfact-cli/)
|
|
281
283
|
[](LICENSE.md)
|
|
282
|
-
[](https://www.python.org/)
|
|
283
284
|
[](https://github.com/nold-ai/specfact-cli)
|
|
284
285
|
|
|
286
|
+
<div align="center">
|
|
287
|
+
|
|
288
|
+
**[🌐 Learn More at noldai.com](https://noldai.com)** • **[📚 Documentation](https://nold-ai.github.io/specfact-cli)** • **[💬 Support](mailto:hello@noldai.com)**
|
|
289
|
+
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## 🚀 Quick Start in 60 Seconds
|
|
295
|
+
|
|
296
|
+
### Install in 10 seconds
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
# Zero-install (recommended)
|
|
300
|
+
uvx --from specfact-cli specfact
|
|
301
|
+
|
|
302
|
+
# Or install with pip
|
|
303
|
+
pip install specfact-cli
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Your first command (< 60 seconds)
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
# Modernizing legacy code? (Recommended)
|
|
310
|
+
specfact import from-code legacy-api --repo .
|
|
311
|
+
|
|
312
|
+
# Starting a new project?
|
|
313
|
+
specfact plan init legacy-api --interactive
|
|
314
|
+
|
|
315
|
+
# Using GitHub Spec-Kit or other tools?
|
|
316
|
+
specfact import from-bridge --repo . --adapter speckit --write
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
That's it! 🎉
|
|
320
|
+
|
|
321
|
+
> **Need machine-readable artifacts?** Use `specfact --output-format json …` (or the per-command `--output-format` flag) to emit plan bundles and reports as JSON instead of YAML.
|
|
322
|
+
|
|
285
323
|
---
|
|
286
324
|
|
|
287
325
|
## What is SpecFact CLI?
|
|
288
326
|
|
|
289
327
|
A brownfield-first CLI that **reverse engineers your legacy code** into documented specs, then prevents regressions with runtime contract enforcement.
|
|
290
328
|
|
|
291
|
-
**Stop
|
|
329
|
+
**Stop vibe coding. Start shipping quality code with contracts.** SpecFact automatically extracts specs from existing code, then enforces them as you modernize—preventing bugs before they reach production.
|
|
292
330
|
|
|
293
331
|
**Perfect for:** Teams modernizing legacy Python systems, data pipelines, DevOps scripts
|
|
294
332
|
|
|
@@ -347,37 +385,6 @@ SpecFact CLI works with your existing tools—no new platform to learn. See real
|
|
|
347
385
|
|
|
348
386
|
---
|
|
349
387
|
|
|
350
|
-
## Quick Start
|
|
351
|
-
|
|
352
|
-
### Install in 10 seconds
|
|
353
|
-
|
|
354
|
-
```bash
|
|
355
|
-
# Zero-install (just run it)
|
|
356
|
-
uvx specfact-cli@latest
|
|
357
|
-
|
|
358
|
-
# Or install with pip
|
|
359
|
-
pip install specfact-cli
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
### Your first command (< 60 seconds)
|
|
363
|
-
|
|
364
|
-
```bash
|
|
365
|
-
# Modernizing legacy code? (Recommended)
|
|
366
|
-
specfact import from-code legacy-api --repo .
|
|
367
|
-
|
|
368
|
-
# Starting a new project?
|
|
369
|
-
specfact plan init legacy-api --interactive
|
|
370
|
-
|
|
371
|
-
# Using GitHub Spec-Kit or other tools?
|
|
372
|
-
specfact import from-bridge --repo . --adapter speckit --write
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
That's it! 🎉
|
|
376
|
-
|
|
377
|
-
> Need machine-readable artifacts? Use `specfact --output-format json …` (or the per-command `--output-format` flag) to emit plan bundles and reports as JSON instead of YAML.
|
|
378
|
-
|
|
379
|
-
---
|
|
380
|
-
|
|
381
388
|
## See It In Action
|
|
382
389
|
|
|
383
390
|
We ran SpecFact CLI **on itself** to prove it works with legacy code:
|
|
@@ -477,11 +484,16 @@ SpecFact CLI is licensed under the Apache License 2.0, which means:
|
|
|
477
484
|
- 💬 **Questions?** [GitHub Discussions](https://github.com/nold-ai/specfact-cli/discussions)
|
|
478
485
|
- 🐛 **Found a bug?** [GitHub Issues](https://github.com/nold-ai/specfact-cli/issues)
|
|
479
486
|
- 📧 **Need help?** [hello@noldai.com](mailto:hello@noldai.com)
|
|
487
|
+
- 🌐 **Learn more:** [noldai.com](https://noldai.com)
|
|
480
488
|
|
|
481
489
|
---
|
|
482
490
|
|
|
483
|
-
>
|
|
491
|
+
<div align="center">
|
|
492
|
+
|
|
493
|
+
**Built with ❤️ by [NOLD AI](https://noldai.com)**
|
|
484
494
|
|
|
485
495
|
Copyright © 2025 Nold AI (Owner: Dominikus Nold)
|
|
486
496
|
|
|
487
497
|
**Trademarks**: NOLD AI (NOLDAI) is a registered trademark (wordmark) at the European Union Intellectual Property Office (EUIPO). All other trademarks mentioned in this project are the property of their respective owners. See [TRADEMARKS.md](TRADEMARKS.md) for more information.
|
|
498
|
+
|
|
499
|
+
</div>
|
|
@@ -1,19 +1,57 @@
|
|
|
1
1
|
# SpecFact CLI
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
>
|
|
3
|
+
> **Stop vibe coding. Start shipping quality code with contracts.**
|
|
4
|
+
> Brownfield-first CLI: Reverse engineer legacy Python → specs → enforced contracts
|
|
5
5
|
|
|
6
|
+
[](https://pypi.org/project/specfact-cli/)
|
|
7
|
+
[](https://pypi.org/project/specfact-cli/)
|
|
6
8
|
[](LICENSE.md)
|
|
7
|
-
[](https://www.python.org/)
|
|
8
9
|
[](https://github.com/nold-ai/specfact-cli)
|
|
9
10
|
|
|
11
|
+
<div align="center">
|
|
12
|
+
|
|
13
|
+
**[🌐 Learn More at noldai.com](https://noldai.com)** • **[📚 Documentation](https://nold-ai.github.io/specfact-cli)** • **[💬 Support](mailto:hello@noldai.com)**
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 🚀 Quick Start in 60 Seconds
|
|
20
|
+
|
|
21
|
+
### Install in 10 seconds
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Zero-install (recommended)
|
|
25
|
+
uvx --from specfact-cli specfact
|
|
26
|
+
|
|
27
|
+
# Or install with pip
|
|
28
|
+
pip install specfact-cli
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Your first command (< 60 seconds)
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Modernizing legacy code? (Recommended)
|
|
35
|
+
specfact import from-code legacy-api --repo .
|
|
36
|
+
|
|
37
|
+
# Starting a new project?
|
|
38
|
+
specfact plan init legacy-api --interactive
|
|
39
|
+
|
|
40
|
+
# Using GitHub Spec-Kit or other tools?
|
|
41
|
+
specfact import from-bridge --repo . --adapter speckit --write
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
That's it! 🎉
|
|
45
|
+
|
|
46
|
+
> **Need machine-readable artifacts?** Use `specfact --output-format json …` (or the per-command `--output-format` flag) to emit plan bundles and reports as JSON instead of YAML.
|
|
47
|
+
|
|
10
48
|
---
|
|
11
49
|
|
|
12
50
|
## What is SpecFact CLI?
|
|
13
51
|
|
|
14
52
|
A brownfield-first CLI that **reverse engineers your legacy code** into documented specs, then prevents regressions with runtime contract enforcement.
|
|
15
53
|
|
|
16
|
-
**Stop
|
|
54
|
+
**Stop vibe coding. Start shipping quality code with contracts.** SpecFact automatically extracts specs from existing code, then enforces them as you modernize—preventing bugs before they reach production.
|
|
17
55
|
|
|
18
56
|
**Perfect for:** Teams modernizing legacy Python systems, data pipelines, DevOps scripts
|
|
19
57
|
|
|
@@ -72,37 +110,6 @@ SpecFact CLI works with your existing tools—no new platform to learn. See real
|
|
|
72
110
|
|
|
73
111
|
---
|
|
74
112
|
|
|
75
|
-
## Quick Start
|
|
76
|
-
|
|
77
|
-
### Install in 10 seconds
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
# Zero-install (just run it)
|
|
81
|
-
uvx specfact-cli@latest
|
|
82
|
-
|
|
83
|
-
# Or install with pip
|
|
84
|
-
pip install specfact-cli
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Your first command (< 60 seconds)
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
# Modernizing legacy code? (Recommended)
|
|
91
|
-
specfact import from-code legacy-api --repo .
|
|
92
|
-
|
|
93
|
-
# Starting a new project?
|
|
94
|
-
specfact plan init legacy-api --interactive
|
|
95
|
-
|
|
96
|
-
# Using GitHub Spec-Kit or other tools?
|
|
97
|
-
specfact import from-bridge --repo . --adapter speckit --write
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
That's it! 🎉
|
|
101
|
-
|
|
102
|
-
> Need machine-readable artifacts? Use `specfact --output-format json …` (or the per-command `--output-format` flag) to emit plan bundles and reports as JSON instead of YAML.
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
113
|
## See It In Action
|
|
107
114
|
|
|
108
115
|
We ran SpecFact CLI **on itself** to prove it works with legacy code:
|
|
@@ -202,11 +209,16 @@ SpecFact CLI is licensed under the Apache License 2.0, which means:
|
|
|
202
209
|
- 💬 **Questions?** [GitHub Discussions](https://github.com/nold-ai/specfact-cli/discussions)
|
|
203
210
|
- 🐛 **Found a bug?** [GitHub Issues](https://github.com/nold-ai/specfact-cli/issues)
|
|
204
211
|
- 📧 **Need help?** [hello@noldai.com](mailto:hello@noldai.com)
|
|
212
|
+
- 🌐 **Learn more:** [noldai.com](https://noldai.com)
|
|
205
213
|
|
|
206
214
|
---
|
|
207
215
|
|
|
208
|
-
>
|
|
216
|
+
<div align="center">
|
|
217
|
+
|
|
218
|
+
**Built with ❤️ by [NOLD AI](https://noldai.com)**
|
|
209
219
|
|
|
210
220
|
Copyright © 2025 Nold AI (Owner: Dominikus Nold)
|
|
211
221
|
|
|
212
222
|
**Trademarks**: NOLD AI (NOLDAI) is a registered trademark (wordmark) at the European Union Intellectual Property Office (EUIPO). All other trademarks mentioned in this project are the property of their respective owners. See [TRADEMARKS.md](TRADEMARKS.md) for more information.
|
|
223
|
+
|
|
224
|
+
</div>
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specfact-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.13.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"
|
|
@@ -15,9 +15,11 @@ from icontract import ensure, require
|
|
|
15
15
|
from rich.console import Console
|
|
16
16
|
from rich.table import Table
|
|
17
17
|
|
|
18
|
-
from specfact_cli.models.quality import CodeQuality
|
|
18
|
+
from specfact_cli.models.quality import CodeQuality, QualityTracking
|
|
19
19
|
from specfact_cli.telemetry import telemetry
|
|
20
20
|
from specfact_cli.utils import print_error, print_success
|
|
21
|
+
from specfact_cli.utils.progress import load_bundle_with_progress
|
|
22
|
+
from specfact_cli.utils.structure import SpecFactStructure
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
app = typer.Typer(help="Analyze codebase for contract coverage and quality")
|
|
@@ -27,7 +29,10 @@ console = Console()
|
|
|
27
29
|
@app.command("contracts")
|
|
28
30
|
@beartype
|
|
29
31
|
@require(lambda repo: isinstance(repo, Path), "Repository path must be Path")
|
|
30
|
-
@require(
|
|
32
|
+
@require(
|
|
33
|
+
lambda bundle: bundle is None or (isinstance(bundle, str) and len(bundle) > 0),
|
|
34
|
+
"Bundle name must be None or non-empty string",
|
|
35
|
+
)
|
|
31
36
|
@ensure(lambda result: result is None, "Must return None")
|
|
32
37
|
def analyze_contracts(
|
|
33
38
|
# Target/Input
|
|
@@ -57,12 +62,6 @@ def analyze_contracts(
|
|
|
57
62
|
**Examples:**
|
|
58
63
|
specfact analyze contracts --repo . --bundle legacy-api
|
|
59
64
|
"""
|
|
60
|
-
from rich.console import Console
|
|
61
|
-
|
|
62
|
-
from specfact_cli.models.quality import QualityTracking
|
|
63
|
-
from specfact_cli.utils.progress import load_bundle_with_progress
|
|
64
|
-
from specfact_cli.utils.structure import SpecFactStructure
|
|
65
|
-
|
|
66
65
|
console = Console()
|
|
67
66
|
|
|
68
67
|
# Use active plan as default if bundle not provided
|
|
@@ -119,43 +118,68 @@ def analyze_contracts(
|
|
|
119
118
|
if quality.crosshair:
|
|
120
119
|
files_with_crosshair += 1
|
|
121
120
|
|
|
121
|
+
# Sort files: prioritize files missing contracts
|
|
122
|
+
# Sort key: (has_all_contracts, total_contracts, file_path)
|
|
123
|
+
# This puts files missing contracts first, then by number of contracts (asc), then alphabetically
|
|
124
|
+
def sort_key(item: tuple[str, CodeQuality]) -> tuple[bool, int, str]:
|
|
125
|
+
file_path, quality = item
|
|
126
|
+
has_all = quality.beartype and quality.icontract and quality.crosshair
|
|
127
|
+
total_contracts = sum([quality.beartype, quality.icontract, quality.crosshair])
|
|
128
|
+
return (has_all, total_contracts, file_path)
|
|
129
|
+
|
|
130
|
+
sorted_files = sorted(quality_tracking.code_quality.items(), key=sort_key)
|
|
131
|
+
|
|
132
|
+
# Show files needing attention first, limit to 30 for readability
|
|
133
|
+
max_display = 30
|
|
134
|
+
files_to_display = sorted_files[:max_display]
|
|
135
|
+
total_files = len(sorted_files)
|
|
136
|
+
|
|
122
137
|
# Display results
|
|
123
|
-
|
|
138
|
+
table_title = "Contract Coverage Analysis"
|
|
139
|
+
if total_files > max_display:
|
|
140
|
+
table_title += f" (showing top {max_display} files needing attention)"
|
|
141
|
+
table = Table(title=table_title)
|
|
124
142
|
table.add_column("File", style="cyan")
|
|
125
143
|
table.add_column("beartype", justify="center")
|
|
126
144
|
table.add_column("icontract", justify="center")
|
|
127
145
|
table.add_column("crosshair", justify="center")
|
|
128
146
|
table.add_column("Coverage", justify="right")
|
|
129
147
|
|
|
130
|
-
for file_path, quality in
|
|
148
|
+
for file_path, quality in files_to_display:
|
|
149
|
+
# Highlight files missing contracts
|
|
150
|
+
file_style = "yellow" if not (quality.beartype and quality.icontract) else "cyan"
|
|
131
151
|
table.add_row(
|
|
132
|
-
file_path,
|
|
133
|
-
"✓" if quality.beartype else "✗",
|
|
134
|
-
"✓" if quality.icontract else "✗",
|
|
135
|
-
"✓" if quality.crosshair else "✗",
|
|
152
|
+
f"[{file_style}]{file_path}[/{file_style}]",
|
|
153
|
+
"✓" if quality.beartype else "[red]✗[/red]",
|
|
154
|
+
"✓" if quality.icontract else "[red]✗[/red]",
|
|
155
|
+
"✓" if quality.crosshair else "[dim]✗[/dim]",
|
|
136
156
|
f"{quality.coverage:.0%}",
|
|
137
157
|
)
|
|
138
158
|
|
|
139
159
|
console.print(table)
|
|
140
160
|
|
|
161
|
+
# Show message if files were filtered
|
|
162
|
+
if total_files > max_display:
|
|
163
|
+
console.print(
|
|
164
|
+
f"\n[yellow]Note:[/yellow] Showing top {max_display} files needing attention "
|
|
165
|
+
f"(out of {total_files} total files analyzed). "
|
|
166
|
+
f"Files missing contracts are prioritized."
|
|
167
|
+
)
|
|
168
|
+
|
|
141
169
|
# Summary
|
|
142
170
|
console.print("\n[bold]Summary:[/bold]")
|
|
143
171
|
console.print(f" Files analyzed: {files_analyzed}")
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
f" Files with
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
f" Files with crosshair: {files_with_crosshair} ({files_with_crosshair / files_analyzed * 100:.0%}%)"
|
|
156
|
-
if files_analyzed > 0
|
|
157
|
-
else " Files with crosshair: 0"
|
|
158
|
-
)
|
|
172
|
+
if files_analyzed > 0:
|
|
173
|
+
beartype_pct = files_with_beartype / files_analyzed
|
|
174
|
+
icontract_pct = files_with_icontract / files_analyzed
|
|
175
|
+
crosshair_pct = files_with_crosshair / files_analyzed
|
|
176
|
+
console.print(f" Files with beartype: {files_with_beartype} ({beartype_pct:.1%})")
|
|
177
|
+
console.print(f" Files with icontract: {files_with_icontract} ({icontract_pct:.1%})")
|
|
178
|
+
console.print(f" Files with crosshair: {files_with_crosshair} ({crosshair_pct:.1%})")
|
|
179
|
+
else:
|
|
180
|
+
console.print(" Files with beartype: 0")
|
|
181
|
+
console.print(" Files with icontract: 0")
|
|
182
|
+
console.print(" Files with crosshair: 0")
|
|
159
183
|
|
|
160
184
|
# Save quality tracking
|
|
161
185
|
quality_file = bundle_dir / "quality-tracking.yaml"
|
|
@@ -179,9 +203,6 @@ def analyze_contracts(
|
|
|
179
203
|
|
|
180
204
|
def _analyze_file_quality(file_path: Path) -> CodeQuality:
|
|
181
205
|
"""Analyze a file for contract coverage."""
|
|
182
|
-
|
|
183
|
-
from specfact_cli.models.quality import CodeQuality
|
|
184
|
-
|
|
185
206
|
try:
|
|
186
207
|
with file_path.open(encoding="utf-8") as f:
|
|
187
208
|
content = f.read()
|
|
@@ -202,114 +223,3 @@ def _analyze_file_quality(file_path: Path) -> CodeQuality:
|
|
|
202
223
|
except Exception:
|
|
203
224
|
# Return default quality if analysis fails
|
|
204
225
|
return CodeQuality()
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
@app.command("enhance")
|
|
208
|
-
@beartype
|
|
209
|
-
@require(lambda file: isinstance(file, Path), "File path must be Path")
|
|
210
|
-
@require(lambda apply: isinstance(apply, str), "Apply must be string")
|
|
211
|
-
@ensure(lambda result: result is None, "Must return None")
|
|
212
|
-
def enhance_contracts(
|
|
213
|
-
# Target/Input
|
|
214
|
-
file: Path = typer.Argument(..., help="Path to file to enhance", exists=True),
|
|
215
|
-
apply: str = typer.Option(
|
|
216
|
-
...,
|
|
217
|
-
"--apply",
|
|
218
|
-
help="Contracts to apply: 'beartype', 'icontract', 'crosshair', or comma-separated list (e.g., 'beartype,icontract')",
|
|
219
|
-
),
|
|
220
|
-
# Output
|
|
221
|
-
output: Path | None = typer.Option(
|
|
222
|
-
None,
|
|
223
|
-
"--output",
|
|
224
|
-
help="Output file path (default: overwrite input file)",
|
|
225
|
-
),
|
|
226
|
-
) -> None:
|
|
227
|
-
"""
|
|
228
|
-
Apply contracts to existing code (LLM-assisted).
|
|
229
|
-
|
|
230
|
-
Prepares LLM prompt context for adding beartype, icontract, or CrossHair
|
|
231
|
-
contracts to existing code files. The CLI orchestrates, LLM writes code.
|
|
232
|
-
|
|
233
|
-
**Parameter Groups:**
|
|
234
|
-
- **Target/Input**: file (required argument), --apply
|
|
235
|
-
- **Output**: --output
|
|
236
|
-
|
|
237
|
-
**Examples:**
|
|
238
|
-
specfact enhance contracts src/auth/login.py --apply beartype,icontract
|
|
239
|
-
specfact enhance contracts src/models/user.py --apply beartype --output src/models/user_enhanced.py
|
|
240
|
-
"""
|
|
241
|
-
|
|
242
|
-
file_path = file.resolve()
|
|
243
|
-
repo_path = file_path.parent.parent # Assume repo root is 2 levels up
|
|
244
|
-
|
|
245
|
-
contracts_to_apply = [c.strip() for c in apply.split(",")]
|
|
246
|
-
valid_contracts = {"beartype", "icontract", "crosshair"}
|
|
247
|
-
invalid_contracts = set(contracts_to_apply) - valid_contracts
|
|
248
|
-
|
|
249
|
-
if invalid_contracts:
|
|
250
|
-
print_error(f"Invalid contract types: {', '.join(invalid_contracts)}")
|
|
251
|
-
print_error(f"Valid types: {', '.join(valid_contracts)}")
|
|
252
|
-
raise typer.Exit(1)
|
|
253
|
-
|
|
254
|
-
telemetry_metadata = {
|
|
255
|
-
"file": str(file_path),
|
|
256
|
-
"contracts": contracts_to_apply,
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
with telemetry.track_command("enhance.contracts", telemetry_metadata) as record:
|
|
260
|
-
console.print(f"[bold cyan]Enhancing contracts for:[/bold cyan] {file_path}")
|
|
261
|
-
console.print(f"[dim]Contracts to apply:[/dim] {', '.join(contracts_to_apply)}\n")
|
|
262
|
-
|
|
263
|
-
# Read file content
|
|
264
|
-
file_content = file_path.read_text(encoding="utf-8")
|
|
265
|
-
|
|
266
|
-
# Generate LLM prompt
|
|
267
|
-
prompt_parts = [
|
|
268
|
-
"# Contract Enhancement Request",
|
|
269
|
-
"",
|
|
270
|
-
f"## File: {file_path}",
|
|
271
|
-
"",
|
|
272
|
-
"## Current Code",
|
|
273
|
-
"```python",
|
|
274
|
-
file_content,
|
|
275
|
-
"```",
|
|
276
|
-
"",
|
|
277
|
-
"## Contracts to Apply",
|
|
278
|
-
]
|
|
279
|
-
|
|
280
|
-
for contract_type in contracts_to_apply:
|
|
281
|
-
if contract_type == "beartype":
|
|
282
|
-
prompt_parts.append("- **beartype**: Add `@beartype` decorator to all functions")
|
|
283
|
-
elif contract_type == "icontract":
|
|
284
|
-
prompt_parts.append(
|
|
285
|
-
"- **icontract**: Add `@require` and `@ensure` decorators with appropriate contracts"
|
|
286
|
-
)
|
|
287
|
-
elif contract_type == "crosshair":
|
|
288
|
-
prompt_parts.append("- **crosshair**: Add property tests using CrossHair")
|
|
289
|
-
|
|
290
|
-
prompt_parts.extend(
|
|
291
|
-
[
|
|
292
|
-
"",
|
|
293
|
-
"## Instructions",
|
|
294
|
-
"Add the requested contracts to the code above.",
|
|
295
|
-
"Maintain existing functionality and code style.",
|
|
296
|
-
"Ensure all contracts are properly imported.",
|
|
297
|
-
"",
|
|
298
|
-
]
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
prompt = "\n".join(prompt_parts)
|
|
302
|
-
|
|
303
|
-
# Save prompt to file
|
|
304
|
-
prompts_dir = repo_path / ".specfact" / "prompts"
|
|
305
|
-
prompts_dir.mkdir(parents=True, exist_ok=True)
|
|
306
|
-
prompt_file = prompts_dir / f"enhance-{file_path.stem}-{'-'.join(contracts_to_apply)}.md"
|
|
307
|
-
prompt_file.write_text(prompt, encoding="utf-8")
|
|
308
|
-
|
|
309
|
-
print_success(f"LLM prompt generated: {prompt_file}")
|
|
310
|
-
console.print("[yellow]Execute this prompt with your LLM to enhance the code[/yellow]")
|
|
311
|
-
|
|
312
|
-
if output:
|
|
313
|
-
console.print(f"[dim]Output will be written to: {output}[/dim]")
|
|
314
|
-
|
|
315
|
-
record({"prompt_generated": True, "prompt_file": str(prompt_file)})
|
|
@@ -318,54 +318,69 @@ def enforce_sdd(
|
|
|
318
318
|
console.print(f"[dim]Frozen sections: {len(sdd_manifest.frozen_sections)}[/dim]")
|
|
319
319
|
# TODO: Implement hash-based frozen section validation in Phase 6
|
|
320
320
|
|
|
321
|
-
# 4. Validate OpenAPI/AsyncAPI
|
|
322
|
-
console.print("\n[cyan]Validating API
|
|
321
|
+
# 4. Validate OpenAPI/AsyncAPI contracts referenced in bundle with Specmatic
|
|
322
|
+
console.print("\n[cyan]Validating API contracts with Specmatic...[/cyan]")
|
|
323
323
|
import asyncio
|
|
324
324
|
|
|
325
325
|
from specfact_cli.integrations.specmatic import check_specmatic_available, validate_spec_with_specmatic
|
|
326
326
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
327
|
+
is_available, error_msg = check_specmatic_available()
|
|
328
|
+
if not is_available:
|
|
329
|
+
console.print(f"[dim]💡 Tip: Install Specmatic to validate API contracts: {error_msg}[/dim]")
|
|
330
|
+
else:
|
|
331
|
+
# Validate contracts referenced in bundle features
|
|
332
|
+
# PlanBundle.features is a list, not a dict
|
|
333
|
+
contract_files = []
|
|
334
|
+
features_iter = (
|
|
335
|
+
plan_bundle.features.values() if isinstance(plan_bundle.features, dict) else plan_bundle.features
|
|
336
|
+
)
|
|
337
|
+
for feature in features_iter:
|
|
338
|
+
if feature.contract:
|
|
339
|
+
contract_path = bundle_dir / feature.contract
|
|
340
|
+
if contract_path.exists():
|
|
341
|
+
contract_files.append((contract_path, feature.key))
|
|
342
|
+
|
|
343
|
+
if contract_files:
|
|
344
|
+
console.print(f"[dim]Found {len(contract_files)} contract(s) referenced in bundle[/dim]")
|
|
345
|
+
for contract_path, feature_key in contract_files[:5]: # Validate up to 5 contracts
|
|
346
|
+
console.print(
|
|
347
|
+
f"[dim]Validating {contract_path.relative_to(bundle_dir)} (from {feature_key})...[/dim]"
|
|
348
|
+
)
|
|
345
349
|
try:
|
|
346
|
-
result = asyncio.run(validate_spec_with_specmatic(
|
|
350
|
+
result = asyncio.run(validate_spec_with_specmatic(contract_path))
|
|
347
351
|
if not result.is_valid:
|
|
348
352
|
deviation = Deviation(
|
|
349
353
|
type=DeviationType.CONTRACT_VIOLATION,
|
|
350
354
|
severity=DeviationSeverity.MEDIUM,
|
|
351
|
-
description=f"API
|
|
352
|
-
location=str(
|
|
353
|
-
fix_hint=f"Run 'specfact spec validate {
|
|
355
|
+
description=f"API contract validation failed: {contract_path.name} (feature: {feature_key})",
|
|
356
|
+
location=str(contract_path),
|
|
357
|
+
fix_hint=f"Run 'specfact spec validate {contract_path}' to see detailed errors",
|
|
354
358
|
)
|
|
355
359
|
report.add_deviation(deviation)
|
|
356
|
-
console.print(
|
|
360
|
+
console.print(
|
|
361
|
+
f" [bold yellow]⚠[/bold yellow] {contract_path.name} has validation issues"
|
|
362
|
+
)
|
|
363
|
+
if result.errors:
|
|
364
|
+
for error in result.errors[:2]:
|
|
365
|
+
console.print(f" - {error}")
|
|
357
366
|
else:
|
|
358
|
-
console.print(f" [bold green]✓[/bold green] {
|
|
367
|
+
console.print(f" [bold green]✓[/bold green] {contract_path.name} is valid")
|
|
359
368
|
except Exception as e:
|
|
360
369
|
console.print(f" [bold yellow]⚠[/bold yellow] Validation error: {e!s}")
|
|
361
|
-
|
|
370
|
+
deviation = Deviation(
|
|
371
|
+
type=DeviationType.CONTRACT_VIOLATION,
|
|
372
|
+
severity=DeviationSeverity.LOW,
|
|
373
|
+
description=f"API contract validation error: {contract_path.name} - {e!s}",
|
|
374
|
+
location=str(contract_path),
|
|
375
|
+
fix_hint=f"Run 'specfact spec validate {contract_path}' to diagnose",
|
|
376
|
+
)
|
|
377
|
+
report.add_deviation(deviation)
|
|
378
|
+
if len(contract_files) > 5:
|
|
362
379
|
console.print(
|
|
363
|
-
f"[dim]... and {len(
|
|
380
|
+
f"[dim]... and {len(contract_files) - 5} more contract(s) (run 'specfact spec validate' to validate all)[/dim]"
|
|
364
381
|
)
|
|
365
382
|
else:
|
|
366
|
-
console.print(
|
|
367
|
-
else:
|
|
368
|
-
console.print("[dim]No API specification files found[/dim]")
|
|
383
|
+
console.print("[dim]No API contracts found in bundle[/dim]")
|
|
369
384
|
|
|
370
385
|
# Generate output report
|
|
371
386
|
output_format_str = output_format.lower()
|