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.
Files changed (144) hide show
  1. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/PKG-INFO +49 -37
  2. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/README.md +48 -36
  3. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/pyproject.toml +1 -1
  4. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/__init__.py +1 -1
  5. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/analyze.py +53 -143
  6. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/enforce.py +46 -31
  7. specfact_cli-0.13.0/src/specfact_cli/commands/generate.py +1391 -0
  8. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/import_cmd.py +86 -5
  9. specfact_cli-0.13.0/src/specfact_cli/commands/spec.py +861 -0
  10. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/sync.py +69 -0
  11. specfact_cli-0.12.1/src/specfact_cli/commands/generate.py +0 -515
  12. specfact_cli-0.12.1/src/specfact_cli/commands/spec.py +0 -407
  13. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/.gitignore +0 -0
  14. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/LICENSE.md +0 -0
  15. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/mappings/node-async.yaml +0 -0
  16. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/mappings/python-async.yaml +0 -0
  17. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/mappings/speckit-default.yaml +0 -0
  18. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/shared/cli-enforcement.md +0 -0
  19. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/specfact.01-import.md +0 -0
  20. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/specfact.02-plan.md +0 -0
  21. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/specfact.03-review.md +0 -0
  22. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/specfact.04-sdd.md +0 -0
  23. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/specfact.05-enforce.md +0 -0
  24. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/specfact.06-sync.md +0 -0
  25. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/specfact.compare.md +0 -0
  26. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/prompts/specfact.validate.md +0 -0
  27. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/schemas/deviation.schema.json +0 -0
  28. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/schemas/plan.schema.json +0 -0
  29. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/schemas/protocol.schema.json +0 -0
  30. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/templates/github-action.yml.j2 +0 -0
  31. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/templates/plan.bundle.yaml.j2 +0 -0
  32. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/templates/pr-template.md.j2 +0 -0
  33. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/templates/protocol.yaml.j2 +0 -0
  34. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/resources/templates/telemetry.yaml.example +0 -0
  35. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/__init__.py +0 -0
  36. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/agents/__init__.py +0 -0
  37. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/agents/analyze_agent.py +0 -0
  38. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/agents/base.py +0 -0
  39. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/agents/plan_agent.py +0 -0
  40. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/agents/registry.py +0 -0
  41. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/agents/sync_agent.py +0 -0
  42. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/__init__.py +0 -0
  43. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
  44. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
  45. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
  46. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
  47. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
  48. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
  49. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
  50. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
  51. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
  52. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/cli.py +0 -0
  53. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/__init__.py +0 -0
  54. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/bridge.py +0 -0
  55. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/drift.py +0 -0
  56. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/implement.py +0 -0
  57. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/init.py +0 -0
  58. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/migrate.py +0 -0
  59. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/plan.py +0 -0
  60. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/repro.py +0 -0
  61. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/run.py +0 -0
  62. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/commands/sdd.py +0 -0
  63. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/common/__init__.py +0 -0
  64. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/common/logger_setup.py +0 -0
  65. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/common/logging_utils.py +0 -0
  66. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/common/text_utils.py +0 -0
  67. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/common/utils.py +0 -0
  68. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/comparators/__init__.py +0 -0
  69. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/comparators/plan_comparator.py +0 -0
  70. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
  71. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
  72. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/__init__.py +0 -0
  73. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/contract_generator.py +0 -0
  74. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/openapi_extractor.py +0 -0
  75. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/plan_generator.py +0 -0
  76. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/protocol_generator.py +0 -0
  77. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/report_generator.py +0 -0
  78. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/task_generator.py +0 -0
  79. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/test_to_openapi.py +0 -0
  80. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/generators/workflow_generator.py +0 -0
  81. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/importers/__init__.py +0 -0
  82. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/importers/speckit_converter.py +0 -0
  83. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/importers/speckit_scanner.py +0 -0
  84. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/integrations/__init__.py +0 -0
  85. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/integrations/specmatic.py +0 -0
  86. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/migrations/__init__.py +0 -0
  87. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/migrations/plan_migrator.py +0 -0
  88. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/__init__.py +0 -0
  89. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/bridge.py +0 -0
  90. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/deviation.py +0 -0
  91. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/enforcement.py +0 -0
  92. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/plan.py +0 -0
  93. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/project.py +0 -0
  94. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/protocol.py +0 -0
  95. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/quality.py +0 -0
  96. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/sdd.py +0 -0
  97. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/source_tracking.py +0 -0
  98. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/models/task.py +0 -0
  99. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/modes/__init__.py +0 -0
  100. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/modes/detector.py +0 -0
  101. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/modes/router.py +0 -0
  102. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/resources/semgrep/async.yml +0 -0
  103. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
  104. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
  105. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/runtime.py +0 -0
  106. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/__init__.py +0 -0
  107. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/bridge_probe.py +0 -0
  108. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/bridge_sync.py +0 -0
  109. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/bridge_watch.py +0 -0
  110. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/change_detector.py +0 -0
  111. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/code_to_spec.py +0 -0
  112. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/drift_detector.py +0 -0
  113. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/repository_sync.py +0 -0
  114. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/spec_to_code.py +0 -0
  115. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/spec_to_tests.py +0 -0
  116. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/speckit_sync.py +0 -0
  117. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/sync/watcher.py +0 -0
  118. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/telemetry.py +0 -0
  119. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/templates/__init__.py +0 -0
  120. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/templates/bridge_templates.py +0 -0
  121. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/__init__.py +0 -0
  122. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
  123. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/bundle_loader.py +0 -0
  124. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/console.py +0 -0
  125. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/enrichment_context.py +0 -0
  126. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/enrichment_parser.py +0 -0
  127. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/feature_keys.py +0 -0
  128. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/git.py +0 -0
  129. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/github_annotations.py +0 -0
  130. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/ide_setup.py +0 -0
  131. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/incremental_check.py +0 -0
  132. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/optional_deps.py +0 -0
  133. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/progress.py +0 -0
  134. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/prompts.py +0 -0
  135. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/sdd_discovery.py +0 -0
  136. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/source_scanner.py +0 -0
  137. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/structure.py +0 -0
  138. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/structured_io.py +0 -0
  139. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/utils/yaml_utils.py +0 -0
  140. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/validators/__init__.py +0 -0
  141. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/validators/contract_validator.py +0 -0
  142. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/validators/fsm.py +0 -0
  143. {specfact_cli-0.12.1 → specfact_cli-0.13.0}/src/specfact_cli/validators/repro_checker.py +0 -0
  144. {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.12.1
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
- > **Understand and Modernize Legacy Code with Confidence**
279
- > Automatically extract specs from existing Python code, then enforce them as contracts
278
+ > **Stop vibe coding. Start shipping quality code with contracts.**
279
+ > Brownfield-first CLI: Reverse engineer legacy Python specs enforced contracts
280
280
 
281
+ [![PyPI version](https://img.shields.io/pypi/v/specfact-cli.svg)](https://pypi.org/project/specfact-cli/)
282
+ [![Python versions](https://img.shields.io/pypi/pyversions/specfact-cli.svg)](https://pypi.org/project/specfact-cli/)
281
283
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE.md)
282
- [![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
283
284
  [![Status](https://img.shields.io/badge/status-beta-orange.svg)](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 guessing what your legacy code does.** SpecFact automatically extracts specs from existing code, then enforces them as you modernize.
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
- > **Built with ❤️ by [NOLD AI](https://noldai.com)**
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
- > **Understand and Modernize Legacy Code with Confidence**
4
- > Automatically extract specs from existing Python code, then enforce them as contracts
3
+ > **Stop vibe coding. Start shipping quality code with contracts.**
4
+ > Brownfield-first CLI: Reverse engineer legacy Python specs enforced contracts
5
5
 
6
+ [![PyPI version](https://img.shields.io/pypi/v/specfact-cli.svg)](https://pypi.org/project/specfact-cli/)
7
+ [![Python versions](https://img.shields.io/pypi/pyversions/specfact-cli.svg)](https://pypi.org/project/specfact-cli/)
6
8
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE.md)
7
- [![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
8
9
  [![Status](https://img.shields.io/badge/status-beta-orange.svg)](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 guessing what your legacy code does.** SpecFact automatically extracts specs from existing code, then enforces them as you modernize.
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
- > **Built with ❤️ by [NOLD AI](https://noldai.com)**
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.12.1"
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"
@@ -9,6 +9,6 @@ This package provides command-line tools for:
9
9
  - Validating reproducibility
10
10
  """
11
11
 
12
- __version__ = "0.12.1"
12
+ __version__ = "0.13.0"
13
13
 
14
14
  __all__ = ["__version__"]
@@ -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(lambda bundle: isinstance(bundle, str) and len(bundle) > 0, "Bundle name must be non-empty string")
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
- table = Table(title="Contract Coverage Analysis")
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 list(quality_tracking.code_quality.items())[:20]: # Show first 20
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
- console.print(
145
- f" Files with beartype: {files_with_beartype} ({files_with_beartype / files_analyzed * 100:.0%}%)"
146
- if files_analyzed > 0
147
- else " Files with beartype: 0"
148
- )
149
- console.print(
150
- f" Files with icontract: {files_with_icontract} ({files_with_icontract / files_analyzed * 100:.0%}%)"
151
- if files_analyzed > 0
152
- else " Files with icontract: 0"
153
- )
154
- console.print(
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 specs with Specmatic (if found)
322
- console.print("\n[cyan]Validating API specifications...[/cyan]")
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
- base_path = Path(".")
328
- spec_files = []
329
- for pattern in [
330
- "**/openapi.yaml",
331
- "**/openapi.yml",
332
- "**/openapi.json",
333
- "**/asyncapi.yaml",
334
- "**/asyncapi.yml",
335
- "**/asyncapi.json",
336
- ]:
337
- spec_files.extend(base_path.glob(pattern))
338
-
339
- if spec_files:
340
- console.print(f"[dim]Found {len(spec_files)} API specification file(s)[/dim]")
341
- is_available, error_msg = check_specmatic_available()
342
- if is_available:
343
- for spec_file in spec_files[:5]: # Validate up to 5 specs
344
- console.print(f"[dim]Validating {spec_file.relative_to(base_path)} with Specmatic...[/dim]")
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(spec_file))
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 specification validation failed: {spec_file.name}",
352
- location=str(spec_file),
353
- fix_hint=f"Run 'specfact spec validate {spec_file}' to see detailed errors",
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(f" [bold yellow]⚠[/bold yellow] {spec_file.name} has validation issues")
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] {spec_file.name} is valid")
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
- if len(spec_files) > 5:
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(spec_files) - 5} more spec file(s) (run 'specfact spec validate' to validate all)[/dim]"
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(f"[dim]💡 Tip: Install Specmatic to validate API specs: {error_msg}[/dim]")
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()