clink-cli 0.2.1__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 (141) hide show
  1. clink/__init__.py +19 -0
  2. clink/agents/__init__.py +30 -0
  3. clink/agents/base.py +70 -0
  4. clink/agents/claude.py +7 -0
  5. clink/agents/codex.py +7 -0
  6. clink/agents/gemini.py +7 -0
  7. clink/allowed_roots.py +356 -0
  8. clink/backend.py +765 -0
  9. clink/cli.py +594 -0
  10. clink/constants.py +78 -0
  11. clink/contracts.py +167 -0
  12. clink/files.py +70 -0
  13. clink/generation.py +274 -0
  14. clink/generation_capabilities.py +207 -0
  15. clink/generation_contracts.py +214 -0
  16. clink/generation_policy.py +174 -0
  17. clink/models.py +243 -0
  18. clink/native.py +409 -0
  19. clink/native_adapters.py +244 -0
  20. clink/native_contracts.py +68 -0
  21. clink/native_generation.py +421 -0
  22. clink/native_generation_claude_policy.py +59 -0
  23. clink/native_generation_contracts.py +264 -0
  24. clink/native_generation_families.py +218 -0
  25. clink/native_generation_family_policy.py +14 -0
  26. clink/native_generation_files.py +153 -0
  27. clink/native_generation_policy.py +82 -0
  28. clink/native_policy.py +86 -0
  29. clink/parsers/__init__.py +34 -0
  30. clink/parsers/agy.py +53 -0
  31. clink/parsers/agy_native.py +161 -0
  32. clink/parsers/agy_native_generation.py +189 -0
  33. clink/parsers/base.py +209 -0
  34. clink/parsers/claude.py +104 -0
  35. clink/parsers/claude_native.py +113 -0
  36. clink/parsers/claude_native_generation.py +346 -0
  37. clink/parsers/codex.py +172 -0
  38. clink/parsers/codex_generation.py +212 -0
  39. clink/parsers/codex_native.py +236 -0
  40. clink/parsers/codex_native_generation.py +367 -0
  41. clink/parsers/gemini.py +110 -0
  42. clink/parsers/grok.py +63 -0
  43. clink/parsers/grok_native.py +91 -0
  44. clink/parsers/grok_native_generation.py +157 -0
  45. clink/policy.py +180 -0
  46. clink/registry.py +544 -0
  47. clink/resources/CODEX-LICENSE +201 -0
  48. clink/resources/CODEX-NOTICE +6 -0
  49. clink/resources/PROVENANCE.md +16 -0
  50. clink/resources/codex-0.166.0-models-direct.json +1284 -0
  51. clink/resources/codex-0.166.0-models-generation.json +1269 -0
  52. clink/resources/native-agy-1.4.12-admission.json +746 -0
  53. clink/resources/native-claude-2.1.304-admission.json +99 -0
  54. clink/resources/native-generation-claude-2.1.304-admission.json +277 -0
  55. clink/resources/native-generation-codex-0.166.0-admission.json +309 -0
  56. clink/resources/native-generation-grok-1.0.70-admission.json +329 -0
  57. clink/resources/native-grok-1.0.70-admission.json +644 -0
  58. clink/runner.py +303 -0
  59. clink/setup.py +399 -0
  60. clink/tasks.py +695 -0
  61. clink_cli-0.2.1.dist-info/METADATA +225 -0
  62. clink_cli-0.2.1.dist-info/RECORD +141 -0
  63. clink_cli-0.2.1.dist-info/WHEEL +5 -0
  64. clink_cli-0.2.1.dist-info/entry_points.txt +3 -0
  65. clink_cli-0.2.1.dist-info/licenses/LICENSE +197 -0
  66. clink_cli-0.2.1.dist-info/licenses/clink/resources/CODEX-LICENSE +201 -0
  67. clink_cli-0.2.1.dist-info/licenses/clink/resources/CODEX-NOTICE +6 -0
  68. clink_cli-0.2.1.dist-info/top_level.txt +7 -0
  69. conf/__init__.py +1 -0
  70. conf/cli_clients/agy.json +40 -0
  71. conf/cli_clients/claude.json +39 -0
  72. conf/cli_clients/codex.json +40 -0
  73. conf/cli_clients/gemini.json +23 -0
  74. conf/cli_clients/grok.json +40 -0
  75. config.py +40 -0
  76. server.py +896 -0
  77. systemprompts/__init__.py +35 -0
  78. systemprompts/analyze_prompt.py +87 -0
  79. systemprompts/chat_prompt.py +57 -0
  80. systemprompts/clink/codex_codereviewer.txt +8 -0
  81. systemprompts/clink/default.txt +8 -0
  82. systemprompts/clink/default_codereviewer.txt +8 -0
  83. systemprompts/clink/default_planner.txt +7 -0
  84. systemprompts/clink/native_codereviewer.txt +1 -0
  85. systemprompts/clink/native_default.txt +1 -0
  86. systemprompts/clink/native_planner.txt +1 -0
  87. systemprompts/codereview_prompt.py +91 -0
  88. systemprompts/consensus_prompt.py +120 -0
  89. systemprompts/debug_prompt.py +150 -0
  90. systemprompts/docgen_prompt.py +252 -0
  91. systemprompts/generate_code_prompt.py +181 -0
  92. systemprompts/planner_prompt.py +127 -0
  93. systemprompts/precommit_prompt.py +179 -0
  94. systemprompts/refactor_prompt.py +323 -0
  95. systemprompts/secaudit_prompt.py +420 -0
  96. systemprompts/testgen_prompt.py +133 -0
  97. systemprompts/thinkdeep_prompt.py +55 -0
  98. systemprompts/tracer_prompt.py +145 -0
  99. tools/__init__.py +43 -0
  100. tools/analyze.py +583 -0
  101. tools/apilookup.py +97 -0
  102. tools/challenge.py +198 -0
  103. tools/chat.py +187 -0
  104. tools/clink.py +130 -0
  105. tools/codereview.py +724 -0
  106. tools/consensus.py +790 -0
  107. tools/debug.py +573 -0
  108. tools/docgen.py +577 -0
  109. tools/listmodels.py +48 -0
  110. tools/models.py +373 -0
  111. tools/planner.py +533 -0
  112. tools/precommit.py +776 -0
  113. tools/refactor.py +645 -0
  114. tools/secaudit.py +685 -0
  115. tools/shared/__init__.py +19 -0
  116. tools/shared/base_models.py +167 -0
  117. tools/shared/base_tool.py +659 -0
  118. tools/shared/exceptions.py +20 -0
  119. tools/shared/schema_builders.py +159 -0
  120. tools/simple/__init__.py +18 -0
  121. tools/simple/base.py +539 -0
  122. tools/testgen.py +513 -0
  123. tools/thinkdeep.py +586 -0
  124. tools/tracer.py +763 -0
  125. tools/version.py +53 -0
  126. tools/workflow/__init__.py +22 -0
  127. tools/workflow/base.py +448 -0
  128. tools/workflow/schema_builders.py +166 -0
  129. tools/workflow/workflow_mixin.py +1195 -0
  130. utils/__init__.py +21 -0
  131. utils/call_state.py +349 -0
  132. utils/client_info.py +293 -0
  133. utils/consensus_manifest.py +131 -0
  134. utils/conversation_memory.py +582 -0
  135. utils/env.py +111 -0
  136. utils/file_types.py +271 -0
  137. utils/file_utils.py +803 -0
  138. utils/image_utils.py +94 -0
  139. utils/security_config.py +163 -0
  140. utils/storage_backend.py +177 -0
  141. utils/token_utils.py +54 -0
