@@ -72,16 +72,24 @@ def generate_toon(
7272 delim = toon .delimiter
7373 dm = toon .delim_marker
7474
75+ # Pre-filter: only modules with at least one function/method
76+ all_modules = list (project .modules or [])
77+ modules_with_items = [(m , self ._module_items (m )) for m in all_modules ]
78+ modules_with_items = [(m , items ) for m , items in modules_with_items if items ]
79+
7580 lines : List [str ] = []
81+
82+ # Format header — helps LLM understand the structure
83+ lines .append (f"# { project .name } function-logic | { len (modules_with_items )} modules" )
84+ lines .append ("# Convention: name with . = method, ~name = async, cc:N shown only when >1" )
85+
7686 lines .append (f"project: { toon ._quote (project .name )} " )
7787 if getattr (project , 'generated_at' , None ):
7888 lines .append (f"generated: { toon ._quote (project .generated_at )} " )
7989
80- modules = list (project .modules or [])
81- lines .append (f"modules[{ len (modules )} ]{{path{ dm } lang{ dm } items}}:" )
90+ lines .append (f"modules[{ len (modules_with_items )} ]{{path{ dm } lang{ dm } items}}:" )
8291 prev_dir : str | None = None
83- for m in modules :
84- items = self ._module_items (m )
92+ for m , items in modules_with_items :
8593 if no_repeat_name :
8694 compressed_path , prev_dir = toon ._compress_module_path (m .path , prev_dir )
8795 path_out = compressed_path
@@ -93,19 +101,15 @@ def generate_toon(
93101 lines .append ("function_details:" )
94102
95103 prev_dir = None
96- for m in modules :
97- items = self ._module_items (m )
98- if not items :
99- continue
100-
104+ for m , items in modules_with_items :
101105 if no_repeat_details :
102106 compressed_path , prev_dir = toon ._compress_module_path (m .path , prev_dir )
103107 details_key = compressed_path
104108 else :
105109 details_key = m .path
106110 lines .append (f" { toon ._quote (details_key )} :" )
107111
108- header = f"line{ dm } name{ dm } kind { dm } sig{ dm } async { dm } cc "
112+ header = f"line{ dm } name{ dm } sig"
109113 if detail in ('standard' , 'full' ):
110114 header += f"{ dm } does"
111115 if detail == 'full' :
@@ -115,15 +119,20 @@ def generate_toon(
115119
116120 for kind , qname , func in items :
117121 sig = self ._build_sig (func , include_async_prefix = False , language = m .language )
118- is_async = 'true' if getattr (func , 'is_async' , False ) else 'false'
119122 start_line = str (getattr (func , 'start_line' , 0 ) or 0 )
123+
124+ # Encode async as ~ prefix, cc as suffix (only when >1)
125+ display_name = qname
126+ if getattr (func , 'is_async' , False ):
127+ display_name = f"~{ qname } "
128+ cc = getattr (func , 'complexity' , 1 ) or 1
129+ if cc > 1 :
130+ display_name = f"{ display_name } cc:{ cc } "
131+
120132 row = [
121133 start_line ,
122- toon ._quote (qname ),
123- toon ._quote (kind ),
134+ toon ._quote (display_name ),
124135 toon ._quote (sig ),
125- is_async ,
126- str (getattr (func , 'complexity' , 1 ) or 1 ),
127136 ]
128137
129138 if detail in ('standard' , 'full' ):
@@ -142,6 +151,73 @@ def generate_toon(
142151
143152 return "\n " .join (lines ).rstrip () + "\n "
144153
154+ def generate_toon_schema (self ) -> str :
155+ """Generate JSON Schema describing the function-logic TOON format."""
156+ import json
157+
158+ schema = {
159+ "$schema" : "https://json-schema.org/draft/2020-12/schema" ,
160+ "title" : "Code2Logic Function-Logic TOON Schema" ,
161+ "description" : (
162+ "Schema for project.functions.toon — compact function/method index. "
163+ "Conventions: name containing '.' = method (Class.method), "
164+ "~prefix = async, 'cc:N' suffix = cyclomatic complexity (only when >1)."
165+ ),
166+ "type" : "object" ,
167+ "properties" : {
168+ "project" : {"type" : "string" , "description" : "Project name" },
169+ "generated" : {"type" : "string" , "description" : "ISO timestamp" },
170+ "modules" : {
171+ "type" : "array" ,
172+ "description" : "Modules with at least one function/method. Rows: path,lang,items. Use ./file for same-dir compression (--no-repeat-module)." ,
173+ "items" : {
174+ "type" : "object" ,
175+ "properties" : {
176+ "path" : {"type" : "string" , "description" : "Relative path or ./basename if same dir as previous" },
177+ "lang" : {"type" : "string" , "description" : "Short language code (py, js, ts, ...)" },
178+ "items" : {"type" : "integer" , "description" : "Number of functions+methods in module" }
179+ }
180+ }
181+ },
182+ "function_details" : {
183+ "type" : "object" ,
184+ "description" : "Per-module function tables. Keys are module paths (or ./basename with --no-repeat-details)." ,
185+ "patternProperties" : {
186+ ".*" : {
187+ "type" : "object" ,
188+ "properties" : {
189+ "functions" : {
190+ "type" : "array" ,
191+ "description" : "Tabular rows: line,name,sig[,does][,decorators,calls,raises]" ,
192+ "items" : {
193+ "type" : "object" ,
194+ "properties" : {
195+ "line" : {"type" : "integer" , "description" : "Start line number" },
196+ "name" : {
197+ "type" : "string" ,
198+ "description" : (
199+ "Function or method name. "
200+ "Contains '.' if method (e.g. Class.method). "
201+ "Prefixed with ~ if async. "
202+ "Suffixed with ' cc:N' if cyclomatic complexity > 1."
203+ )
204+ },
205+ "sig" : {"type" : "string" , "description" : "Signature: (params) [-> return_type]" },
206+ "does" : {"type" : "string" , "description" : "Intent/purpose (standard+full detail)" },
207+ "decorators" : {"type" : "string" , "description" : "Pipe-separated decorators (full detail)" },
208+ "calls" : {"type" : "string" , "description" : "Pipe-separated function calls (full detail)" },
209+ "raises" : {"type" : "string" , "description" : "Pipe-separated exceptions (full detail)" }
210+ }
211+ }
212+ }
213+ }
214+ }
215+ }
216+ }
217+ }
218+ }
219+ return json .dumps (schema , indent = 2 , ensure_ascii = False )
220+
145221 def _build_data (self , project : ProjectInfo , detail : str ) -> dict :
146222 modules_data = []
147223 for m in project .modules or []:
0 commit comments