code2llm 0.5.123__tar.gz → 0.5.125__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 (150) hide show
  1. {code2llm-0.5.123 → code2llm-0.5.125}/PKG-INFO +2 -2
  2. {code2llm-0.5.123 → code2llm-0.5.125}/README.md +1 -1
  3. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/__init__.py +1 -1
  4. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/cli_analysis.py +4 -0
  5. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/cli_exports/orchestrator.py +53 -0
  6. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/analyzer.py +10 -0
  7. code2llm-0.5.125/code2llm/exporters/report_generators.py +76 -0
  8. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/generators/llm_flow.py +35 -4
  9. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/generators/llm_task.py +35 -3
  10. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/nlp/__init__.py +1 -1
  11. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm.egg-info/PKG-INFO +2 -2
  12. {code2llm-0.5.123 → code2llm-0.5.125}/pyproject.toml +1 -1
  13. {code2llm-0.5.123 → code2llm-0.5.125}/setup.py +6 -1
  14. code2llm-0.5.123/code2llm/exporters/report_generators.py +0 -34
  15. {code2llm-0.5.123 → code2llm-0.5.125}/LICENSE +0 -0
  16. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/__main__.py +0 -0
  17. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/analysis/__init__.py +0 -0
  18. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/analysis/call_graph.py +0 -0
  19. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/analysis/cfg.py +0 -0
  20. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/analysis/coupling.py +0 -0
  21. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/analysis/data_analysis.py +0 -0
  22. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/analysis/dfg.py +0 -0
  23. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/analysis/pipeline_classifier.py +0 -0
  24. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/analysis/pipeline_detector.py +0 -0
  25. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/analysis/pipeline_resolver.py +0 -0
  26. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/analysis/side_effects.py +0 -0
  27. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/analysis/smells.py +0 -0
  28. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/analysis/type_inference.py +0 -0
  29. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/analysis/utils/__init__.py +0 -0
  30. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/analysis/utils/ast_helpers.py +0 -0
  31. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/api.py +0 -0
  32. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/cli.py +0 -0
  33. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/cli_commands.py +0 -0
  34. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/cli_exports/__init__.py +0 -0
  35. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/cli_exports/code2logic.py +0 -0
  36. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/cli_exports/formats.py +0 -0
  37. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/cli_exports/prompt.py +0 -0
  38. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/cli_parser.py +0 -0
  39. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/__init__.py +0 -0
  40. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/ast_registry.py +0 -0
  41. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/config.py +0 -0
  42. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/export_pipeline.py +0 -0
  43. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/file_analyzer.py +0 -0
  44. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/file_cache.py +0 -0
  45. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/file_filter.py +0 -0
  46. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/gitignore.py +0 -0
  47. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/incremental.py +0 -0
  48. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/lang/__init__.py +0 -0
  49. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/lang/base.py +0 -0
  50. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/lang/cpp.py +0 -0
  51. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/lang/csharp.py +0 -0
  52. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/lang/generic.py +0 -0
  53. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/lang/go_lang.py +0 -0
  54. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/lang/java.py +0 -0
  55. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/lang/php.py +0 -0
  56. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/lang/ruby.py +0 -0
  57. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/lang/rust.py +0 -0
  58. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/lang/ts_extractors.py +0 -0
  59. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/lang/ts_parser.py +0 -0
  60. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/lang/typescript.py +0 -0
  61. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/large_repo.py +0 -0
  62. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/models.py +0 -0
  63. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/persistent_cache.py +0 -0
  64. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/refactoring.py +0 -0
  65. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/repo_files.py +0 -0
  66. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/streaming/__init__.py +0 -0
  67. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/streaming/cache.py +0 -0
  68. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/streaming/incremental.py +0 -0
  69. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/streaming/prioritizer.py +0 -0
  70. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/streaming/scanner.py +0 -0
  71. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/streaming/strategies.py +0 -0
  72. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/streaming_analyzer.py +0 -0
  73. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/core/toon_size_manager.py +0 -0
  74. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/__init__.py +0 -0
  75. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/article_view.py +0 -0
  76. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/base.py +0 -0
  77. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/context_exporter.py +0 -0
  78. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/context_view.py +0 -0
  79. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/dashboard_data.py +0 -0
  80. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/dashboard_renderer.py +0 -0
  81. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/evolution_exporter.py +0 -0
  82. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/flow_constants.py +0 -0
  83. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/flow_exporter.py +0 -0
  84. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/flow_renderer.py +0 -0
  85. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/html_dashboard.py +0 -0
  86. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/index_generator/__init__.py +0 -0
  87. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/index_generator/renderer.py +0 -0
  88. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/index_generator/scanner.py +0 -0
  89. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/index_generator.py +0 -0
  90. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/json_exporter.py +0 -0
  91. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/llm_exporter.py +0 -0
  92. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/map_exporter.py +0 -0
  93. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/mermaid_exporter.py +0 -0
  94. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
  95. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/project_yaml/__init__.py +0 -0
  96. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/project_yaml/constants.py +0 -0
  97. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/project_yaml/core.py +0 -0
  98. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/project_yaml/evolution.py +0 -0
  99. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/project_yaml/health.py +0 -0
  100. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/project_yaml/hotspots.py +0 -0
  101. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/project_yaml/modules.py +0 -0
  102. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/project_yaml_exporter.py +0 -0
  103. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/readme_exporter.py +0 -0
  104. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/toon/__init__.py +0 -0
  105. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/toon/helpers.py +0 -0
  106. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/toon/metrics.py +0 -0
  107. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/toon/metrics_core.py +0 -0
  108. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/toon/metrics_duplicates.py +0 -0
  109. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/toon/metrics_health.py +0 -0
  110. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/toon/module_detail.py +0 -0
  111. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/toon/renderer.py +0 -0
  112. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/toon.py +0 -0
  113. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/toon_view.py +0 -0
  114. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/validate_project.py +0 -0
  115. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/exporters/yaml_exporter.py +0 -0
  116. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/generators/__init__.py +0 -0
  117. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/generators/_utils.py +0 -0
  118. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/generators/mermaid.py +0 -0
  119. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/nlp/config.py +0 -0
  120. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/nlp/entity_resolution.py +0 -0
  121. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/nlp/intent_matching.py +0 -0
  122. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/nlp/normalization.py +0 -0
  123. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/nlp/pipeline.py +0 -0
  124. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/patterns/__init__.py +0 -0
  125. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/patterns/detector.py +0 -0
  126. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/refactor/__init__.py +0 -0
  127. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm/refactor/prompt_engine.py +0 -0
  128. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm.egg-info/SOURCES.txt +0 -0
  129. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm.egg-info/dependency_links.txt +0 -0
  130. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm.egg-info/entry_points.txt +0 -0
  131. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm.egg-info/requires.txt +0 -0
  132. {code2llm-0.5.123 → code2llm-0.5.125}/code2llm.egg-info/top_level.txt +0 -0
  133. {code2llm-0.5.123 → code2llm-0.5.125}/setup.cfg +0 -0
  134. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_advanced_analysis.py +0 -0
  135. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_analyzer.py +0 -0
  136. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_calls_toon_export.py +0 -0
  137. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_deep_analysis.py +0 -0
  138. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_edge_cases.py +0 -0
  139. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_flow_exporter.py +0 -0
  140. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_format_quality.py +0 -0
  141. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_multilanguage_e2e.py +0 -0
  142. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_nlp_pipeline.py +0 -0
  143. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_nonpython_cc_calls.py +0 -0
  144. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_persistent_cache.py +0 -0
  145. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_pipeline_detector.py +0 -0
  146. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_project_toon_export.py +0 -0
  147. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_prompt_engine.py +0 -0
  148. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_prompt_txt.py +0 -0
  149. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_refactoring_engine.py +0 -0
  150. {code2llm-0.5.123 → code2llm-0.5.125}/tests/test_toon_v2.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code2llm
