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/version.py ADDED
@@ -0,0 +1,53 @@
1
+ """Local fork metadata; this utility makes no network requests."""
2
+
3
+ import json
4
+ import platform
5
+ from typing import Any
6
+
7
+ from mcp.types import TextContent
8
+
9
+ from config import FORK_REPOSITORY, SOURCE_COMMIT, SOURCE_REPOSITORY, __author__, __updated__, __version__
10
+ from tools.shared.base_models import ToolRequest
11
+ from tools.shared.base_tool import BaseTool
12
+
13
+
14
+ class VersionTool(BaseTool):
15
+ def get_name(self) -> str:
16
+ return "version"
17
+
18
+ def get_description(self) -> str:
19
+ return "Report clink-mcp-server version, platform and PAL source provenance locally."
20
+
21
+ def get_input_schema(self) -> dict[str, Any]:
22
+ return {"type": "object", "properties": {}, "additionalProperties": False}
23
+
24
+ def get_system_prompt(self) -> str:
25
+ return ""
26
+
27
+ def requires_model(self) -> bool:
28
+ return False
29
+
30
+ def get_request_model(self):
31
+ return ToolRequest
32
+
33
+ def get_annotations(self) -> dict[str, Any]:
34
+ return {"readOnlyHint": True, "openWorldHint": False}
35
+
36
+ async def prepare_prompt(self, request) -> str:
37
+ return ""
38
+
39
+ async def execute(self, arguments: dict[str, Any]) -> list[TextContent]:
40
+ payload = {
41
+ "status": "success",
42
+ "name": "clink-mcp-server",
43
+ "version": __version__,
44
+ "updated": __updated__,
45
+ "author": __author__,
46
+ "repository": FORK_REPOSITORY,
47
+ "source": {"repository": SOURCE_REPOSITORY, "commit": SOURCE_COMMIT, "version": "9.8.2"},
48
+ "license": "Apache-2.0; copyright Beehive Innovations retained",
49
+ "platform": platform.system(),
50
+ "python": platform.python_version(),
51
+ "continuations": "process-local UUID replay; no native session resume",
52
+ }
53
+ return [TextContent(type="text", text=json.dumps(payload, ensure_ascii=False))]
@@ -0,0 +1,22 @@
1
+ """
2
+ Workflow tools for PAL MCP.
3
+
4
+ Workflow tools follow a multi-step pattern with forced pauses between steps
5
+ to encourage thorough investigation and analysis. They inherit from WorkflowTool
6
+ which combines BaseTool with BaseWorkflowMixin.
7
+
8
+ Available workflow tools:
9
+ - debug: Systematic investigation and root cause analysis
10
+ - planner: Sequential planning (special case - no AI calls)
11
+ - analyze: Code analysis workflow
12
+ - codereview: Code review workflow
13
+ - precommit: Pre-commit validation workflow
14
+ - refactor: Refactoring analysis workflow
15
+ - thinkdeep: Deep thinking workflow
16
+ """
17
+
18
+ from .base import WorkflowTool
19
+ from .schema_builders import WorkflowSchemaBuilder
20
+ from .workflow_mixin import BaseWorkflowMixin
21
+
22
+ __all__ = ["WorkflowTool", "WorkflowSchemaBuilder", "BaseWorkflowMixin"]
tools/workflow/base.py ADDED
@@ -0,0 +1,448 @@
1
+ """
2
+ Base class for workflow MCP tools.
3
+
4
+ Workflow tools follow a multi-step pattern:
5
+ 1. CLI calls tool with work step data
6
+ 2. Tool tracks findings and progress
7
+ 3. Tool forces the CLI to pause and investigate between steps
8
+ 4. Once work is complete, tool calls external AI model for expert analysis
9
+ 5. Tool returns structured response combining investigation + expert analysis
10
+
11
+ They combine BaseTool's capabilities with BaseWorkflowMixin's workflow functionality
12
+ and use SchemaBuilder for consistent schema generation.
13
+ """
14
+
15
+ from abc import abstractmethod
16
+ from typing import Any, Optional
17
+
18
+ from tools.shared.base_models import WorkflowRequest
19
+ from tools.shared.base_tool import BaseTool
20
+
21
+ from .schema_builders import WorkflowSchemaBuilder
22
+ from .workflow_mixin import BaseWorkflowMixin
23
+
24
+
25
+ class WorkflowTool(BaseTool, BaseWorkflowMixin):
26
+ """
27
+ Base class for workflow (multi-step) tools.
28
+
29
+ Workflow tools perform systematic multi-step work with expert analysis.
30
+ They benefit from:
31
+ - Automatic workflow orchestration from BaseWorkflowMixin
32
+ - Automatic schema generation using SchemaBuilder
33
+ - Inherited conversation handling and file processing from BaseTool
34
+ - Progress tracking with ConsolidatedFindings
35
+ - Expert analysis integration
36
+
37
+ To create a workflow tool:
38
+ 1. Inherit from WorkflowTool
39
+ 2. Tool name is automatically provided by get_name() method
40
+ 3. Implement get_required_actions() for step guidance
41
+ 4. Implement should_call_expert_analysis() for completion criteria
42
+ 5. Implement prepare_expert_analysis_context() for expert prompts
43
+ 6. Optionally implement get_tool_fields() for additional fields
44
+ 7. Optionally override workflow behavior methods
45
+
46
+ Example:
47
+ class DebugTool(WorkflowTool):
48
+ # get_name() is inherited from BaseTool
49
+
50
+ def get_tool_fields(self) -> Dict[str, Dict[str, Any]]:
51
+ return {
52
+ "hypothesis": {
53
+ "type": "string",
54
+ "description": "Current theory about the issue",
55
+ }
56
+ }
57
+
58
+ def get_required_actions(
59
+ self, step_number: int, confidence: str, findings: str, total_steps: int
60
+ ) -> List[str]:
61
+ return ["Examine relevant code files", "Trace execution flow", "Check error logs"]
62
+
63
+ def should_call_expert_analysis(self, consolidated_findings) -> bool:
64
+ return len(consolidated_findings.relevant_files) > 0
65
+ """
66
+
67
+ def __init__(self):
68
+ """Initialize WorkflowTool with proper multiple inheritance."""
69
+ BaseTool.__init__(self)
70
+ BaseWorkflowMixin.__init__(self)
71
+
72
+ def get_tool_fields(self) -> dict[str, dict[str, Any]]:
73
+ """
74
+ Return tool-specific field definitions beyond the standard workflow fields.
75
+
76
+ Workflow tools automatically get all standard workflow fields:
77
+ - step, step_number, total_steps, next_step_required
78
+ - findings, files_checked, relevant_files, relevant_context
79
+ - issues_found, confidence, hypothesis
80
+ - plus common fields (model, temperature, etc.)
81
+
82
+ Override this method to add additional tool-specific fields.
83
+
84
+ Returns:
85
+ Dict mapping field names to JSON schema objects
86
+
87
+ Example:
88
+ return {
89
+ "severity_filter": {
90
+ "type": "string",
91
+ "enum": ["low", "medium", "high"],
92
+ "description": "Minimum severity level to report",
93
+ }
94
+ }
95
+ """
96
+ return {}
97
+
98
+ def get_required_fields(self) -> list[str]:
99
+ """
100
+ Return additional required fields beyond the standard workflow requirements.
101
+
102
+ Workflow tools automatically require:
103
+ - step, step_number, total_steps, next_step_required, findings
104
+ - model (if in auto mode)
105
+
106
+ Override this to add additional required fields.
107
+
108
+ Returns:
109
+ List of additional required field names
110
+ """
111
+ return []
112
+
113
+ def get_annotations(self) -> Optional[dict[str, Any]]:
114
+ """
115
+ Return tool annotations. Workflow tools are read-only by default.
116
+
117
+ All workflow tools perform analysis and investigation without modifying
118
+ the environment. They may call external AI models for expert analysis,
119
+ but they don't write files or make system changes.
120
+
121
+ Override this method if your workflow tool needs different annotations.
122
+
123
+ Returns:
124
+ Dictionary with readOnlyHint set to True
125
+ """
126
+ return {"readOnlyHint": True}
127
+
128
+ def get_input_schema(self) -> dict[str, Any]:
129
+ """
130
+ Generate the complete input schema using SchemaBuilder.
131
+
132
+ This method automatically combines:
133
+ - Standard workflow fields (step, findings, etc.)
134
+ - Common fields (temperature, thinking_mode, etc.)
135
+ - Model field with proper auto-mode handling
136
+ - Tool-specific fields from get_tool_fields()
137
+ - Required fields from get_required_fields()
138
+
139
+ Returns:
140
+ Complete JSON schema for the workflow tool
141
+ """
142
+ requires_model = self.requires_model()
143
+ model_field_schema = self.get_model_field_schema() if requires_model else None
144
+ auto_mode = self.is_effective_auto_mode() if requires_model else False
145
+ return WorkflowSchemaBuilder.build_schema(
146
+ tool_specific_fields=self.get_tool_fields(),
147
+ required_fields=self.get_required_fields(),
148
+ model_field_schema=model_field_schema,
149
+ auto_mode=auto_mode,
150
+ tool_name=self.get_name(),
151
+ require_model=requires_model,
152
+ )
153
+
154
+ def get_workflow_request_model(self):
155
+ """
156
+ Return the workflow request model class.
157
+
158
+ Workflow tools use WorkflowRequest by default, which includes
159
+ all the standard workflow fields. Override this if your tool
160
+ needs a custom request model.
161
+ """
162
+ return WorkflowRequest
163
+
164
+ # Implement the abstract method from BaseWorkflowMixin
165
+ def get_work_steps(self, request) -> list[str]:
166
+ """
167
+ Default implementation - workflow tools typically don't need predefined steps.
168
+
169
+ The workflow is driven by the CLI's investigation process rather than
170
+ predefined steps. Override this if your tool needs specific step guidance.
171
+ """
172
+ return []
173
+
174
+ # Default implementations for common workflow patterns
175
+
176
+ def get_standard_required_actions(self, step_number: int, confidence: str, base_actions: list[str]) -> list[str]:
177
+ """
178
+ Helper method to generate standard required actions based on confidence and step.
179
+
180
+ This provides common patterns that most workflow tools can use:
181
+ - Early steps: broad exploration
182
+ - Low confidence: deeper investigation
183
+ - Medium/high confidence: verification and confirmation
184
+
185
+ Args:
186
+ step_number: Current step number
187
+ confidence: Current confidence level
188
+ base_actions: Tool-specific base actions
189
+
190
+ Returns:
191
+ List of required actions appropriate for the current state
192
+ """
193
+ if step_number == 1:
194
+ # Initial investigation
195
+ return [
196
+ "Search for code related to the reported issue or symptoms",
197
+ "Examine relevant files and understand the current implementation",
198
+ "Understand the project structure and locate relevant modules",
199
+ "Identify how the affected functionality is supposed to work",
200
+ ]
201
+ elif confidence in ["exploring", "low"]:
202
+ # Need deeper investigation
203
+ return base_actions + [
204
+ "Trace method calls and data flow through the system",
205
+ "Check for edge cases, boundary conditions, and assumptions in the code",
206
+ "Look for related configuration, dependencies, or external factors",
207
+ ]
208
+ elif confidence in ["medium", "high"]:
209
+ # Close to solution - need confirmation
210
+ return base_actions + [
211
+ "Examine the exact code sections where you believe the issue occurs",
212
+ "Trace the execution path that leads to the failure",
213
+ "Verify your hypothesis with concrete code evidence",
214
+ "Check for any similar patterns elsewhere in the codebase",
215
+ ]
216
+ else:
217
+ # General continued investigation
218
+ return base_actions + [
219
+ "Continue examining the code paths identified in your hypothesis",
220
+ "Gather more evidence using appropriate investigation tools",
221
+ "Test edge cases and boundary conditions",
222
+ "Look for patterns that confirm or refute your theory",
223
+ ]
224
+
225
+ def should_call_expert_analysis_default(self, consolidated_findings) -> bool:
226
+ """
227
+ Default implementation for expert analysis decision.
228
+
229
+ This provides a reasonable default that most workflow tools can use:
230
+ - Call expert analysis if we have relevant files or significant findings
231
+ - Skip if confidence is "certain" (handled by the workflow mixin)
232
+
233
+ Override this for tool-specific logic.
234
+
235
+ Args:
236
+ consolidated_findings: The consolidated findings from all work steps
237
+
238
+ Returns:
239
+ True if expert analysis should be called
240
+ """
241
+ # Call expert analysis if we have relevant files or substantial findings
242
+ return (
243
+ len(consolidated_findings.relevant_files) > 0
244
+ or len(consolidated_findings.findings) >= 2
245
+ or len(consolidated_findings.issues_found) > 0
246
+ )
247
+
248
+ def prepare_standard_expert_context(
249
+ self, consolidated_findings, initial_description: str, context_sections: dict[str, str] = None
250
+ ) -> str:
251
+ """
252
+ Helper method to prepare standard expert analysis context.
253
+
254
+ This provides a common structure that most workflow tools can use,
255
+ with the ability to add tool-specific sections.
256
+
257
+ Args:
258
+ consolidated_findings: The consolidated findings from all work steps
259
+ initial_description: Description of the initial request/issue
260
+ context_sections: Optional additional sections to include
261
+
262
+ Returns:
263
+ Formatted context string for expert analysis
264
+ """
265
+ context_parts = [f"=== ISSUE DESCRIPTION ===\n{initial_description}\n=== END DESCRIPTION ==="]
266
+
267
+ # Add work progression
268
+ if consolidated_findings.findings:
269
+ findings_text = "\n".join(consolidated_findings.findings)
270
+ context_parts.append(f"\n=== INVESTIGATION FINDINGS ===\n{findings_text}\n=== END FINDINGS ===")
271
+
272
+ # Add relevant methods if available
273
+ if consolidated_findings.relevant_context:
274
+ methods_text = "\n".join(f"- {method}" for method in consolidated_findings.relevant_context)
275
+ context_parts.append(f"\n=== RELEVANT METHODS/FUNCTIONS ===\n{methods_text}\n=== END METHODS ===")
276
+
277
+ # Add hypothesis evolution if available
278
+ if consolidated_findings.hypotheses:
279
+ hypotheses_text = "\n".join(
280
+ f"Step {h['step']} ({h['confidence']} confidence): {h['hypothesis']}"
281
+ for h in consolidated_findings.hypotheses
282
+ )
283
+ context_parts.append(f"\n=== HYPOTHESIS EVOLUTION ===\n{hypotheses_text}\n=== END HYPOTHESES ===")
284
+
285
+ # Add issues found if available
286
+ if consolidated_findings.issues_found:
287
+ issues_text = "\n".join(
288
+ f"[{issue.get('severity', 'unknown').upper()}] {issue.get('description', 'No description')}"
289
+ for issue in consolidated_findings.issues_found
290
+ )
291
+ context_parts.append(f"\n=== ISSUES IDENTIFIED ===\n{issues_text}\n=== END ISSUES ===")
292
+
293
+ # Add tool-specific sections
294
+ if context_sections:
295
+ for section_title, section_content in context_sections.items():
296
+ context_parts.append(
297
+ f"\n=== {section_title.upper()} ===\n{section_content}\n=== END {section_title.upper()} ==="
298
+ )
299
+
300
+ return "\n".join(context_parts)
301
+
302
+ def handle_completion_without_expert_analysis(
303
+ self, request, consolidated_findings, initial_description: str = None
304
+ ) -> dict[str, Any]:
305
+ """
306
+ Generic handler for completion when expert analysis is not needed.
307
+
308
+ This provides a standard response format for when the tool determines
309
+ that external expert analysis is not required. All workflow tools
310
+ can use this generic implementation or override for custom behavior.
311
+
312
+ Args:
313
+ request: The workflow request object
314
+ consolidated_findings: The consolidated findings from all work steps
315
+ initial_description: Optional initial description (defaults to request.step)
316
+
317
+ Returns:
318
+ Dictionary with completion response data
319
+ """
320
+ # Prepare work summary using inheritance hook
321
+ work_summary = self.prepare_work_summary()
322
+
323
+ return {
324
+ "status": self.get_completion_status(),
325
+ self.get_completion_data_key(): {
326
+ "initial_request": initial_description or request.step,
327
+ "steps_taken": len(consolidated_findings.findings),
328
+ "files_examined": list(consolidated_findings.files_checked),
329
+ "relevant_files": list(consolidated_findings.relevant_files),
330
+ "relevant_context": list(consolidated_findings.relevant_context),
331
+ "work_summary": work_summary,
332
+ "final_analysis": self.get_final_analysis_from_request(request),
333
+ "confidence_level": self.get_confidence_level(request),
334
+ },
335
+ "next_steps": self.get_completion_message(),
336
+ "skip_expert_analysis": True,
337
+ "expert_analysis": {
338
+ "status": self.get_skip_expert_analysis_status(),
339
+ "reason": self.get_skip_reason(),
340
+ },
341
+ }
342
+
343
+ # Inheritance hooks for customization
344
+
345
+ def prepare_work_summary(self) -> str:
346
+ """
347
+ Prepare a summary of the work performed. Override for custom summaries.
348
+ Default implementation provides a basic summary.
349
+ """
350
+ try:
351
+ return self._prepare_work_summary()
352
+ except AttributeError:
353
+ try:
354
+ return f"Completed {len(self.work_history)} work steps"
355
+ except AttributeError:
356
+ return "Completed 0 work steps"
357
+
358
+ def get_completion_status(self) -> str:
359
+ """Get the status to use when completing without expert analysis."""
360
+ return "high_confidence_completion"
361
+
362
+ def get_completion_data_key(self) -> str:
363
+ """Get the key name for completion data in the response."""
364
+ return f"complete_{self.get_name()}"
365
+
366
+ def get_final_analysis_from_request(self, request) -> Optional[str]:
367
+ """Extract final analysis from request. Override for tool-specific extraction."""
368
+ try:
369
+ return request.hypothesis
370
+ except AttributeError:
371
+ return None
372
+
373
+ def get_confidence_level(self, request) -> str:
374
+ """Get confidence level from request. Override for tool-specific logic."""
375
+ try:
376
+ return request.confidence or "high"
377
+ except AttributeError:
378
+ return "high"
379
+
380
+ def get_completion_message(self) -> str:
381
+ """Get completion message. Override for tool-specific messaging."""
382
+ return (
383
+ f"{self.get_name().capitalize()} complete with high confidence. You have identified the exact "
384
+ "analysis and solution. MANDATORY: Present the user with the results "
385
+ "and proceed with implementing the solution without requiring further "
386
+ "consultation. Focus on the precise, actionable steps needed."
387
+ )
388
+
389
+ def get_skip_reason(self) -> str:
390
+ """Get reason for skipping expert analysis. Override for tool-specific reasons."""
391
+ return f"{self.get_name()} completed with sufficient confidence"
392
+
393
+ def get_skip_expert_analysis_status(self) -> str:
394
+ """Get status for skipped expert analysis. Override for tool-specific status."""
395
+ return "skipped_by_tool_design"
396
+
397
+ def is_continuation_workflow(self, request) -> bool:
398
+ """
399
+ Check if this is a continuation workflow that should skip multi-step investigation.
400
+
401
+ When continuation_id is provided, the workflow typically continues from a previous
402
+ conversation and should go directly to expert analysis rather than starting a new
403
+ multi-step investigation.
404
+
405
+ Args:
406
+ request: The workflow request object
407
+
408
+ Returns:
409
+ True if this is a continuation that should skip multi-step workflow
410
+ """
411
+ continuation_id = self.get_request_continuation_id(request)
412
+ return bool(continuation_id)
413
+
414
+ # Abstract methods that must be implemented by specific workflow tools
415
+ # (These are inherited from BaseWorkflowMixin and must be implemented)
416
+
417
+ @abstractmethod
418
+ def get_required_actions(
419
+ self, step_number: int, confidence: str, findings: str, total_steps: int, request=None
420
+ ) -> list[str]:
421
+ """Define required actions for each work phase.
422
+
423
+ Args:
424
+ step_number: Current step number
425
+ confidence: Current confidence level
426
+ findings: Current findings text
427
+ total_steps: Total estimated steps
428
+ request: Optional request object for continuation-aware decisions
429
+
430
+ Returns:
431
+ List of required actions for the current step
432
+ """
433
+ pass
434
+
435
+ @abstractmethod
436
+ def should_call_expert_analysis(self, consolidated_findings) -> bool:
437
+ """Decide when to call external model based on tool-specific criteria"""
438
+ pass
439
+
440
+ @abstractmethod
441
+ def prepare_expert_analysis_context(self, consolidated_findings) -> str:
442
+ """Prepare context for external model call"""
443
+ pass
444
+
445
+ # Default execute method - delegates to workflow
446
+ async def execute(self, arguments: dict[str, Any]) -> list:
447
+ """Execute the workflow tool - delegates to BaseWorkflowMixin."""
448
+ return await self.execute_workflow(arguments)
@@ -0,0 +1,166 @@
1
+ """
2
+ Schema builders for workflow MCP tools.
3
+
4
+ This module provides workflow-specific schema generation functionality,
5
+ keeping workflow concerns separated from simple tool concerns.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ from ..shared.base_models import WORKFLOW_FIELD_DESCRIPTIONS
11
+ from ..shared.schema_builders import SchemaBuilder
12
+
13
+
14
+ class WorkflowSchemaBuilder:
15
+ """
16
+ Schema builder for workflow MCP tools.
17
+
18
+ This class extends the base SchemaBuilder with workflow-specific fields
19
+ and schema generation logic, maintaining separation of concerns.
20
+ """
21
+
22
+ # Workflow-specific field schemas
23
+ WORKFLOW_FIELD_SCHEMAS = {
24
+ "step": {
25
+ "type": "string",
26
+ "description": WORKFLOW_FIELD_DESCRIPTIONS["step"],
27
+ },
28
+ "step_number": {
29
+ "type": "integer",
30
+ "minimum": 1,
31
+ "description": WORKFLOW_FIELD_DESCRIPTIONS["step_number"],
32
+ },
33
+ "total_steps": {
34
+ "type": "integer",
35
+ "minimum": 1,
36
+ "description": WORKFLOW_FIELD_DESCRIPTIONS["total_steps"],
37
+ },
38
+ "next_step_required": {
39
+ "type": "boolean",
40
+ "description": WORKFLOW_FIELD_DESCRIPTIONS["next_step_required"],
41
+ },
42
+ "findings": {
43
+ "type": "string",
44
+ "description": WORKFLOW_FIELD_DESCRIPTIONS["findings"],
45
+ },
46
+ "files_checked": {
47
+ "type": "array",
48
+ "items": {"type": "string"},
49
+ "description": WORKFLOW_FIELD_DESCRIPTIONS["files_checked"],
50
+ },
51
+ "relevant_files": {
52
+ "type": "array",
53
+ "items": {"type": "string"},
54
+ "description": WORKFLOW_FIELD_DESCRIPTIONS["relevant_files"],
55
+ },
56
+ "relevant_context": {
57
+ "type": "array",
58
+ "items": {"type": "string"},
59
+ "description": WORKFLOW_FIELD_DESCRIPTIONS["relevant_context"],
60
+ },
61
+ "issues_found": {
62
+ "type": "array",
63
+ "items": {"type": "object"},
64
+ "description": WORKFLOW_FIELD_DESCRIPTIONS["issues_found"],
65
+ },
66
+ "confidence": {
67
+ "type": "string",
68
+ "enum": ["exploring", "low", "medium", "high", "very_high", "almost_certain", "certain"],
69
+ "description": WORKFLOW_FIELD_DESCRIPTIONS["confidence"],
70
+ },
71
+ "hypothesis": {
72
+ "type": "string",
73
+ "description": WORKFLOW_FIELD_DESCRIPTIONS["hypothesis"],
74
+ },
75
+ "use_assistant_model": {
76
+ "type": "boolean",
77
+ "default": True,
78
+ "description": WORKFLOW_FIELD_DESCRIPTIONS["use_assistant_model"],
79
+ },
80
+ }
81
+
82
+ @staticmethod
83
+ def build_schema(
84
+ tool_specific_fields: dict[str, dict[str, Any]] = None,
85
+ required_fields: list[str] = None,
86
+ model_field_schema: dict[str, Any] = None,
87
+ auto_mode: bool = False,
88
+ tool_name: str = None,
89
+ excluded_workflow_fields: list[str] = None,
90
+ excluded_common_fields: list[str] = None,
91
+ require_model: bool = False,
92
+ ) -> dict[str, Any]:
93
+ """
94
+ Build complete schema for workflow tools.
95
+
96
+ Args:
97
+ tool_specific_fields: Additional fields specific to the tool
98
+ required_fields: List of required field names (beyond workflow defaults)
99
+ model_field_schema: Schema for the model field
100
+ auto_mode: Whether the tool is in auto mode (affects model requirement)
101
+ tool_name: Name of the tool (for schema title)
102
+ excluded_workflow_fields: Workflow fields to exclude from schema (e.g., for planning tools)
103
+ excluded_common_fields: Common fields to exclude from schema
104
+
105
+ Returns:
106
+ Complete JSON schema for the workflow tool
107
+ """
108
+ properties = {}
109
+
110
+ # Add workflow fields first, excluding any specified fields
111
+ workflow_fields = WorkflowSchemaBuilder.WORKFLOW_FIELD_SCHEMAS.copy()
112
+ if excluded_workflow_fields:
113
+ for field in excluded_workflow_fields:
114
+ workflow_fields.pop(field, None)
115
+ properties.update(workflow_fields)
116
+
117
+ # Add common fields (temperature, thinking_mode, etc.) from base builder, excluding any specified fields
118
+ common_fields = SchemaBuilder.COMMON_FIELD_SCHEMAS.copy()
119
+ if excluded_common_fields:
120
+ for field in excluded_common_fields:
121
+ common_fields.pop(field, None)
122
+ properties.update(common_fields)
123
+
124
+ # Add model field if provided
125
+ if model_field_schema:
126
+ properties["model"] = model_field_schema
127
+
128
+ # Add tool-specific fields if provided
129
+ if tool_specific_fields:
130
+ properties.update(tool_specific_fields)
131
+
132
+ # Build required fields list - workflow tools have standard required fields
133
+ standard_required = ["step", "step_number", "total_steps", "next_step_required", "findings"]
134
+
135
+ # Filter out excluded fields from required fields
136
+ if excluded_workflow_fields:
137
+ standard_required = [field for field in standard_required if field not in excluded_workflow_fields]
138
+
139
+ required = standard_required + (required_fields or [])
140
+
141
+ # Build the complete schema
142
+ schema = {
143
+ "$schema": "http://json-schema.org/draft-07/schema#",
144
+ "type": "object",
145
+ "properties": properties,
146
+ "required": required,
147
+ "additionalProperties": False,
148
+ }
149
+
150
+ if tool_name:
151
+ schema["title"] = f"{tool_name.capitalize()}Request"
152
+
153
+ return schema
154
+
155
+ @staticmethod
156
+ def get_workflow_fields() -> dict[str, dict[str, Any]]:
157
+ """Get the standard field schemas for workflow tools."""
158
+ combined = {}
159
+ combined.update(WorkflowSchemaBuilder.WORKFLOW_FIELD_SCHEMAS)
160
+ combined.update(SchemaBuilder.COMMON_FIELD_SCHEMAS)
161
+ return combined
162
+
163
+ @staticmethod
164
+ def get_workflow_only_fields() -> dict[str, dict[str, Any]]:
165
+ """Get only the workflow-specific field schemas."""
166
+ return WorkflowSchemaBuilder.WORKFLOW_FIELD_SCHEMAS.copy()