code2llm 0.3.7__py3-none-any.whl

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 (49) hide show
  1. code2flow/__init__.py +47 -0
  2. code2flow/__main__.py +6 -0
  3. code2flow/analysis/__init__.py +23 -0
  4. code2flow/analysis/call_graph.py +210 -0
  5. code2flow/analysis/cfg.py +293 -0
  6. code2flow/analysis/coupling.py +77 -0
  7. code2flow/analysis/data_analysis.py +249 -0
  8. code2flow/analysis/dfg.py +224 -0
  9. code2flow/analysis/pipeline_detector.py +445 -0
  10. code2flow/analysis/side_effects.py +313 -0
  11. code2flow/analysis/smells.py +192 -0
  12. code2flow/analysis/type_inference.py +306 -0
  13. code2flow/cli.py +493 -0
  14. code2flow/core/__init__.py +36 -0
  15. code2flow/core/analyzer.py +765 -0
  16. code2flow/core/config.py +177 -0
  17. code2flow/core/models.py +194 -0
  18. code2flow/core/streaming_analyzer.py +666 -0
  19. code2flow/exporters/__init__.py +35 -0
  20. code2flow/exporters/base.py +13 -0
  21. code2flow/exporters/context_exporter.py +207 -0
  22. code2flow/exporters/flow_exporter.py +570 -0
  23. code2flow/exporters/json_exporter.py +17 -0
  24. code2flow/exporters/llm_exporter.py +12 -0
  25. code2flow/exporters/map_exporter.py +218 -0
  26. code2flow/exporters/mermaid_exporter.py +67 -0
  27. code2flow/exporters/toon.py +982 -0
  28. code2flow/exporters/yaml_exporter.py +108 -0
  29. code2flow/llm_flow_generator.py +451 -0
  30. code2flow/llm_task_generator.py +263 -0
  31. code2flow/mermaid_generator.py +481 -0
  32. code2flow/nlp/__init__.py +23 -0
  33. code2flow/nlp/config.py +174 -0
  34. code2flow/nlp/entity_resolution.py +326 -0
  35. code2flow/nlp/intent_matching.py +297 -0
  36. code2flow/nlp/normalization.py +122 -0
  37. code2flow/nlp/pipeline.py +388 -0
  38. code2flow/patterns/__init__.py +0 -0
  39. code2flow/patterns/detector.py +168 -0
  40. code2flow/refactor/__init__.py +0 -0
  41. code2flow/refactor/prompt_engine.py +150 -0
  42. code2flow/visualizers/__init__.py +0 -0
  43. code2flow/visualizers/graph.py +196 -0
  44. code2llm-0.3.7.dist-info/METADATA +604 -0
  45. code2llm-0.3.7.dist-info/RECORD +49 -0
  46. code2llm-0.3.7.dist-info/WHEEL +5 -0
  47. code2llm-0.3.7.dist-info/entry_points.txt +2 -0
  48. code2llm-0.3.7.dist-info/licenses/LICENSE +201 -0
  49. code2llm-0.3.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,263 @@