3
- Version: 0.5.123
3
+ Version: 0.5.125
4
4
  Summary: High-performance Python code flow analysis with optimized TOON format - CFG, DFG, call graphs, and intelligent code queries
5
5
  Home-page: https://github.com/wronai/stts
6
6
  Author: STTS Project
@@ -67,7 +67,7 @@ Dynamic: requires-python
67
67
 
68
68
  ## AI Cost Tracking
69
69
 
70
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.123-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
70
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.125-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
71
71
  ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-57.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
72
72
 
73
73
  - 🤖 **LLM usage:** $7.5000 (166 commits)
@@ -3,7 +3,7 @@
3
3
 
4
4
  ## AI Cost Tracking
5
5
 
6
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.123-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
6
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.125-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
7
  ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-57.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
9
  - 🤖 **LLM usage:** $7.5000 (166 commits)
@@ -8,7 +8,7 @@ Includes NLP Processing Pipeline for query normalization, intent matching,
8
8
  and entity resolution with multilingual support.
9
9
  """
10
10
 
11
- __version__ = "0.5.123"
11
+ __version__ = "0.5.125"
12
12
  __author__ = "STTS Project"
13
13
 
14
14
  # Core analysis components (lightweight, always needed)
@@ -84,6 +84,10 @@ def _build_config(args, output_dir: Path):
84
84
  # Persistent cache flags (read via getattr with defaults in analyzer.py)
85
85
  no_cache = getattr(args, 'no_cache', False) or getattr(args, 'force', False)
86
86
  config.no_cache = no_cache
87
+ # Watch mode for auto-detecting changed files
88
+ config.watch = getattr(args, 'watch', False)
89
+ # Dry-run mode (handled in orchestrator, but stored for reference)
90
+ config.dry_run = getattr(args, 'dry_run', False)
87
91
  return config
88
92
 
89
93
 
@@ -42,6 +42,18 @@ FORMAT_FILENAMES: Dict[str, str] = {
42
42
  'project-yaml': 'project.yaml',
43
43
  }
44
44
 
45
+ # Files produced per format in dry-run preview
46
+ FORMAT_DRY_RUN_FILES: Dict[str, List[str]] = {
47
+ 'toon': ['analysis.toon'],
48
+ 'map': ['map.toon.yaml'],
49
+ 'evolution': ['evolution.toon.yaml'],
50
+ 'context': ['context.md'],
51
+ 'mermaid': ['calls.mmd', 'calls.png'],
52
+ 'yaml': ['analysis.yaml'],
53
+ 'json': ['analysis.json'],
54
+ 'readme': ['README.md'],
55
+ }
56
+
45
57
  # Human-readable labels
46
58
  FORMAT_LABELS: Dict[str, str] = {
47
59
  'toon': 'TOON (diagnostics)',
@@ -69,6 +81,41 @@ def _build_export_config(args, formats: List[str]) -> Dict[str, Any]:
69
81
  }
70
82
 
71
83
 
84
+ def _collect_dry_run_files(formats: List[str], output_dir: Path) -> List[Path]:
85
+ """Return the list of files that would be written for the given formats."""
86
+ output_files: List[Path] = []
87
+ for fmt in formats:
88
+ for name in FORMAT_DRY_RUN_FILES.get(fmt, []):
89
+ output_files.append(output_dir / name)
90
+ output_files.append(output_dir / 'project.toon.yaml')
91
+ output_files.append(output_dir / 'prompt.txt')
92
+ return output_files
93
+
94
+
95
+ def _show_dry_run_plan(formats: List[str], output_dir: Path, is_chunked: bool, result) -> None:
96
+ """Display what would be exported in dry-run mode."""
97
+ print("\n📋 DRY-RUN: Would export the following:\n")
98
+
99
+ output_files = _collect_dry_run_files(formats, output_dir)
100
+
101
+ size_hint = ""
102
+ func_count = len(getattr(result, 'functions', []))
103
+ if func_count > 0:
104
+ size_hint = f" (~{func_count * 50 // 1024}KB est.)"
105
+
106
+ for f in sorted(set(output_files)):
107
+ print(f" 📄 {f}{size_hint}")
108
+
109
+ stats = getattr(result, 'stats', {})
110
+ if stats:
111
+ print(f"\n📊 Based on analysis:")
112
+ print(f" - Functions: {stats.get('functions_found', 'N/A')}")
113
+ print(f" - Classes: {stats.get('classes_found', 'N/A')}")
114
+ print(f" - Files: {stats.get('files_processed', 'N/A')}")
115
+
116
+ print(f"\n✅ Dry-run complete. Use without --dry-run to export.\n")
117
+
118
+
72
119
  def _run_exports(args, result, output_dir: Path, source_path: Optional[Path] = None):
73
120
  """Export analysis results in requested formats.
