@@ -87,6 +87,9 @@ def _build_context(self, project_name: str) -> Dict:
8787 # Project description: LLM if available, else package docstring
8888 project_description = self ._generate_description (project_name , entry_points )
8989
90+ # Metadata: author, license, contributors
91+ metadata = self ._extract_project_metadata ()
92+
9093 return {
9194 "project_name" : project_name ,
9295 "project_path" : self .result .project_path ,
@@ -102,6 +105,13 @@ def _build_context(self, project_name: str) -> Dict:
102105 "module_tree" : module_tree ,
103106 "modules" : self .result .modules ,
104107 "sync_markers" : self .config .readme .sync_markers ,
108+ # New metadata fields
109+ "author" : metadata .get ("author" , "" ),
110+ "license" : metadata .get ("license" , "" ),
111+ "license_file" : metadata .get ("license_file" , "" ),
112+ "contributors" : metadata .get ("contributors" , []),
113+ "repo_url" : self .config .repo_url ,
114+ "version" : metadata .get ("version" , "0.1.0" ),
105115 }
106116
107117 def _calc_avg_complexity (self ) -> float :
@@ -165,6 +175,100 @@ def _extract_project_description(self, project_name: str) -> str:
165175 return doc .strip ()
166176 return ""
167177
178+ def _extract_project_metadata (self ) -> Dict :
179+ """Extract project metadata (author, license, version) from pyproject.toml or git."""
180+ metadata = {
181+ "author" : "" ,
182+ "license" : "" ,
183+ "license_file" : "" ,
184+ "contributors" : [],
185+ "version" : "0.1.0" ,
186+ }
187+
188+ # Try pyproject.toml
189+ try :
190+ import tomllib
191+ pyproject_path = Path (self .result .project_path ) / "pyproject.toml"
192+ if pyproject_path .exists ():
193+ with open (pyproject_path , "rb" ) as f :
194+ data = tomllib .load (f )
195+
196+ project = data .get ("project" , {})
197+ metadata ["version" ] = project .get ("version" , metadata ["version" ])
198+
199+ # Authors
200+ authors = project .get ("authors" , [])
201+ if authors :
202+ if isinstance (authors [0 ], dict ):
203+ metadata ["author" ] = authors [0 ].get ("name" , "" )
204+ metadata ["contributors" ] = [a .get ("name" , "" ) for a in authors [1 :] if a .get ("name" )]
205+ else :
206+ metadata ["author" ] = str (authors [0 ])
207+
208+ # License
209+ metadata ["license" ] = project .get ("license" , {}).get ("text" , "" )
210+ if not metadata ["license" ]:
211+ metadata ["license" ] = project .get ("license" , "" )
212+ except Exception :
213+ pass
214+
215+ # Try git for contributors if not found
216+ if not metadata ["contributors" ]:
217+ try :
218+ import subprocess
219+ result = subprocess .run (
220+ ["git" , "shortlog" , "-sne" , "HEAD" ],
221+ capture_output = True , text = True , cwd = self .result .project_path
222+ )
223+ if result .returncode == 0 :
224+ contributors = []
225+ for line in result .stdout .strip ().split ("\n " )[:5 ]:
226+ parts = line .split ("\t " )
227+ if len (parts ) >= 2 :
228+ contributors .append (parts [1 ])
229+ metadata ["contributors" ] = contributors
230+ except Exception :
231+ pass
232+
233+ # Try git config for author if not found
234+ if not metadata ["author" ]:
235+ try :
236+ import subprocess
237+ name = subprocess .run (
238+ ["git" , "config" , "user.name" ],
239+ capture_output = True , text = True , cwd = self .result .project_path
240+ )
241+ email = subprocess .run (
242+ ["git" , "config" , "user.email" ],
243+ capture_output = True , text = True , cwd = self .result .project_path
244+ )
245+ if name .returncode == 0 and name .stdout .strip ():
246+ author = name .stdout .strip ()
247+ if email .returncode == 0 and email .stdout .strip ():
248+ author += f" <{ email .stdout .strip ()} >"
249+ metadata ["author" ] = author
250+ except Exception :
251+ pass
252+
253+ # Find LICENSE file
254+ for license_file in ["LICENSE" , "LICENSE.txt" , "LICENSE.md" , "COPYING" ]:
255+ license_path = Path (self .result .project_path ) / license_file
256+ if license_path .exists ():
257+ metadata ["license_file" ] = license_file
258+ # Try to extract license name from file content
259+ if not metadata ["license" ]:
260+ try :
261+ content = license_path .read_text (encoding = "utf-8" ).lower ()
262+ for license_type in ["mit" , "apache" , "gpl" , "bsd" , "mpl" , "lgpl" ]:
263+ if license_type in content :
264+ metadata ["license" ] = license_type .upper ()
265+ break
266+ except Exception :
267+ pass
268+ break
269+
270+ return metadata
271+
168272 def _build_manual (self , project_name : str , sections : List [str ], context : Dict ) -> str :
169273 """Fallback manual README builder (orchestrator)."""
170274 section_builders = {
0 commit comments