tools/docgen.py ADDED
@@ -0,0 +1,577 @@
1
+ """
2
+ Documentation Generation tool - Automated code documentation with complexity analysis
3
+
4
+ This tool provides a structured workflow for adding comprehensive documentation to codebases.
5
+ It guides you through systematic code analysis to generate modern documentation with:
6
+ - Function/method parameter documentation
7
+ - Big O complexity analysis
8
+ - Call flow and dependency documentation
9
+ - Inline comments for complex logic
10
+ - Smart updating of existing documentation
11
+
12
+ Key features:
13
+ - Step-by-step documentation workflow with progress tracking
14
+ - Context-aware file embedding (references during analysis, full content for documentation)
15
+ - Automatic conversation threading and history preservation
16
+ - Expert analysis integration with external models
17
+ - Support for multiple programming languages and documentation styles
18
+ - Configurable documentation features via parameters
19
+ """
20
+
21
+ import logging
22
+ from typing import TYPE_CHECKING, Any, Optional
23
+
24
+ from pydantic import Field
25
+
26
+ if TYPE_CHECKING:
27
+ from tools.models import ToolModelCategory
28
+
29
+ from config import TEMPERATURE_ANALYTICAL
30
+ from systemprompts import DOCGEN_PROMPT
31
+ from tools.shared.base_models import WorkflowRequest
32
+
33
+ from .workflow.base import WorkflowTool
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+ # Tool-specific field descriptions for documentation generation
38
+ DOCGEN_FIELD_DESCRIPTIONS = {
39
+ "step": (
40
+ "Step 1 (Discovery): list every file that needs documentation and record the total. Do not write docs yet. "
41
+ "Steps 2+: document exactly one file per step. Never change code logic; log bugs separately. Keep the counters accurate."
42
+ ),
43
+ "step_number": "Current documentation step (starts at 1).",
44
+ "total_steps": "1 discovery step + one step per file documented (tracks via `total_files_to_document`).",
45
+ "next_step_required": "True while more files still need documentation; False once everything is complete.",
46
+ "findings": "Summarize documentation gaps, complexity, call flows, and well-documented areas. Stop and report immediately if you uncover a bug.",
47
+ "relevant_files": "Absolute paths for the file(s) you are documenting this step—stick to a single file per step.",
48
+ "relevant_context": "Functions or methods needing documentation (e.g. 'Class.method', 'function_name'), especially complex or user-facing areas.",
49
+ "num_files_documented": "Count of files finished so far. Increment only when a file is fully documented.",
50
+ "total_files_to_document": "Total files identified in discovery; completion requires matching this count.",
51
+ "document_complexity": "Include algorithmic complexity (Big O) analysis when True (default).",
52
+ "document_flow": "Include call flow/dependency notes when True (default).",
53
+ "update_existing": "True (default) to polish inaccurate or outdated docs instead of leaving them untouched.",
54
+ "comments_on_complex_logic": "True (default) to add inline comments around non-obvious logic.",
55
+ }
56
+
57
+
58
+ class DocgenRequest(WorkflowRequest):
59
+ """Request model for documentation generation steps"""
60
+
61
+ # Required workflow fields
62
+ step: str = Field(..., description=DOCGEN_FIELD_DESCRIPTIONS["step"])
63
+ step_number: int = Field(..., description=DOCGEN_FIELD_DESCRIPTIONS["step_number"])
64
+ total_steps: int = Field(..., description=DOCGEN_FIELD_DESCRIPTIONS["total_steps"])
65
+ next_step_required: bool = Field(..., description=DOCGEN_FIELD_DESCRIPTIONS["next_step_required"])
66
+
67
+ # Documentation analysis tracking fields
68
+ findings: str = Field(..., description=DOCGEN_FIELD_DESCRIPTIONS["findings"])
69
+ relevant_files: list[str] = Field(default_factory=list, description=DOCGEN_FIELD_DESCRIPTIONS["relevant_files"])
70
+ relevant_context: list[str] = Field(default_factory=list, description=DOCGEN_FIELD_DESCRIPTIONS["relevant_context"])
71
+
72
+ # Critical completion tracking counters
73
+ num_files_documented: int = Field(0, description=DOCGEN_FIELD_DESCRIPTIONS["num_files_documented"])
74
+ total_files_to_document: int = Field(0, description=DOCGEN_FIELD_DESCRIPTIONS["total_files_to_document"])
75
+
76
+ # Documentation generation configuration parameters
77
+ document_complexity: Optional[bool] = Field(True, description=DOCGEN_FIELD_DESCRIPTIONS["document_complexity"])
78
+ document_flow: Optional[bool] = Field(True, description=DOCGEN_FIELD_DESCRIPTIONS["document_flow"])
79
+ update_existing: Optional[bool] = Field(True, description=DOCGEN_FIELD_DESCRIPTIONS["update_existing"])
80
+ comments_on_complex_logic: Optional[bool] = Field(
81
+ True, description=DOCGEN_FIELD_DESCRIPTIONS["comments_on_complex_logic"]
82
+ )
83
+
84
+
85
+ class DocgenTool(WorkflowTool):
86
+ """
87
+ Documentation generation tool for automated code documentation with complexity analysis.
88
+
89
+ This tool implements a structured documentation workflow that guides users through
90
+ methodical code analysis to generate comprehensive documentation including:
91
+ - Function/method signatures and parameter descriptions
92
+ - Algorithmic complexity (Big O) analysis
93
+ - Call flow and dependency documentation
94
+ - Inline comments for complex logic
95
+ - Modern documentation style appropriate for the language/platform
96
+ """
97
+
98
+ def __init__(self):
99
+ super().__init__()
100
+ self.initial_request = None
101
+
102
+ def get_name(self) -> str:
103
+ return "docgen"
104
+
105
+ def get_description(self) -> str:
106
+ return (
107
+ "Generates comprehensive code documentation with systematic analysis of functions, classes, and complexity. "
108
+ "Use for documentation generation, code analysis, complexity assessment, and API documentation. "
109
+ "Analyzes code structure and patterns to create thorough documentation."
110
+ )
111
+
112
+ def get_system_prompt(self) -> str:
113
+ return DOCGEN_PROMPT
114
+
115
+ def get_default_temperature(self) -> float:
116
+ return TEMPERATURE_ANALYTICAL
117
+
118
+ def get_model_category(self) -> "ToolModelCategory":
119
+ """Docgen requires analytical and reasoning capabilities"""
120
+ from tools.models import ToolModelCategory
121
+
122
+ return ToolModelCategory.EXTENDED_REASONING
123
+
124
+ def requires_model(self) -> bool:
125
+ """
126
+ Docgen tool doesn't require model resolution at the MCP boundary.
127
+
128
+ The docgen tool is a self-contained workflow tool that guides the CLI agent through
129
+ systematic documentation generation without calling external AI models.
130
+
131
+ Returns:
132
+ bool: False - docgen doesn't need external AI model access
133
+ """
134
+ return False
135
+
136
+ def requires_expert_analysis(self) -> bool:
137
+ """Docgen is self-contained and doesn't need expert analysis."""
138
+ return False
139
+
140
+ def get_workflow_request_model(self):
141
+ """Return the docgen-specific request model."""
142
+ return DocgenRequest
143
+
144
+ def get_tool_fields(self) -> dict[str, dict[str, Any]]:
145
+ """Return the tool-specific fields for docgen."""
146
+ return {
147
+ "document_complexity": {
148
+ "type": "boolean",
149
+ "default": True,
150
+ "description": DOCGEN_FIELD_DESCRIPTIONS["document_complexity"],
151
+ },
152
+ "document_flow": {
153
+ "type": "boolean",
154
+ "default": True,
155
+ "description": DOCGEN_FIELD_DESCRIPTIONS["document_flow"],
156
+ },
157
+ "update_existing": {
158
+ "type": "boolean",
159
+ "default": True,
160
+ "description": DOCGEN_FIELD_DESCRIPTIONS["update_existing"],
161
+ },
162
+ "comments_on_complex_logic": {
163
+ "type": "boolean",
164
+ "default": True,
165
+ "description": DOCGEN_FIELD_DESCRIPTIONS["comments_on_complex_logic"],
166
+ },
167
+ "num_files_documented": {
168
+ "type": "integer",
169
+ "default": 0,
170
+ "minimum": 0,
171
+ "description": DOCGEN_FIELD_DESCRIPTIONS["num_files_documented"],
172
+ },
173
+ "total_files_to_document": {
174
+ "type": "integer",
175
+ "default": 0,
176
+ "minimum": 0,
177
+ "description": DOCGEN_FIELD_DESCRIPTIONS["total_files_to_document"],
178
+ },
179
+ }
180
+
181
+ def get_required_fields(self) -> list[str]:
182
+ """Return additional required fields beyond the standard workflow requirements."""
183
+ return [
184
+ "document_complexity",
185
+ "document_flow",
186
+ "update_existing",
187
+ "comments_on_complex_logic",
188
+ "num_files_documented",
189
+ "total_files_to_document",
190
+ ]
191
+
192
+ def get_input_schema(self) -> dict[str, Any]:
193
+ """Generate input schema using WorkflowSchemaBuilder with field exclusions."""
194
+ from .workflow.schema_builders import WorkflowSchemaBuilder
195
+
196
+ # Exclude workflow fields that documentation generation doesn't need
197
+ excluded_workflow_fields = [
198
+ "confidence", # Documentation doesn't use confidence levels
199
+ "hypothesis", # Documentation doesn't use hypothesis
200
+ "files_checked", # Documentation uses doc_files and doc_methods instead for better tracking
201
+ ]
202
+
203
+ # Exclude common fields that documentation generation doesn't need
204
+ excluded_common_fields = [
205
+ "cli_name",
206
+ "role",
207
+ "model", # Documentation doesn't need external model selection
208
+ "temperature", # Documentation doesn't need temperature control
209
+ "thinking_mode", # Documentation doesn't need thinking mode
210
+ "images", # Documentation doesn't use images
211
+ ]
212
+
213
+ return WorkflowSchemaBuilder.build_schema(
214
+ tool_specific_fields=self.get_tool_fields(),
215
+ required_fields=self.get_required_fields(), # Include docgen-specific required fields
216
+ model_field_schema=None, # Exclude model field - docgen doesn't need external model selection
217
+ auto_mode=False, # Force non-auto mode to prevent model field addition
218
+ tool_name=self.get_name(),
219
+ excluded_workflow_fields=excluded_workflow_fields,
220
+ excluded_common_fields=excluded_common_fields,
221
+ )
222
+
223
+ def get_required_actions(
224
+ self, step_number: int, confidence: str, findings: str, total_steps: int, request=None
225
+ ) -> list[str]:
226
+ """Define required actions for comprehensive documentation analysis with step-by-step file focus."""
227
+ if step_number == 1:
228
+ # Initial discovery ONLY - no documentation yet
229
+ return [
230
+ "CRITICAL: DO NOT ALTER ANY CODE LOGIC! Only add documentation (docstrings, comments)",
231
+ "Discover ALL files in the current directory (not nested) that need documentation",
232
+ "COUNT the exact number of files that need documentation",
233
+ "LIST all the files you found that need documentation by name",
234
+ "IDENTIFY the programming language(s) to use MODERN documentation style (/// for Objective-C, /** */ for Java/JavaScript, etc.)",
235
+ "DO NOT start documenting any files yet - this is discovery phase only",
236
+ "Report the total count and file list clearly to the user",
237
+ "IMMEDIATELY call docgen step 2 after discovery to begin documentation phase",
238
+ "WHEN CALLING DOCGEN step 2: Set total_files_to_document to the exact count you found",
239
+ "WHEN CALLING DOCGEN step 2: Set num_files_documented to 0 (haven't started yet)",
240
+ ]
241
+ elif step_number == 2:
242
+ # Start documentation phase with first file
243
+ return [
244
+ "CRITICAL: DO NOT ALTER ANY CODE LOGIC! Only add documentation (docstrings, comments)",
245
+ "Choose the FIRST file from your discovered list to start documentation",
246
+ "For the chosen file: identify ALL functions, classes, and methods within it",
247
+ 'USE MODERN documentation style for the programming language (/// for Objective-C, /** */ for Java/JavaScript, """ for Python, etc.)',
248
+ "Document ALL functions/methods in the chosen file - don't skip any - DOCUMENTATION ONLY",
249
+ "When file is 100% documented, increment num_files_documented from 0 to 1",
250
+ "Note any dependencies this file has (what it imports/calls) and what calls into it",
251
+ "CRITICAL: If you find ANY bugs/logic errors, STOP documenting and report to user immediately",
252
+ "Report which specific functions you documented in this step for accountability",
253
+ "Report progress: num_files_documented (1) out of total_files_to_document",
254
+ ]
255
+ elif step_number <= 4:
256
+ # Continue with focused file-by-file approach
257
+ return [
258
+ "CRITICAL: DO NOT ALTER ANY CODE LOGIC! Only add documentation (docstrings, comments)",
259
+ "Choose the NEXT undocumented file from your discovered list",
260
+ "For the chosen file: identify ALL functions, classes, and methods within it",
261
+ "USE MODERN documentation style for the programming language (NEVER use legacy /* */ style for languages with modern alternatives)",
262
+ "Document ALL functions/methods in the chosen file - don't skip any - DOCUMENTATION ONLY",
263
+ "When file is 100% documented, increment num_files_documented by 1",
264
+ "Verify that EVERY function in the current file has proper documentation (no skipping)",
265
+ "CRITICAL: If you find ANY bugs/logic errors, STOP documenting and report to user immediately",
266
+ "Report specific function names you documented for verification",
267
+ "Report progress: current num_files_documented out of total_files_to_document",
268
+ ]
269
+ else:
270
+ # Continue systematic file-by-file coverage
271
+ return [
272
+ "CRITICAL: DO NOT ALTER ANY CODE LOGIC! Only add documentation (docstrings, comments)",
273
+ "Check counters: num_files_documented vs total_files_to_document",
274
+ "If num_files_documented < total_files_to_document: choose NEXT undocumented file",
275
+ "USE MODERN documentation style appropriate for each programming language (NEVER legacy styles)",
276
+ "Document every function, method, and class in current file with no exceptions",
277
+ "When file is 100% documented, increment num_files_documented by 1",
278
+ "CRITICAL: If you find ANY bugs/logic errors, STOP documenting and report to user immediately",
279
+ "Report progress: current num_files_documented out of total_files_to_document",
280
+ "If num_files_documented < total_files_to_document: RESTART docgen with next step",
281
+ "ONLY set next_step_required=false when num_files_documented equals total_files_to_document",
282
+ "For nested dependencies: check if functions call into subdirectories and document those too",
283
+ "CRITICAL: If ANY bugs/logic errors were found, STOP and ask user before proceeding",
284
+ ]
285
+
286
+ def should_call_expert_analysis(self, consolidated_findings, request=None) -> bool:
287
+ """Docgen is self-contained and doesn't need expert analysis."""
288
+ return False
289
+
290
+ def prepare_expert_analysis_context(self, consolidated_findings) -> str:
291
+ """Docgen doesn't use expert analysis."""
292
+ return ""
293
+
294
+ def get_step_guidance(self, step_number: int, confidence: str, request) -> dict[str, Any]:
295
+ """
296
+ Provide step-specific guidance for documentation generation workflow.
297
+
298
+ This method generates docgen-specific guidance used by get_step_guidance_message().
299
+ """
300
+ # Generate the next steps instruction based on required actions
301
+ # Calculate dynamic total_steps based on files to document
302
+ total_files_to_document = self.get_request_total_files_to_document(request)
303
+ calculated_total_steps = 1 + total_files_to_document if total_files_to_document > 0 else request.total_steps
304
+
305
+ required_actions = self.get_required_actions(step_number, confidence, request.findings, calculated_total_steps)
306
+
307
+ if step_number == 1:
308
+ next_steps = (
309
+ f"DISCOVERY PHASE ONLY - DO NOT START DOCUMENTING YET!\n"
310
+ f"MANDATORY: DO NOT call the {self.get_name()} tool again immediately. You MUST first perform "
311
+ f"FILE DISCOVERY step by step. DO NOT DOCUMENT ANYTHING YET. "
312
+ f"MANDATORY ACTIONS before calling {self.get_name()} step {step_number + 1}:\n"
313
+ + "\n".join(f"{i+1}. {action}" for i, action in enumerate(required_actions))
314
+ + f"\n\nCRITICAL: When you call {self.get_name()} step 2, set total_files_to_document to the exact count "
315
+ f"of files needing documentation and set num_files_documented to 0 (haven't started documenting yet). "
316
+ f"Your total_steps will be automatically calculated as 1 (discovery) + number of files to document. "
317
+ f"Step 2 will BEGIN the documentation phase. Report the count clearly and then IMMEDIATELY "
318
+ f"proceed to call {self.get_name()} step 2 to start documenting the first file."
319
+ )
320
+ elif step_number == 2:
321
+ next_steps = (
322
+ f"DOCUMENTATION PHASE BEGINS! ABSOLUTE RULE: DO NOT ALTER ANY CODE LOGIC! DOCUMENTATION ONLY!\n"
323
+ f"START FILE-BY-FILE APPROACH! Focus on ONE file until 100% complete. "
324
+ f"MANDATORY ACTIONS before calling {self.get_name()} step {step_number + 1}:\n"
325
+ + "\n".join(f"{i+1}. {action}" for i, action in enumerate(required_actions))
326
+ + f"\n\nREPORT your progress: which specific functions did you document? Update num_files_documented from 0 to 1 when first file complete. "
327
+ f"REPORT counters: current num_files_documented out of total_files_to_document. "
328
+ f"CRITICAL: If you found ANY bugs/logic errors, STOP documenting and ask user what to do before continuing. "
329
+ f"Do NOT move to a new file until the current one is completely documented. "
330
+ f"When ready for step {step_number + 1}, report completed work with updated counters."
331
+ )
332
+ elif step_number <= 4:
333
+ next_steps = (
334
+ f"ABSOLUTE RULE: DO NOT ALTER ANY CODE LOGIC! DOCUMENTATION ONLY!\n"
335
+ f"CONTINUE FILE-BY-FILE APPROACH! Focus on ONE file until 100% complete. "
336
+ f"MANDATORY ACTIONS before calling {self.get_name()} step {step_number + 1}:\n"
337
+ + "\n".join(f"{i+1}. {action}" for i, action in enumerate(required_actions))
338
+ + f"\n\nREPORT your progress: which specific functions did you document? Update num_files_documented when file complete. "
339
+ f"REPORT counters: current num_files_documented out of total_files_to_document. "
340
+ f"CRITICAL: If you found ANY bugs/logic errors, STOP documenting and ask user what to do before continuing. "
341
+ f"Do NOT move to a new file until the current one is completely documented. "
342
+ f"When ready for step {step_number + 1}, report completed work with updated counters."
343
+ )
344
+ else:
345
+ next_steps = (
346
+ f"ABSOLUTE RULE: DO NOT ALTER ANY CODE LOGIC! DOCUMENTATION ONLY!\n"
347
+ f"CRITICAL: Check if MORE FILES need documentation before finishing! "
348
+ f"REQUIRED ACTIONS before calling {self.get_name()} step {step_number + 1}:\n"
349
+ + "\n".join(f"{i+1}. {action}" for i, action in enumerate(required_actions))
350
+ + f"\n\nREPORT which functions you documented and update num_files_documented when file complete. "
351
+ f"CHECK: If num_files_documented < total_files_to_document, RESTART {self.get_name()} with next step! "
352
+ f"CRITICAL: Only set next_step_required=false when num_files_documented equals total_files_to_document! "
353
+ f"REPORT counters: current num_files_documented out of total_files_to_document. "
354
+ f"CRITICAL: If ANY bugs/logic errors were found during documentation, STOP and ask user before proceeding. "
355
+ f"NO recursive {self.get_name()} calls without actual documentation work!"
356
+ )
357
+
358
+ return {"next_steps": next_steps}
359
+
360
+ # Hook method overrides for docgen-specific behavior
361
+
362
+ async def handle_work_completion(self, response_data: dict, request, arguments: dict) -> dict:
363
+ """
364
+ Override work completion to enforce counter validation.
365
+
366
+ The docgen tool MUST complete ALL files before finishing. If counters don't match,
367
+ force continuation regardless of next_step_required setting.
368
+ """
369
+ # CRITICAL VALIDATION: Check if all files have been documented using proper inheritance hooks
370
+ num_files_documented = self.get_request_num_files_documented(request)
371
+ total_files_to_document = self.get_request_total_files_to_document(request)
372
+
373
+ if num_files_documented < total_files_to_document:
374
+ # Counters don't match - force continuation!
375
+ logger.warning(
376
+ f"Docgen stopping early: {num_files_documented} < {total_files_to_document}. "
377
+ f"Forcing continuation to document remaining files."
378
+ )
379
+
380
+ # Override to continuation mode
381
+ response_data["status"] = "documentation_analysis_required"
382
+ response_data[f"pause_for_{self.get_name()}"] = True
383
+ response_data["next_steps"] = (
384
+ f"CRITICAL ERROR: You attempted to finish documentation with only {num_files_documented} "
385
+ f"out of {total_files_to_document} files documented! You MUST continue documenting "
386
+ f"the remaining {total_files_to_document - num_files_documented} files. "
387
+ f"Call {self.get_name()} again with step {request.step_number + 1} and continue documentation "
388
+ f"of the next undocumented file. DO NOT set next_step_required=false until ALL files are documented!"
389
+ )
390
+ return response_data
391
+
392
+ # If counters match, proceed with normal completion
393
+ return await super().handle_work_completion(response_data, request, arguments)
394
+
395
+ def prepare_step_data(self, request) -> dict:
396
+ """
397
+ Prepare docgen-specific step data for processing.
398
+
399
+ Calculates total_steps dynamically based on number of files to document:
400
+ - Step 1: Discovery phase
401
+ - Steps 2+: One step per file to document
402
+ """
403
+ # Calculate dynamic total_steps based on files to document
404
+ total_files_to_document = self.get_request_total_files_to_document(request)
405
+ if total_files_to_document > 0:
406
+ # Discovery step (1) + one step per file
407
+ calculated_total_steps = 1 + total_files_to_document
408
+ else:
409
+ # Fallback to request total_steps if no file count available
410
+ calculated_total_steps = request.total_steps
411
+
412
+ step_data = {
413
+ "step": request.step,
414
+ "step_number": request.step_number,
415
+ "total_steps": calculated_total_steps, # Use calculated value
416
+ "findings": request.findings,
417
+ "relevant_files": request.relevant_files,
418
+ "relevant_context": request.relevant_context,
419
+ "num_files_documented": request.num_files_documented,
420
+ "total_files_to_document": request.total_files_to_document,
421
+ "issues_found": [], # Docgen uses this for documentation gaps
422
+ "confidence": "medium", # Default confidence for docgen
423
+ "hypothesis": "systematic_documentation_needed", # Default hypothesis
424
+ "images": [], # Docgen doesn't typically use images
425
+ # CRITICAL: Include documentation configuration parameters so the model can see them
426
+ "document_complexity": request.document_complexity,
427
+ "document_flow": request.document_flow,
428
+ "update_existing": request.update_existing,
429
+ "comments_on_complex_logic": request.comments_on_complex_logic,
430
+ }
431
+ return step_data
432
+
433
+ def should_skip_expert_analysis(self, request, consolidated_findings) -> bool:
434
+ """
435
+ Docgen tool skips expert analysis when the CLI agent has "certain" confidence.
436
+ """
437
+ return request.confidence == "certain" and not request.next_step_required
438
+
439
+ # Override inheritance hooks for docgen-specific behavior
440
+
441
+ def get_completion_status(self) -> str:
442
+ """Docgen tools use docgen-specific status."""
443
+ return "documentation_analysis_complete"
444
+
445
+ def get_completion_data_key(self) -> str:
446
+ """Docgen uses 'complete_documentation_analysis' key."""
447
+ return "complete_documentation_analysis"
448
+
449
+ def get_final_analysis_from_request(self, request):
450
+ """Docgen tools use 'hypothesis' field for documentation strategy."""
451
+ return request.hypothesis
452
+
453
+ def get_confidence_level(self, request) -> str:
454
+ """Docgen tools use 'certain' for high confidence."""
455
+ return request.confidence or "high"
456
+
457
+ def get_completion_message(self) -> str:
458
+ """Docgen-specific completion message."""
459
+ return (
460
+ "Documentation analysis complete with high confidence. You have identified the comprehensive "
461
+ "documentation needs and strategy. MANDATORY: Present the user with the documentation plan "
462
+ "and IMMEDIATELY proceed with implementing the documentation without requiring further "
463
+ "consultation. Focus on the precise documentation improvements needed."
464
+ )
465
+
466
+ def get_skip_reason(self) -> str:
467
+ """Docgen-specific skip reason."""
468
+ return "Completed comprehensive documentation analysis locally"
469
+
470
+ def get_request_relevant_context(self, request) -> list:
471
+ """Get relevant_context for docgen tool."""
472
+ try:
473
+ return request.relevant_context or []
474
+ except AttributeError:
475
+ return []
476
+
477
+ def get_request_num_files_documented(self, request) -> int:
478
+ """Get num_files_documented from request. Override for custom handling."""
479
+ try:
480
+ return request.num_files_documented or 0
481
+ except AttributeError:
482
+ return 0
483
+
484
+ def get_request_total_files_to_document(self, request) -> int:
485
+ """Get total_files_to_document from request. Override for custom handling."""
486
+ try:
487
+ return request.total_files_to_document or 0
488
+ except AttributeError:
489
+ return 0
490
+
491
+ def get_skip_expert_analysis_status(self) -> str:
492
+ """Docgen-specific expert analysis skip status."""
493
+ return "skipped_due_to_complete_analysis"
494
+
495
+ def prepare_work_summary(self) -> str:
496
+ """Docgen-specific work summary."""
497
+ try:
498
+ return f"Completed {len(self.work_history)} documentation analysis steps"
499
+ except AttributeError:
500
+ return "Completed documentation analysis"
501
+
502
+ def get_completion_next_steps_message(self, expert_analysis_used: bool = False) -> str:
503
+ """
504
+ Docgen-specific completion message.
505
+ """
506
+ return (
507
+ "DOCUMENTATION ANALYSIS IS COMPLETE FOR ALL FILES (num_files_documented equals total_files_to_document). "
508
+ "MANDATORY FINAL VERIFICATION: Before presenting your summary, you MUST perform a final verification scan. "
509
+ "Read through EVERY file you documented and check EVERY function, method, class, and property to confirm "
510
+ "it has proper documentation including complexity analysis and call flow information. If ANY items lack "
511
+ "documentation, document them immediately before finishing. "
512
+ "THEN present a clear summary showing: 1) Final counters: num_files_documented out of total_files_to_document, "
513
+ "2) Complete accountability list of ALL files you documented with verification status, "
514
+ "3) Detailed list of EVERY function/method you documented in each file (proving complete coverage), "
515
+ "4) Any dependency relationships you discovered between files, 5) Recommended documentation improvements with concrete examples including "
516
+ "complexity analysis and call flow information. 6) **CRITICAL**: List any bugs or logic issues you found "
517
+ "during documentation but did NOT fix - present these to the user and ask what they'd like to do about them. "
518
+ "Make it easy for a developer to see the complete documentation status across the entire codebase with full accountability."
519
+ )
520
+
521
+ def get_step_guidance_message(self, request) -> str:
522
+ """
523
+ Docgen-specific step guidance with detailed analysis instructions.
524
+ """
525
+ step_guidance = self.get_step_guidance(request.step_number, request.confidence, request)
526
+ return step_guidance["next_steps"]
527
+
528
+ def customize_workflow_response(self, response_data: dict, request) -> dict:
529
+ """
530
+ Customize response to match docgen tool format.
531
+ """
532
+ # Store initial request on first step
533
+ if request.step_number == 1:
534
+ self.initial_request = request.step
535
+
536
+ # Convert generic status names to docgen-specific ones
537
+ tool_name = self.get_name()
538
+ status_mapping = {
539
+ f"{tool_name}_in_progress": "documentation_analysis_in_progress",
540
+ f"pause_for_{tool_name}": "pause_for_documentation_analysis",
541
+ f"{tool_name}_required": "documentation_analysis_required",
542
+ f"{tool_name}_complete": "documentation_analysis_complete",
543
+ }
544
+
545
+ if response_data["status"] in status_mapping:
546
+ response_data["status"] = status_mapping[response_data["status"]]
547
+
548
+ # Rename status field to match docgen tool
549
+ if f"{tool_name}_status" in response_data:
550
+ response_data["documentation_analysis_status"] = response_data.pop(f"{tool_name}_status")
551
+ # Add docgen-specific status fields
552
+ response_data["documentation_analysis_status"]["documentation_strategies"] = len(
553
+ self.consolidated_findings.hypotheses
554
+ )
555
+
556
+ # Rename complete documentation analysis data
557
+ if f"complete_{tool_name}" in response_data:
558
+ response_data["complete_documentation_analysis"] = response_data.pop(f"complete_{tool_name}")
559
+
560
+ # Map the completion flag to match docgen tool
561
+ if f"{tool_name}_complete" in response_data:
562
+ response_data["documentation_analysis_complete"] = response_data.pop(f"{tool_name}_complete")
563
+
564
+ # Map the required flag to match docgen tool
565
+ if f"{tool_name}_required" in response_data:
566
+ response_data["documentation_analysis_required"] = response_data.pop(f"{tool_name}_required")
567
+
568
+ return response_data
569
+
570
+ # Required abstract methods from BaseTool
571
+ def get_request_model(self):
572
+ """Return the docgen-specific request model."""
573
+ return DocgenRequest
574
+
575
+ async def prepare_prompt(self, request) -> str:
576
+ """Not used - workflow tools use execute_workflow()."""
577
+ return "" # Workflow tools use execute_workflow() directly
tools/listmodels.py ADDED
@@ -0,0 +1,48 @@
1
+ """Local CLI discovery without authentication or inference probes."""
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ from mcp.types import TextContent
7
+
8
+ from clink.backend import get_backend
9
+ from tools.shared.base_models import ToolRequest
10
+ from tools.shared.base_tool import BaseTool
11
+
12
+
13
+ class ListModelsTool(BaseTool):
14
+ def get_name(self) -> str:
15
+ return "listmodels"
16
+
17
+ def get_description(self) -> str:
18
+ return "Describe configured CLIs, executable availability, roles, native model defaults and restrictions without inference."
19
+
20
+ def get_input_schema(self) -> dict[str, Any]:
21
+ return {"type": "object", "properties": {}, "additionalProperties": False}
22
+
23
+ def get_system_prompt(self) -> str:
24
+ return ""
25
+
26
+ def requires_model(self) -> bool:
27
+ return False
28
+
29
+ def get_request_model(self):
30
+ return ToolRequest
31
+
32
+ def get_annotations(self) -> dict[str, Any]:
33
+ return {"readOnlyHint": True, "openWorldHint": False}
34
+
35
+ async def prepare_prompt(self, request) -> str:
36
+ return ""
37
+
38
+ async def execute(self, arguments: dict[str, Any]) -> list[TextContent]:
39
+ from clink.native import get_native_runtime
40
+
41
+ payload = {
42
+ "status": "success",
43
+ "clients": get_backend().describe(),
44
+ "native_task_clients": get_native_runtime().describe(),
45
+ "authentication": "unknown; no login or inference probes performed",
46
+ "model_selection": "request → role → client → native CLI default; no aliases or backend fallback",
47
+ }
48
+ return [TextContent(type="text", text=json.dumps(payload, ensure_ascii=False))]