74
121
 
@@ -79,6 +126,12 @@ def _run_exports(args, result, output_dir: Path, source_path: Optional[Path] = N
79
126
  requested_formats = [f.strip() for f in args.format.split(',')]
80
127
  formats = _expand_all_formats(requested_formats, getattr(args, 'png', False))
81
128
  is_chunked = getattr(args, 'chunk', False)
129
+ dry_run = getattr(args, 'dry_run', False)
130
+
131
+ # Dry-run: show what would be exported without writing
132
+ if dry_run:
133
+ _show_dry_run_plan(formats, output_dir, is_chunked, result)
134
+ return
82
135
 
83
136
  # Skip cache for chunked or when explicitly disabled
84
137
  skip_cache = is_chunked or getattr(args, 'no_cache', False)
@@ -52,6 +52,16 @@ class ProjectAnalyzer:
52
52
  print(f" - Parallel: {self.config.performance.parallel_enabled}, Workers: {workers}")
53
53
 
54
54
  pcache, cached_results, files_to_analyze = self._load_from_persistent_cache(files, project_path)
55
+
56
+ # Watch mode: show what changed
57
+ if getattr(self.config, 'watch', False) and files_to_analyze:
58
+ print(f"\n👁️ Watch mode: {len(files_to_analyze)} files changed since last run:")
59
+ for fp, _ in files_to_analyze[:10]:
60
+ print(f" • {Path(fp).name}")
61
+ if len(files_to_analyze) > 10:
62
+ print(f" ... and {len(files_to_analyze) - 10} more")
63
+ print()
64
+
55
65
  fresh_results = self._run_analysis(files_to_analyze)
56
66
  self._store_to_persistent_cache(pcache, files_to_analyze, fresh_results)
57
67
 
@@ -0,0 +1,76 @@
1
+ """Report generators — produce views from project.yaml (single source of truth).
2
+
3
+ Thin re-export module. Actual generators live in separate files:
4
+ toon_view.py → ToonViewGenerator → project.toon.yaml
5
+ context_view.py → ContextViewGenerator → context.md
6
+ article_view.py → ArticleViewGenerator → status.md
7
+ html_dashboard.py → HTMLDashboardGenerator → dashboard.html
8
+ """
9
+
10
+ import yaml
11
+ from typing import Any, Dict
12
+
13
+ from .toon_view import ToonViewGenerator
14
+ from .context_view import ContextViewGenerator
15
+ from .article_view import ArticleViewGenerator
16
+ from .html_dashboard import HTMLDashboardGenerator
17
+
18
+
19
+ def load_project_yaml(path: str) -> Dict[str, Any]:
20
+ """Load and validate project.yaml with detailed error reporting."""
21
+ import yaml
22
+ from yaml.scanner import ScannerError
23
+ from yaml.parser import ParserError
24
+
25
+ try:
26
+ with open(path, "r", encoding="utf-8") as f:
27
+ content = f.read()
28
+ except FileNotFoundError:
29
+ raise ValueError(f"project.yaml not found: {path}")
30
+ except Exception as e:
31
+ raise ValueError(f"Cannot read project.yaml ({path}): {e}")
32
+
33
+ # Check for empty file
34
+ if not content.strip():
35
+ raise ValueError(f"project.yaml is empty: {path}")
36
+
37
+ # Try to parse YAML with detailed error reporting
38
+ try:
39
+ data = yaml.safe_load(content)
40
+ except ScannerError as e:
41
+ line = e.problem_mark.line + 1 if e.problem_mark else "?"
42
+ col = e.problem_mark.column if e.problem_mark else "?"
43
+ raise ValueError(
44
+ f"YAML syntax error in {path} at line {line}, column {col}: {e.problem}\n"
45
+ f"Hint: Check indentation and special characters (:, -, #)")
46
+ except ParserError as e:
47
+ line = e.problem_mark.line + 1 if e.problem_mark else "?"
48
+ raise ValueError(
49
+ f"YAML parse error in {path} at line {line}: {e.problem}\n"
50
+ f"Hint: Verify YAML structure (mapping vs list)")
51
+ except Exception as e:
52
+ raise ValueError(f"YAML error in {path}: {e}")
53
+
54
+ # Validate structure
55
+ if data is None:
56
+ raise ValueError(f"project.yaml is null/empty: {path}")
57
+ if not isinstance(data, dict):
58
+ raise ValueError(
59
+ f"Invalid project.yaml: expected dict/object, got {type(data).__name__} in {path}\n"
60
+ f"Hint: YAML must start with key-value pairs, not a list")
61
+ if "version" not in data:
62
+ raise ValueError(
63
+ f"Invalid project.yaml: missing required 'version' key in {path}\n"
64
+ f"Required keys: version, project, analysis\n"
65
+ f"Found keys: {list(data.keys())[:10]}")
66
+
67
+ return data
68
+
69
+
70
+ __all__ = [
71
+ "load_project_yaml",
72
+ "ToonViewGenerator",
73
+ "ContextViewGenerator",
74
+ "ArticleViewGenerator",
75
+ "HTMLDashboardGenerator",
76
+ ]
@@ -21,11 +21,42 @@ def _strip_bom(text: str) -> str:
21
21
 
22
22
 
23
23
  def _safe_read_yaml(path: Path) -> Dict[str, Any]:
24
- """Read YAML file safely, handling BOM and type validation."""
25
- raw = _strip_bom(path.read_text(encoding="utf-8"))
26
- loaded = yaml.safe_load(raw) or {}
24
+ """Read YAML file safely, handling BOM and type validation with detailed errors."""
25
+ from yaml.scanner import ScannerError
26
+ from yaml.parser import ParserError
27
+
28
+ try:
29
+ raw = _strip_bom(path.read_text(encoding="utf-8"))
30
+ except FileNotFoundError:
31
+ raise ValueError(f"File not found: {path}")
32
+ except Exception as e:
33
+ raise ValueError(f"Cannot read file {path}: {e}")
34
+
35
+ if not raw.strip():
36
+ raise ValueError(f"File is empty: {path}")
37
+
38
+ try:
39
+ loaded = yaml.safe_load(raw)
40
+ except ScannerError as e:
41
+ line = e.problem_mark.line + 1 if e.problem_mark else "?"
42
+ col = e.problem_mark.column if e.problem_mark else "?"
43
+ raise ValueError(
44
+ f"YAML syntax error in {path} at line {line}, column {col}: {e.problem}\n"
45
+ f"Hint: Check indentation, avoid tabs, watch special characters (:, -, #)")
46
+ except ParserError as e:
47
+ line = e.problem_mark.line + 1 if e.problem_mark else "?"
48
+ raise ValueError(
49
+ f"YAML parse error in {path} at line {line}: {e.problem}\n"
50
+ f"Hint: Verify structure - are you using list where dict expected?")
51
+ except Exception as e:
52
+ raise ValueError(f"YAML error in {path}: {e}")
53
+
54
+ if loaded is None:
55
+ raise ValueError(f"File is null/empty YAML: {path}")
27
56
  if not isinstance(loaded, dict):
28
- raise ValueError("analysis.yaml must be a mapping at top-level")
57
+ raise ValueError(
58
+ f"Expected YAML mapping (dict), got {type(loaded).__name__} in {path}\n"
59
+ f"Hint: File must start with 'key: value' pairs, not a list")
29
60
  return loaded
30
61
 
31
62
 
@@ -212,13 +212,45 @@ def parse_llm_task_text(text: str) -> Dict[str, Any]:
212
212
 
213
213
 
214
214
  def load_input(path: Path) -> Dict[str, Any]:
215
- raw = path.read_text(encoding="utf-8")
215
+ """Load input file with detailed YAML/JSON error reporting."""
216
+ from yaml.scanner import ScannerError
217
+ from yaml.parser import ParserError
218
+
219
+ try:
220
+ raw = path.read_text(encoding="utf-8")
221
+ except FileNotFoundError:
222
+ raise ValueError(f"Input file not found: {path}")
223
+ except Exception as e:
224
+ raise ValueError(f"Cannot read input file {path}: {e}")
225
+
216
226
  raw = _strip_bom(raw)
217
227
 
228
+ if not raw.strip():
229
+ raise ValueError(f"Input file is empty: {path}")
230
+
218
231
  if path.suffix.lower() in {".yaml", ".yml"}:
219
- loaded = yaml.safe_load(raw) or {}
232
+ try:
233
+ loaded = yaml.safe_load(raw)
234
+ except ScannerError as e:
235
+ line = e.problem_mark.line + 1 if e.problem_mark else "?"
236
+ col = e.problem_mark.column if e.problem_mark else "?"
237
+ raise ValueError(
238
+ f"YAML syntax error at line {line}, column {col}: {e.problem}\n"
239
+ f"Hint: Check indentation in {path}")
240
+ except ParserError as e:
241
+ line = e.problem_mark.line + 1 if e.problem_mark else "?"
242
+ raise ValueError(
243
+ f"YAML parse error at line {line}: {e.problem}\n"
244
+ f"Hint: Verify YAML structure in {path}")
245
+ except Exception as e:
246
+ raise ValueError(f"YAML error in {path}: {e}")
247
+
248
+ if loaded is None:
249
+ raise ValueError(f"YAML file is null/empty: {path}")
220
250
  if not isinstance(loaded, dict):
221
- raise ValueError("YAML input must be a mapping/object at top level")
251
+ raise ValueError(
252
+ f"YAML must be a mapping/object, got {type(loaded).__name__} in {path}\n"
253
+ f"Hint: File should start with 'key: value' pairs")
222
254
  return loaded
223
255
 
224
256
  if path.suffix.lower() == ".json":
@@ -4,7 +4,7 @@ Provides query normalization, intent matching, and entity resolution
4
4
  with multilingual support and fuzzy matching.
5
5
  """
6
6
 
7
- __version__ = "0.5.123"
7
+ __version__ = "0.5.125"
8
8
 
9
9
  from .pipeline import NLPPipeline
10
10
  from .normalization import QueryNormalizer
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code2llm
3
- Version: 0.5.123
3
+ Version: 0.5.125
4
4
  Summary: High-performance Python code flow analysis with optimized TOON format - CFG, DFG, call graphs, and intelligent code queries
5
5
  Home-page: https://github.com/wronai/stts
6
6
  Author: STTS Project
@@ -67,7 +67,7 @@ Dynamic: requires-python
67
67
 
68
68
  ## AI Cost Tracking
69
69
 
70
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.123-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
70
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.5.125-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
71
71
  ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-57.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
72
72
 
73
73
  - 🤖 **LLM usage:** $7.5000 (166 commits)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code2llm"
7
- version = "0.5.123"
7
+ version = "0.5.125"
8
8
  description = "High-performance Python code flow analysis with optimized TOON format - CFG, DFG, call graphs, and intelligent code queries"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -4,7 +4,12 @@ from setuptools import setup, find_packages
4
4
  import os
5
5
 
6
6
  # Read version
7
- version = "0.5.122"
7
+ def read_version():
8
+ version_path = os.path.join(os.path.dirname(__file__), 'VERSION')
9
+ with open(version_path, 'r') as f:
10
+ return f.read().strip()
11
+
12
+ version = read_version()
8
13
 
9
14
  # Read long description
10
15
  def read_readme():
@@ -1,34 +0,0 @@
1
- """Report generators — produce views from project.yaml (single source of truth).
2
-
3
- Thin re-export module. Actual generators live in separate files:
4
- toon_view.py → ToonViewGenerator → project.toon.yaml
5
- context_view.py → ContextViewGenerator → context.md
6
- article_view.py → ArticleViewGenerator → status.md
7
- html_dashboard.py → HTMLDashboardGenerator → dashboard.html
8
- """
9
-
10
- import yaml
11
- from typing import Any, Dict
12
-
13
- from .toon_view import ToonViewGenerator
14
- from .context_view import ContextViewGenerator
15
- from .article_view import ArticleViewGenerator
16
- from .html_dashboard import HTMLDashboardGenerator
17
-
18
-
19
- def load_project_yaml(path: str) -> Dict[str, Any]:
20
- """Load and validate project.yaml."""
21
- with open(path, "r", encoding="utf-8") as f:
22
- data = yaml.safe_load(f)
23
- if not isinstance(data, dict) or "version" not in data:
24
- raise ValueError(f"Invalid project.yaml: missing 'version' key in {path}")
25
- return data
26
-
27
-
28
- __all__ = [
29
- "load_project_yaml",
30
- "ToonViewGenerator",
31
- "ContextViewGenerator",
32
- "ArticleViewGenerator",
33
- "HTMLDashboardGenerator",
34
- ]
File without changes
File without changes
File without changes
File without changes