1
+ import argparse
2
+ import sys
3
+ from pathlib import Path
4
+ from typing import Any, Dict, List, Optional, Tuple
5
+
6
+ import yaml
7
+
8
+
9
+ def _strip_bom(text: str) -> str:
10
+ return text[1:] if text.startswith("\ufeff") else text
11
+
12
+
13
+ def _ensure_list(value: Any) -> List[Any]:
14
+ if value is None:
15
+ return []
16
+ if isinstance(value, list):
17
+ return value
18
+ return [value]
19
+
20
+
21
+ def _deep_get(d: Dict[str, Any], path: Tuple[str, ...]) -> Any:
22
+ cur: Any = d
23
+ for key in path:
24
+ if not isinstance(cur, dict) or key not in cur:
25
+ return None
26
+ cur = cur[key]
27
+ return cur
28
+
29
+
30
+ def normalize_llm_task(data: Dict[str, Any]) -> Dict[str, Any]:
31
+ task = data.get("task") or {}
32
+ context = data.get("context") or {}
33
+ deliverables = data.get("deliverables") or {}
34
+ interfaces = data.get("interfaces") or {}
35
+ rules = data.get("rules") or {}
36
+ acceptance = data.get("acceptance") or {}
37
+ examples = data.get("examples")
38
+ notes = data.get("notes_for_llm") or {}
39
+
40
+ normalized: Dict[str, Any] = {
41
+ "task": {
42
+ "title": task.get("title") or "",
43
+ "one_line_goal": task.get("one_line_goal") or "",
44
+ },
45
+ "context": {
46
+ "product_area": context.get("product_area") or "",
47
+ "current_behavior": context.get("current_behavior") or "",
48
+ "desired_behavior": context.get("desired_behavior") or "",
49
+ },
50
+ "deliverables": {
51
+ "language": deliverables.get("language") or "any",
52
+ "must_generate": _ensure_list(deliverables.get("must_generate")),
53
+ "files_to_create_or_edit": _ensure_list(deliverables.get("files_to_create_or_edit")),
54
+ },
55
+ "interfaces": {
56
+ "inputs": _ensure_list(interfaces.get("inputs")),
57
+ "outputs": _ensure_list(interfaces.get("outputs")),
58
+ },
59
+ "rules": {
60
+ "must": _ensure_list(rules.get("must")),
61
+ "must_not": _ensure_list(rules.get("must_not")),
62
+ "assumptions": _ensure_list(rules.get("assumptions")),
63
+ "edge_cases": _ensure_list(rules.get("edge_cases")),
64
+ "performance": _ensure_list(rules.get("performance")),
65
+ },
66
+ "acceptance": {
67
+ "tests": _ensure_list(acceptance.get("tests")),
68
+ "done_definition": _ensure_list(acceptance.get("done_definition")),
69
+ },
70
+ "examples": _ensure_list(examples),
71
+ "notes_for_llm": {
72
+ "constraints": _ensure_list(notes.get("constraints")),
73
+ "style": _ensure_list(notes.get("style")),
74
+ "language_specific_hints": _ensure_list(notes.get("language_specific_hints")),
75
+ },
76
+ }
77
+
78
+ return normalized
79
+
80
+
81
+ _SECTION_KEYS = {
82
+ "TITLE": ("task", "title"),
83
+ "GOAL": ("task", "one_line_goal"),
84
+ "PRODUCT_AREA": ("context", "product_area"),
85
+ "CURRENT": ("context", "current_behavior"),
86
+ "DESIRED": ("context", "desired_behavior"),
87
+ }
88
+
89
+
90
+ def _parse_bullets(lines: List[str]) -> List[str]:
91
+ items: List[str] = []
92
+ for raw in lines:
93
+ s = raw.strip()
94
+ if not s:
95
+ continue
96
+ if s.startswith("-"):
97
+ items.append(s[1:].strip())
98
+ else:
99
+ items.append(s)
100
+ return items
101
+
102
+
103
+ def parse_llm_task_text(text: str) -> Dict[str, Any]:
104
+ text = _strip_bom(text)
105
+ lines = text.replace("\r\n", "\n").replace("\r", "\n").split("\n")
106
+
107
+ sections: Dict[str, List[str]] = {}
108
+ current: Optional[str] = None
109
+
110
+ def start_section(name: str) -> None:
111
+ nonlocal current
112
+ current = name
113
+ sections.setdefault(name, [])
114
+
115
+ for line in lines:
116
+ stripped = line.strip()
117
+ if not stripped:
118
+ if current is not None:
119
+ sections[current].append("")
120
+ continue
121
+
122
+ upper = stripped.upper()
123
+ if upper.endswith(":"):
124
+ key = upper[:-1].strip()
125
+ if key in {
126
+ "TITLE",
127
+ "GOAL",
128
+ "CURRENT",
129
+ "DESIRED",
130
+ "INPUTS",
131
+ "OUTPUTS",
132
+ "RULES (MUST)",
133
+ "RULES (MUST NOT)",
134
+ "EDGE CASES",
135
+ "ACCEPTANCE TESTS",
136
+ "DELIVERABLES",
137
+ }:
138
+ start_section(key)
139
+ continue
140
+
141
+ if current is None:
142
+ continue
143
+ sections[current].append(line)
144
+
145
+ data: Dict[str, Any] = {
146
+ "task": {"title": "", "one_line_goal": ""},
147
+ "context": {"product_area": "", "current_behavior": "", "desired_behavior": ""},
148
+ "deliverables": {"language": "any", "must_generate": [], "files_to_create_or_edit": []},
149
+ "interfaces": {"inputs": [], "outputs": []},
150
+ "rules": {"must": [], "must_not": [], "assumptions": [], "edge_cases": [], "performance": []},
151
+ "acceptance": {"tests": [], "done_definition": []},
152
+ "examples": [],
153
+ "notes_for_llm": {"constraints": [], "style": [], "language_specific_hints": []},
154
+ }
155
+
156
+ for section_name, path in _SECTION_KEYS.items():
157
+ content_lines = sections.get(section_name)
158
+ if not content_lines:
159
+ continue
160
+ value = "\n".join(content_lines).strip()
161
+ if value:
162
+ parent = data
163
+ for key in path[:-1]:
164
+ parent = parent[key]
165
+ parent[path[-1]] = value
166
+
167
+ if sections.get("INPUTS"):
168
+ data["interfaces"]["inputs"] = _parse_bullets(sections["INPUTS"])
169
+ if sections.get("OUTPUTS"):
170
+ data["interfaces"]["outputs"] = _parse_bullets(sections["OUTPUTS"])
171
+ if sections.get("RULES (MUST)"):
172
+ data["rules"]["must"] = _parse_bullets(sections["RULES (MUST)"])
173
+ if sections.get("RULES (MUST NOT)"):
174
+ data["rules"]["must_not"] = _parse_bullets(sections["RULES (MUST NOT)"])
175
+ if sections.get("EDGE CASES"):
176
+ data["rules"]["edge_cases"] = _parse_bullets(sections["EDGE CASES"])
177
+
178
+ if sections.get("ACCEPTANCE TESTS"):
179
+ tests: List[Dict[str, str]] = []
180
+ raw_items = _parse_bullets(sections["ACCEPTANCE TESTS"])
181
+ for idx, item in enumerate(raw_items, 1):
182
+ tests.append({"name": f"test_{idx}", "given": "", "when": "", "then": item})
183
+ data["acceptance"]["tests"] = tests
184
+
185
+ if sections.get("DELIVERABLES"):
186
+ data["deliverables"]["must_generate"] = _parse_bullets(sections["DELIVERABLES"])
187
+
188
+ return data
189
+
190
+
191
+ def load_input(path: Path) -> Dict[str, Any]:
192
+ raw = path.read_text(encoding="utf-8")
193
+ raw = _strip_bom(raw)
194
+
195
+ if path.suffix.lower() in {".yaml", ".yml"}:
196
+ loaded = yaml.safe_load(raw) or {}
197
+ if not isinstance(loaded, dict):
198
+ raise ValueError("YAML input must be a mapping/object at top level")
199
+ return loaded
200
+
201
+ if path.suffix.lower() == ".json":
202
+ import json
203
+
204
+ loaded = json.loads(raw)
205
+ if not isinstance(loaded, dict):
206
+ raise ValueError("JSON input must be an object at top level")
207
+ return loaded
208
+
209
+ return parse_llm_task_text(raw)
210
+
211
+
212
+ def dump_yaml(data: Dict[str, Any]) -> str:
213
+ return yaml.safe_dump(
214
+ data,
215
+ sort_keys=False,
216
+ allow_unicode=True,
217
+ width=100,
218
+ default_flow_style=False,
219
+ )
220
+
221
+
222
+ def create_parser() -> argparse.ArgumentParser:
223
+ p = argparse.ArgumentParser(
224
+ prog="llm-task-generator",
225
+ description="Generate normalized llm_task.yaml from simplified task spec (text/YAML/JSON).",
226
+ )
227
+ p.add_argument("-i", "--input", required=True, help="Input file: .txt/.md/.yaml/.yml/.json")
228
+ p.add_argument("-o", "--output", required=True, help="Output YAML file path")
229
+ p.add_argument(
230
+ "--validate-only",
231
+ action="store_true",
232
+ help="Only validate/normalize input; do not write output file",
233
+ )
234
+ return p
235
+
236
+
237
+ def main(argv: Optional[List[str]] = None) -> int:
238
+ args = create_parser().parse_args(argv)
239
+
240
+ input_path = Path(args.input)
241
+ if not input_path.exists():
242
+ print(f"Error: input file not found: {input_path}", file=sys.stderr)
243
+ return 2
244
+
245
+ data = load_input(input_path)
246
+
247
+ if "task" not in data:
248
+ data = {"task": data}
249
+
250
+ normalized = normalize_llm_task(data)
251
+
252
+ if args.validate_only:
253
+ sys.stdout.write(dump_yaml(normalized))
254
+ return 0
255
+
256
+ output_path = Path(args.output)
257
+ output_path.parent.mkdir(parents=True, exist_ok=True)
258
+ output_path.write_text(dump_yaml(normalized), encoding="utf-8")
259
+ return 0
260
+
261
+
262
+ if __name__ == "__main__":
263
+ raise SystemExit(main())