Skip to content

Commit 4fc57af

Browse files
Resolve merge conflicts
1 parent 1c4739f commit 4fc57af

3 files changed

Lines changed: 138 additions & 39 deletions

File tree

osa_tool/config/prompts/docstring_generation.toml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,3 +355,60 @@ Format you answer in a way you're writing README file for the module. Use such t
355355
## Overview
356356
## Purpose
357357
Do not mention or describe any submodule or files! Rename snake_case names on meaningful names.Keep in mind that your audience is document readers, so use a deterministic tone to generate precise content and don't let them know you're provided with any information. AVOID ANY SPECULATION and inaccurate descriptions! Now, provide the summarized idea of the module based on it's components'''
358+
359+
class_generation_jsdoc = '''Generate a JSDoc comment for the following JavaScript/TypeScript class {class_name}. Include:
360+
- Respond strictly in English.
361+
- A short summary of what the class does.
362+
- Keep it concise; do NOT list methods or attributes as separate sections.
363+
- Do NOT use Python conventions: no self, __init__, Args:, Returns:, or Attributes: sections.
364+
365+
Return only the comment text, without quotation marks.'''
366+
367+
class_update_jsdoc = '''Update the provided description for the following JavaScript/TypeScript class {class_name} using the project's main idea as context.
368+
Do not mention the project idea explicitly.
369+
370+
The main idea: {main_idea}
371+
Old docstring description part: {old_description}
372+
373+
Return only the changed description, without code, other documentation parts, or quotation marks.'''
374+
375+
method_generation_jsdoc = '''Generate a JSDoc comment for the following JavaScript/TypeScript function.
376+
- Respond strictly in English.
377+
- Include a short summary.
378+
- Include an @param {{type}} name line for every parameter; infer types from code and use * if unknown.
379+
- Include an @returns {{type}} line when the function returns a value.
380+
- Do NOT use Python conventions such as self, __init__, Args:, or Returns:.
381+
- Do NOT document inner functions separately.
382+
383+
Method name: {method_name}
384+
Source code:
385+
```
386+
{source_code}
387+
```
388+
Arguments: {arguments}
389+
Decorators: {decorators}
390+
Related context (for understanding only; do not document it):
391+
{context}
392+
393+
Return only the JSDoc comment text.'''
394+
395+
method_update_jsdoc = '''Update the provided JSDoc comment for the following JavaScript/TypeScript function.
396+
Preserve correct information and add missing details based on the source code.
397+
- Use @param {{type}} name for each parameter and @returns {{type}} for a returned value.
398+
- Do NOT use Python conventions such as self, __init__, Args:, or Returns:.
399+
- Do NOT invent parameters or behaviour.
400+
401+
Original JSDoc:
402+
{docstring}
403+
404+
Method name: {method_name}{class_location}
405+
Decorators: {decorators}
406+
Source code:
407+
```
408+
{source_code}
409+
```
410+
Related context (for understanding only; do not document it):
411+
{context}
412+
Project main idea (context only): {main_idea}
413+
414+
Return only the updated JSDoc comment text.'''

osa_tool/operations/codebase/docstring_generation/docgen.py

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ def __init__(self, config_manager: ConfigManager):
7676
self.config_manager = config_manager
7777
self.model_settings = self.config_manager.get_model_settings("docstring")
7878
self.model_handler: ProtollmHandler = ModelHandlerFactory.build(self.model_settings)
79+
self.readme_model_handler: ProtollmHandler = ModelHandlerFactory.build(
80+
self.config_manager.get_model_settings("readme")
81+
)
7982
self.main_idea = None
8083
self._function_index_cache = None
8184
self.is_small_model = self._is_small_model_name(self.model_settings.model)
@@ -325,14 +328,7 @@ async def generate_class_documentation(
325328
attributes = class_details.attributes
326329
methods = class_details.methods
327330
if language in ("javascript", "typescript"):
328-
prompt = (
329-
f"""Generate a JSDoc comment for the following JavaScript/TypeScript class {class_name}. Include:\n"""
330-
"- Respond strictly in English.\n"
331-
"- A short summary of what the class does.\n"
332-
"- Keep it concise; do NOT list methods/attributes as separate sections.\n"
333-
"- Do NOT use Python conventions: no `self`, no `__init__`, no `Args:`/`Returns:`/`Attributes:` sections.\n\n"
334-
"Return only the comment text without any quotation."
335-
)
331+
prompt = self._render_prompt("class_generation_jsdoc", class_name=class_name)
336332
else:
337333
prompt = (
338334
self._get_class_generation_prompt_small(class_name, attributes, methods)
@@ -342,6 +338,8 @@ async def generate_class_documentation(
342338

343339
async with semaphore:
344340
docstring = await self.model_handler.async_request(prompt)
341+
if language in ("javascript", "typescript"):
342+
return docstring.strip()
345343
return self.extract_pure_docstring(docstring)
346344

347345
def _get_class_generation_prompt_large(self, class_name: str, attributes: list, methods: list) -> str:
@@ -411,12 +409,11 @@ async def update_class_documentation(
411409
old_description=old_desc,
412410
)
413411
else:
414-
prompt = (
415-
f"""Update the provided description for the following JavaScript/TypeScript class {class_details.name} using provided main idea of the project.\n"""
416-
"""Do not pay too much attention to the provided main idea - try not to mention it explicitly.\n"""
417-
f"""The main idea: {self.main_idea}\n"""
418-
f"""Old docstring description part: {old_desc}\n\n"""
419-
"""Return only pure changed description - without any code, other parts of docs, any quotations)"""
412+
prompt = self._render_prompt(
413+
"class_update_jsdoc",
414+
class_name=class_details.name,
415+
main_idea=self.main_idea,
416+
old_description=old_desc,
420417
)
421418

422419
async with semaphore:
@@ -452,6 +449,8 @@ async def generate_method_documentation(
452449

453450
async with semaphore:
454451
docstring = await self.model_handler.async_request(prompt)
452+
if language in ("javascript", "typescript"):
453+
return docstring.strip()
455454
extracted = self.extract_pure_docstring(docstring)
456455
return self.clean_docstring(extracted)
457456

@@ -460,13 +459,13 @@ def _get_method_generation_prompt_large(
460459
) -> str:
461460
arguments = [a for a in method_details["arguments"] if a not in ("self", "cls")]
462461
if language in ("javascript", "typescript"):
463-
intro = (
464-
"Generate a JSDoc comment for the following JavaScript/TypeScript function. Use JSDoc tags and include:\n"
465-
"- Respond strictly in English.\n"
466-
"- A short summary of what the function does.\n"
467-
"- A `@param {type} name` line for each parameter (infer the type from the code; use `*` if unknown).\n"
468-
"- A `@returns {type}` line describing the return value (omit it if the function returns nothing).\n"
469-
"- Do NOT use Python conventions: no `self`, no `__init__`, no `Args:`/`Returns:` sections.\n\n"
462+
return self._render_prompt(
463+
"method_generation_jsdoc",
464+
method_name=method_details["method_name"],
465+
source_code=method_details["source_code"],
466+
arguments=arguments,
467+
decorators=method_details["decorators"],
468+
context=context_code or "",
470469
)
471470
else:
472471
intro = (
@@ -493,9 +492,6 @@ def _get_method_generation_prompt_large(
493492
"Method Details:\n"
494493
f"- Method decorators: {method_details['decorators']}\n\n"
495494
)
496-
497-
if language in ("javascript", "typescript"):
498-
return prompt
499495
return self._render_prompt(
500496
"method_generation_standard",
501497
method_name=method_details["method_name"],
@@ -557,6 +553,8 @@ async def update_method_documentation(
557553

558554
async with semaphore:
559555
response = await self.model_handler.async_request(prompt)
556+
if language in ("javascript", "typescript"):
557+
return response.strip()
560558
return self.clean_docstring(self.extract_pure_docstring(response))
561559

562560
def _get_method_update_prompt_large(
@@ -568,14 +566,15 @@ def _get_method_update_prompt_large(
568566
language: str = "python",
569567
) -> str:
570568
if language in ("javascript", "typescript"):
571-
guidelines = (
572-
"Update the provided JSDoc comment for the following JavaScript/TypeScript function.\n"
573-
"Preserve correct existing information and add missing details based on the source code.\n\n"
574-
"Guidelines:\n"
575-
"- Improve clarity and completeness without rewriting everything from scratch.\n"
576-
"- Use JSDoc tags: `@param {type} name` for each parameter, `@returns {type}` for the return value (omit if nothing is returned).\n"
577-
"- Do NOT use Python conventions: no `self`, no `__init__`, no `Args:`/`Returns:` sections.\n"
578-
"- Do NOT invent parameters or behavior.\n\n"
569+
return self._render_prompt(
570+
"method_update_jsdoc",
571+
docstring=docstring,
572+
method_name=method_details["method_name"],
573+
class_location=f" (located inside {class_name} class)" if class_name else "",
574+
decorators=method_details["decorators"],
575+
source_code=method_details["source_code"],
576+
context=context_code or "",
577+
main_idea=self.main_idea,
579578
)
580579
else:
581580
guidelines = (
@@ -600,9 +599,6 @@ def _get_method_update_prompt_large(
600599
f"{method_details['source_code']}\n"
601600
"```\n\n"
602601
)
603-
604-
if language in ("javascript", "typescript"):
605-
return prompt
606602
return self._render_prompt(
607603
"method_update_standard",
608604
docstring=docstring,
@@ -1473,7 +1469,7 @@ async def generate_the_main_idea(self, parsed_structure: dict, top_n: int = 5) -
14731469

14741470
components = "\n\n".join(prompt_structure)
14751471

1476-
self.main_idea = await self.model_handler.async_request(
1472+
self.main_idea = await self.readme_model_handler.async_request(
14771473
self._render_prompt("main_idea_generation", components=components)
14781474
)
14791475

@@ -1523,7 +1519,7 @@ async def summarize_directory(name: str, file_summaries: List[str], submodule_su
15231519
logger.info(f"Generating summary for the module {name}")
15241520

15251521
async with semaphore:
1526-
return await self.model_handler.async_request(
1522+
return await self.readme_model_handler.async_request(
15271523
self._render_prompt("submodule_summary", components=components, main_idea=self.main_idea)
15281524
)
15291525

tests/unit/operations/codebase/docstring_generation/test_docgen.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,24 @@ async def test_generate_class_documentation_uses_small_model_prompt(mock_config_
327327
assert "must start at column 0" not in prompt
328328

329329

330+
@pytest.mark.asyncio
331+
async def test_generate_class_documentation_keeps_jsdoc_response_raw(mock_config_manager):
332+
docgen = DocGen(mock_config_manager)
333+
docgen.is_small_model = True
334+
docgen.model_handler.async_request = AsyncMock(return_value="/**\n * Documents a class.\n */")
335+
336+
result = await docgen.generate_class_documentation(
337+
ClassDocumentationDetails(name="Widget"),
338+
asyncio.Semaphore(1),
339+
language="typescript",
340+
)
341+
342+
prompt = docgen.model_handler.async_request.await_args.args[0]
343+
assert result == "/**\n * Documents a class.\n */"
344+
assert "JSDoc" in prompt
345+
assert "Google-style" not in prompt
346+
347+
330348
@pytest.mark.asyncio
331349
async def test_update_class_documentation(mock_config_manager):
332350
# Arrange
@@ -395,6 +413,30 @@ async def test_update_method_documentation(mock_config_manager):
395413
docgen.model_handler.async_request.assert_called_once()
396414

397415

416+
@pytest.mark.asyncio
417+
async def test_generate_method_documentation_uses_jsdoc_for_small_models(mock_config_manager):
418+
docgen = DocGen(mock_config_manager)
419+
docgen.is_small_model = True
420+
docgen.model_handler.async_request = AsyncMock(return_value="Describes the function.")
421+
422+
result = await docgen.generate_method_documentation(
423+
{
424+
"method_name": "run",
425+
"source_code": "return value;",
426+
"arguments": ["value"],
427+
"decorators": [],
428+
"docstring": "",
429+
},
430+
asyncio.Semaphore(1),
431+
language="javascript",
432+
)
433+
434+
prompt = docgen.model_handler.async_request.await_args.args[0]
435+
assert result == "Describes the function."
436+
assert "JSDoc" in prompt
437+
assert "Google-style" not in prompt
438+
439+
398440
def test_valid_triple_quotes(mock_config_manager):
399441
# Arrange
400442
docgen = DocGen(mock_config_manager)
@@ -722,7 +764,7 @@ async def test_generate_the_main_idea_filters_and_sorts(mock_config_manager, moc
722764
# Arrange
723765
docgen = DocGen(mock_config_manager)
724766
mock_request = mocker.AsyncMock(return_value="# Project\n## Overview\n## Purpose")
725-
docgen.model_handler.async_request = mock_request
767+
docgen.readme_model_handler.async_request = mock_request
726768

727769
parsed_structure = {
728770
"src/core.py": {
@@ -772,7 +814,11 @@ async def test_summarize_submodules_creates_summaries(mock_config_manager, mocke
772814
(sub_dir / "__init__.py").write_text("")
773815
(sub_dir / "helper.py").write_text("def helper(): pass")
774816

775-
mocker.patch.object(docgen.model_handler, "async_request", new=mocker.AsyncMock(return_value="Summary of module"))
817+
mocker.patch.object(
818+
docgen.readme_model_handler,
819+
"async_request",
820+
new=mocker.AsyncMock(return_value="Summary of module"),
821+
)
776822

777823
project_structure = {
778824
str(pkg_dir / "core.py"): {

0 commit comments

Comments
 (0)