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/planner.py ADDED
@@ -0,0 +1,533 @@
1
+ """
2
+ Interactive Sequential Planner - Break down complex tasks through step-by-step planning
3
+
4
+ This tool enables structured planning through an interactive, step-by-step process that builds
5
+ plans incrementally with the ability to revise, branch, and adapt as understanding deepens.
6
+
7
+ The planner guides users through sequential thinking with forced pauses between steps to ensure
8
+ thorough consideration of alternatives, dependencies, and strategic decisions before moving to
9
+ tactical implementation details.
10
+
11
+ Key features:
12
+ - Sequential planning with full context awareness
13
+ - Forced deep reflection for complex plans (≥5 steps) in early stages
14
+ - Branching capabilities for exploring alternative approaches
15
+ - Revision capabilities to update earlier decisions
16
+ - Dynamic step count adjustment as plans evolve
17
+ - Self-contained completion without external expert analysis
18
+
19
+ Perfect for: complex project planning, system design with unknowns, migration strategies,
20
+ architectural decisions, and breaking down large problems into manageable steps.
21
+ """
22
+
23
+ import logging
24
+ from typing import TYPE_CHECKING, Any
25
+
26
+ from pydantic import Field, field_validator
27
+
28
+ if TYPE_CHECKING:
29
+ from tools.models import ToolModelCategory
30
+
31
+ from config import TEMPERATURE_BALANCED
32
+ from systemprompts import PLANNER_PROMPT
33
+ from tools.shared.base_models import WorkflowRequest
34
+
35
+ from .workflow.base import WorkflowTool
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+ # Tool-specific field descriptions matching original planner tool
40
+ PLANNER_FIELD_DESCRIPTIONS = {
41
+ "step": (
42
+ "Planning content for this step. Step 1: describe the task, problem and scope. Later steps: capture updates, "
43
+ "revisions, branches, or open questions that shape the plan."
44
+ ),
45
+ "step_number": "Current planning step number (starts at 1).",
46
+ "total_steps": "Estimated number of planning steps; adjust as the plan evolves.",
47
+ "next_step_required": "Set true when another planning step will follow after this one.",
48
+ "is_step_revision": "Set true when you are replacing a previously recorded step.",
49
+ "revises_step_number": "Step number being replaced when revising.",
50
+ "is_branch_point": "True when this step creates a new branch to explore an alternative path.",
51
+ "branch_from_step": "If branching, the step number that this branch starts from.",
52
+ "branch_id": "Name for this branch (e.g. 'approach-A', 'migration-path').",
53
+ "more_steps_needed": "True when you now expect to add additional steps beyond the prior estimate.",
54
+ }
55
+
56
+
57
+ class PlannerRequest(WorkflowRequest):
58
+ """Request model for planner workflow tool matching original planner exactly"""
59
+
60
+ # Required fields for each planning step
61
+ step: str = Field(..., description=PLANNER_FIELD_DESCRIPTIONS["step"])
62
+ step_number: int = Field(..., description=PLANNER_FIELD_DESCRIPTIONS["step_number"])
63
+ total_steps: int = Field(..., description=PLANNER_FIELD_DESCRIPTIONS["total_steps"])
64
+ next_step_required: bool = Field(..., description=PLANNER_FIELD_DESCRIPTIONS["next_step_required"])
65
+
66
+ # Optional revision/branching fields (planning-specific)
67
+ is_step_revision: bool | None = Field(False, description=PLANNER_FIELD_DESCRIPTIONS["is_step_revision"])
68
+ revises_step_number: int | None = Field(None, description=PLANNER_FIELD_DESCRIPTIONS["revises_step_number"])
69
+ is_branch_point: bool | None = Field(False, description=PLANNER_FIELD_DESCRIPTIONS["is_branch_point"])
70
+ branch_from_step: int | None = Field(None, description=PLANNER_FIELD_DESCRIPTIONS["branch_from_step"])
71
+ branch_id: str | None = Field(None, description=PLANNER_FIELD_DESCRIPTIONS["branch_id"])
72
+ more_steps_needed: bool | None = Field(False, description=PLANNER_FIELD_DESCRIPTIONS["more_steps_needed"])
73
+
74
+ # Exclude all investigation/analysis fields that aren't relevant to planning
75
+ findings: str = Field(
76
+ default="", exclude=True, description="Not used for planning - step content serves as findings"
77
+ )
78
+ files_checked: list[str] = Field(default_factory=list, exclude=True, description="Planning doesn't examine files")
79
+ relevant_files: list[str] = Field(default_factory=list, exclude=True, description="Planning doesn't use files")
80
+ relevant_context: list[str] = Field(
81
+ default_factory=list, exclude=True, description="Planning doesn't track code context"
82
+ )
83
+ issues_found: list[dict] = Field(default_factory=list, exclude=True, description="Planning doesn't find issues")
84
+ confidence: str = Field(default="planning", exclude=True, description="Planning uses different confidence model")
85
+ hypothesis: str | None = Field(default=None, exclude=True, description="Planning doesn't use hypothesis")
86
+
87
+ # Exclude other non-planning fields
88
+ temperature: float | None = Field(default=None, exclude=True)
89
+ thinking_mode: str | None = Field(default=None, exclude=True)
90
+ use_assistant_model: bool | None = Field(default=False, exclude=True, description="Planning is self-contained")
91
+ images: list | None = Field(default=None, exclude=True, description="Planning doesn't use images")
92
+
93
+ @field_validator("step_number")
94
+ @classmethod
95
+ def validate_step_number(cls, v):
96
+ if v < 1:
97
+ raise ValueError("step_number must be at least 1")
98
+ return v
99
+
100
+ @field_validator("total_steps")
101
+ @classmethod
102
+ def validate_total_steps(cls, v):
103
+ if v < 1:
104
+ raise ValueError("total_steps must be at least 1")
105
+ return v
106
+
107
+
108
+ class PlannerTool(WorkflowTool):
109
+ """
110
+ Planner workflow tool for step-by-step planning using the workflow architecture.
111
+
112
+ This tool provides the same planning capabilities as the original planner tool
113
+ but uses the new workflow architecture for consistency with other workflow tools.
114
+ It maintains all the original functionality including:
115
+ - Sequential step-by-step planning
116
+ - Branching and revision capabilities
117
+ - Deep thinking pauses for complex plans
118
+ - Conversation memory integration
119
+ - Self-contained operation (no expert analysis)
120
+ """
121
+
122
+ def __init__(self):
123
+ super().__init__()
124
+ self.branches = {}
125
+
126
+ def get_name(self) -> str:
127
+ return "planner"
128
+
129
+ def get_description(self) -> str:
130
+ return (
131
+ "Breaks down complex tasks through interactive, sequential planning with revision and branching capabilities. "
132
+ "Use for complex project planning, system design, migration strategies, and architectural decisions. "
133
+ "Builds plans incrementally with deep reflection for complex scenarios."
134
+ )
135
+
136
+ def get_system_prompt(self) -> str:
137
+ return PLANNER_PROMPT
138
+
139
+ def get_default_temperature(self) -> float:
140
+ return TEMPERATURE_BALANCED
141
+
142
+ def get_model_category(self) -> "ToolModelCategory":
143
+ """Planner requires deep analysis and reasoning"""
144
+ from tools.models import ToolModelCategory
145
+
146
+ return ToolModelCategory.EXTENDED_REASONING
147
+
148
+ def requires_model(self) -> bool:
149
+ """
150
+ Planner tool doesn't require model resolution at the MCP boundary.
151
+
152
+ The planner is a pure data processing tool that organizes planning steps
153
+ and provides structured guidance without calling external AI models.
154
+
155
+ Returns:
156
+ bool: False - planner doesn't need AI model access
157
+ """
158
+ return False
159
+
160
+ def get_workflow_request_model(self):
161
+ """Return the planner-specific request model."""
162
+ return PlannerRequest
163
+
164
+ def get_input_schema(self) -> dict[str, Any]:
165
+ """Generate input schema for planner workflow using override pattern."""
166
+ from .workflow.schema_builders import WorkflowSchemaBuilder
167
+
168
+ # Planner tool-specific field definitions
169
+ planner_field_overrides = {
170
+ # Override standard workflow fields that need planning-specific descriptions
171
+ "step": {
172
+ "type": "string",
173
+ "description": PLANNER_FIELD_DESCRIPTIONS["step"], # Very planning-specific instructions
174
+ },
175
+ # NEW planning-specific fields (not in base workflow)
176
+ "is_step_revision": {
177
+ "type": "boolean",
178
+ "description": PLANNER_FIELD_DESCRIPTIONS["is_step_revision"],
179
+ },
180
+ "revises_step_number": {
181
+ "type": "integer",
182
+ "minimum": 1,
183
+ "description": PLANNER_FIELD_DESCRIPTIONS["revises_step_number"],
184
+ },
185
+ "is_branch_point": {
186
+ "type": "boolean",
187
+ "description": PLANNER_FIELD_DESCRIPTIONS["is_branch_point"],
188
+ },
189
+ "branch_from_step": {
190
+ "type": "integer",
191
+ "minimum": 1,
192
+ "description": PLANNER_FIELD_DESCRIPTIONS["branch_from_step"],
193
+ },
194
+ "branch_id": {
195
+ "type": "string",
196
+ "description": PLANNER_FIELD_DESCRIPTIONS["branch_id"],
197
+ },
198
+ "more_steps_needed": {
199
+ "type": "boolean",
200
+ "description": PLANNER_FIELD_DESCRIPTIONS["more_steps_needed"],
201
+ },
202
+ }
203
+
204
+ # Define excluded fields for planner workflow
205
+ excluded_workflow_fields = [
206
+ "findings", # Planning uses step content instead
207
+ "files_checked", # Planning doesn't examine files
208
+ "relevant_files", # Planning doesn't use files
209
+ "relevant_context", # Planning doesn't track code context
210
+ "issues_found", # Planning doesn't find issues
211
+ "confidence", # Planning uses different confidence model
212
+ "hypothesis", # Planning doesn't use hypothesis
213
+ ]
214
+
215
+ excluded_common_fields = [
216
+ "cli_name",
217
+ "role",
218
+ "temperature", # Planning doesn't need temperature control
219
+ "thinking_mode", # Planning doesn't need thinking mode
220
+ "images", # Planning doesn't use images
221
+ "absolute_file_paths", # Planning doesn't use file attachments
222
+ ]
223
+
224
+ # Build schema with proper field exclusion (following consensus pattern)
225
+ return WorkflowSchemaBuilder.build_schema(
226
+ tool_specific_fields=planner_field_overrides,
227
+ required_fields=[], # No additional required fields beyond workflow defaults
228
+ model_field_schema=None,
229
+ auto_mode=False,
230
+ tool_name=self.get_name(),
231
+ excluded_workflow_fields=excluded_workflow_fields,
232
+ excluded_common_fields=excluded_common_fields,
233
+ )
234
+
235
+ # ================================================================================
236
+ # Abstract Methods - Required Implementation from BaseWorkflowMixin
237
+ # ================================================================================
238
+
239
+ def get_required_actions(
240
+ self, step_number: int, confidence: str, findings: str, total_steps: int, request=None
241
+ ) -> list[str]:
242
+ """Define required actions for each planning phase."""
243
+ if step_number == 1:
244
+ # Initial planning tasks
245
+ return [
246
+ "Think deeply about the complete scope and complexity of what needs to be planned",
247
+ "Consider multiple approaches and their trade-offs",
248
+ "Identify key constraints, dependencies, and potential challenges",
249
+ "Think about stakeholders, success criteria, and critical requirements",
250
+ ]
251
+ elif step_number <= 3 and total_steps >= 5:
252
+ # Complex plan early stages - force deep thinking
253
+ if step_number == 2:
254
+ return [
255
+ "Evaluate the approach from step 1 - are there better alternatives?",
256
+ "Break down the major phases and identify critical decision points",
257
+ "Consider resource requirements and potential bottlenecks",
258
+ "Think about how different parts interconnect and affect each other",
259
+ ]
260
+ else: # step_number == 3
261
+ return [
262
+ "Validate that the emerging plan addresses the original requirements",
263
+ "Identify any gaps or assumptions that need clarification",
264
+ "Consider how to validate progress and adjust course if needed",
265
+ "Think about what the first concrete steps should be",
266
+ ]
267
+ else:
268
+ # Later steps or simple plans
269
+ return [
270
+ "Continue developing the plan with concrete, actionable steps",
271
+ "Consider implementation details and practical considerations",
272
+ "Think about how to sequence and coordinate different activities",
273
+ "Prepare for execution planning and resource allocation",
274
+ ]
275
+
276
+ def should_call_expert_analysis(self, consolidated_findings, request=None) -> bool:
277
+ """Planner is self-contained and doesn't need expert analysis."""
278
+ return False
279
+
280
+ def prepare_expert_analysis_context(self, consolidated_findings) -> str:
281
+ """Planner doesn't use expert analysis."""
282
+ return ""
283
+
284
+ def requires_expert_analysis(self) -> bool:
285
+ """Planner is self-contained like the original planner tool."""
286
+ return False
287
+
288
+ # ================================================================================
289
+ # Workflow Customization - Match Original Planner Behavior
290
+ # ================================================================================
291
+
292
+ def prepare_step_data(self, request) -> dict:
293
+ """
294
+ Prepare step data from request with planner-specific fields.
295
+ """
296
+ step_data = {
297
+ "step": request.step,
298
+ "step_number": request.step_number,
299
+ "findings": f"Planning step {request.step_number}: {request.step}", # Use step content as findings
300
+ "files_checked": [], # Planner doesn't check files
301
+ "relevant_files": [], # Planner doesn't use files
302
+ "relevant_context": [], # Planner doesn't track context like debug
303
+ "issues_found": [], # Planner doesn't track issues
304
+ "confidence": "planning", # Planning confidence is different from investigation
305
+ "hypothesis": None, # Planner doesn't use hypothesis
306
+ "images": [], # Planner doesn't use images
307
+ # Planner-specific fields
308
+ "is_step_revision": request.is_step_revision or False,
309
+ "revises_step_number": request.revises_step_number,
310
+ "is_branch_point": request.is_branch_point or False,
311
+ "branch_from_step": request.branch_from_step,
312
+ "branch_id": request.branch_id,
313
+ "more_steps_needed": request.more_steps_needed or False,
314
+ }
315
+ return step_data
316
+
317
+ def build_base_response(self, request, continuation_id: str = None) -> dict:
318
+ """
319
+ Build the base response structure with planner-specific fields.
320
+ """
321
+ # Use work_history from workflow mixin for consistent step tracking
322
+ # Add 1 to account for current step being processed
323
+ current_step_count = len(self.work_history) + 1
324
+
325
+ response_data = {
326
+ "status": f"{self.get_name()}_in_progress",
327
+ "step_number": request.step_number,
328
+ "total_steps": request.total_steps,
329
+ "next_step_required": request.next_step_required,
330
+ "step_content": request.step,
331
+ f"{self.get_name()}_status": {
332
+ "files_checked": len(self.consolidated_findings.files_checked),
333
+ "relevant_files": len(self.consolidated_findings.relevant_files),
334
+ "relevant_context": len(self.consolidated_findings.relevant_context),
335
+ "issues_found": len(self.consolidated_findings.issues_found),
336
+ "images_collected": len(self.consolidated_findings.images),
337
+ "current_confidence": self.get_request_confidence(request),
338
+ "step_history_length": current_step_count, # Use work_history + current step
339
+ },
340
+ "metadata": {
341
+ "branches": list(self.branches.keys()),
342
+ "step_history_length": current_step_count, # Use work_history + current step
343
+ "is_step_revision": request.is_step_revision or False,
344
+ "revises_step_number": request.revises_step_number,
345
+ "is_branch_point": request.is_branch_point or False,
346
+ "branch_from_step": request.branch_from_step,
347
+ "branch_id": request.branch_id,
348
+ "more_steps_needed": request.more_steps_needed or False,
349
+ },
350
+ }
351
+
352
+ if continuation_id:
353
+ response_data["continuation_id"] = continuation_id
354
+
355
+ return response_data
356
+
357
+ def handle_work_continuation(self, response_data: dict, request) -> dict:
358
+ """
359
+ Handle work continuation with planner-specific deep thinking pauses.
360
+ """
361
+ response_data["status"] = f"pause_for_{self.get_name()}"
362
+ response_data[f"{self.get_name()}_required"] = True
363
+
364
+ # Get planner-specific required actions
365
+ required_actions = self.get_required_actions(request.step_number, "planning", request.step, request.total_steps)
366
+ response_data["required_actions"] = required_actions
367
+
368
+ # Enhanced deep thinking pauses for complex plans
369
+ if request.total_steps >= 5 and request.step_number <= 3:
370
+ response_data["status"] = "pause_for_deep_thinking"
371
+ response_data["thinking_required"] = True
372
+ response_data["required_thinking"] = required_actions
373
+
374
+ if request.step_number == 1:
375
+ response_data["next_steps"] = (
376
+ f"MANDATORY: DO NOT call the {self.get_name()} tool again immediately. This is a complex plan ({request.total_steps} steps) "
377
+ f"that requires deep thinking. You MUST first spend time reflecting on the planning challenge:\n\n"
378
+ f"REQUIRED DEEP THINKING before calling {self.get_name()} step {request.step_number + 1}:\n"
379
+ f"1. Analyze the FULL SCOPE: What exactly needs to be accomplished?\n"
380
+ f"2. Consider MULTIPLE APPROACHES: What are 2-3 different ways to tackle this?\n"
381
+ f"3. Identify CONSTRAINTS & DEPENDENCIES: What limits our options?\n"
382
+ f"4. Think about SUCCESS CRITERIA: How will we know we've succeeded?\n"
383
+ f"5. Consider RISKS & MITIGATION: What could go wrong early vs late?\n\n"
384
+ f"Only call {self.get_name()} again with step_number: {request.step_number + 1} AFTER this deep analysis."
385
+ )
386
+ elif request.step_number == 2:
387
+ response_data["next_steps"] = (
388
+ f"STOP! Complex planning requires reflection between steps. DO NOT call {self.get_name()} immediately.\n\n"
389
+ f"MANDATORY REFLECTION before {self.get_name()} step {request.step_number + 1}:\n"
390
+ f"1. EVALUATE YOUR APPROACH: Is the direction from step 1 still the best?\n"
391
+ f"2. IDENTIFY MAJOR PHASES: What are the 3-5 main chunks of work?\n"
392
+ f"3. SPOT DEPENDENCIES: What must happen before what?\n"
393
+ f"4. CONSIDER RESOURCES: What skills, tools, or access do we need?\n"
394
+ f"5. FIND CRITICAL PATHS: Where could delays hurt the most?\n\n"
395
+ f"Think deeply about these aspects, then call {self.get_name()} with step_number: {request.step_number + 1}."
396
+ )
397
+ elif request.step_number == 3:
398
+ response_data["next_steps"] = (
399
+ f"PAUSE for final strategic reflection. DO NOT call {self.get_name()} yet.\n\n"
400
+ f"FINAL DEEP THINKING before {self.get_name()} step {request.step_number + 1}:\n"
401
+ f"1. VALIDATE COMPLETENESS: Does this plan address all original requirements?\n"
402
+ f"2. CHECK FOR GAPS: What assumptions need validation? What's unclear?\n"
403
+ f"3. PLAN FOR ADAPTATION: How will we know if we need to change course?\n"
404
+ f"4. DEFINE FIRST STEPS: What are the first 2-3 concrete actions?\n"
405
+ f"5. TRANSITION MINDSET: Ready to shift from strategic to tactical planning?\n\n"
406
+ f"After this reflection, call {self.get_name()} with step_number: {request.step_number + 1} to continue with tactical details."
407
+ )
408
+ else:
409
+ # Normal flow for simple plans or later steps
410
+ remaining_steps = request.total_steps - request.step_number
411
+ response_data["next_steps"] = (
412
+ f"Continue with step {request.step_number + 1}. Approximately {remaining_steps} steps remaining."
413
+ )
414
+
415
+ return response_data
416
+
417
+ def customize_workflow_response(self, response_data: dict, request) -> dict:
418
+ """
419
+ Customize response to match original planner tool format.
420
+ """
421
+ # No need to append to step_history since workflow mixin already manages work_history
422
+ # and we calculate step counts from work_history
423
+
424
+ # Handle branching like original planner
425
+ if request.is_branch_point and request.branch_from_step and request.branch_id:
426
+ if request.branch_id not in self.branches:
427
+ self.branches[request.branch_id] = []
428
+ step_data = self.prepare_step_data(request)
429
+ self.branches[request.branch_id].append(step_data)
430
+
431
+ # Ensure metadata exists and preserve existing metadata from build_base_response
432
+ if "metadata" not in response_data:
433
+ response_data["metadata"] = {}
434
+
435
+ # Store planner-specific metadata that should persist through workflow metadata addition
436
+ planner_metadata = {
437
+ "branches": list(self.branches.keys()),
438
+ "is_step_revision": request.is_step_revision or False,
439
+ "revises_step_number": request.revises_step_number,
440
+ "is_branch_point": request.is_branch_point or False,
441
+ "branch_from_step": request.branch_from_step,
442
+ "branch_id": request.branch_id,
443
+ "more_steps_needed": request.more_steps_needed or False,
444
+ }
445
+
446
+ # Update metadata while preserving existing values
447
+ response_data["metadata"].update(planner_metadata)
448
+
449
+ # Add planner-specific output instructions for final steps
450
+ if not request.next_step_required:
451
+ response_data["planning_complete"] = True
452
+ response_data["plan_summary"] = (
453
+ f"COMPLETE PLAN: {request.step} (Total {request.total_steps} steps completed)"
454
+ )
455
+ response_data["output"] = {
456
+ "instructions": "This is a structured planning response. Present the step_content as the main planning analysis. If next_step_required is true, continue with the next step. If planning_complete is true, present the complete plan in a well-structured format with clear sections, headings, numbered steps, and visual elements like ASCII charts for phases/dependencies. Use bullet points, sub-steps, sequences, and visual organization to make complex plans easy to understand and follow. IMPORTANT: Do NOT use emojis - use clear text formatting and ASCII characters only. Do NOT mention time estimates or costs unless explicitly requested.",
457
+ "format": "step_by_step_planning",
458
+ "presentation_guidelines": {
459
+ "completed_plans": "Use clear headings, numbered phases, ASCII diagrams for workflows/dependencies, bullet points for sub-tasks, and visual sequences where helpful. No emojis. No time/cost estimates unless requested.",
460
+ "step_content": "Present as main analysis with clear structure and actionable insights. No emojis. No time/cost estimates unless requested.",
461
+ "continuation": "Use continuation_id for related planning sessions or implementation planning",
462
+ },
463
+ }
464
+ response_data["next_steps"] = (
465
+ "Planning complete. Present the complete plan to the user in a well-structured format with clear sections, "
466
+ "numbered steps, visual elements (ASCII charts/diagrams where helpful), sub-step breakdowns, and implementation guidance. "
467
+ "Use headings, bullet points, and visual organization to make the plan easy to follow. "
468
+ "If there are phases, dependencies, or parallel tracks, show these relationships visually. "
469
+ "IMPORTANT: Do NOT use emojis - use clear text formatting and ASCII characters only. "
470
+ "Do NOT mention time estimates or costs unless explicitly requested. "
471
+ "After presenting the plan, offer to either help implement specific parts or use the continuation_id to start related planning sessions."
472
+ )
473
+
474
+ # Convert generic status names to planner-specific ones
475
+ tool_name = self.get_name()
476
+ status_mapping = {
477
+ f"{tool_name}_in_progress": "planning_in_progress",
478
+ f"pause_for_{tool_name}": "pause_for_planning",
479
+ f"{tool_name}_required": "planning_required",
480
+ f"{tool_name}_complete": "planning_complete",
481
+ }
482
+
483
+ if response_data["status"] in status_mapping:
484
+ response_data["status"] = status_mapping[response_data["status"]]
485
+
486
+ return response_data
487
+
488
+ # ================================================================================
489
+ # Hook Method Overrides for Planner-Specific Behavior
490
+ # ================================================================================
491
+
492
+ def get_completion_status(self) -> str:
493
+ """Planner uses planning-specific status."""
494
+ return "planning_complete"
495
+
496
+ def get_completion_data_key(self) -> str:
497
+ """Planner uses 'complete_planning' key."""
498
+ return "complete_planning"
499
+
500
+ def get_completion_message(self) -> str:
501
+ """Planner-specific completion message."""
502
+ return (
503
+ "Planning complete. Present the complete plan to the user in a well-structured format "
504
+ "and offer to help implement specific parts or start related planning sessions."
505
+ )
506
+
507
+ def get_skip_reason(self) -> str:
508
+ """Planner-specific skip reason."""
509
+ return "Planner is self-contained and completes planning without external analysis"
510
+
511
+ def get_skip_expert_analysis_status(self) -> str:
512
+ """Planner-specific expert analysis skip status."""
513
+ return "skipped_by_tool_design"
514
+
515
+ def store_initial_issue(self, step_description: str):
516
+ """Store initial planning description."""
517
+ self.initial_planning_description = step_description
518
+
519
+ def get_initial_request(self, fallback_step: str) -> str:
520
+ """Get initial planning description."""
521
+ try:
522
+ return self.initial_planning_description
523
+ except AttributeError:
524
+ return fallback_step
525
+
526
+ # Required abstract methods from BaseTool
527
+ def get_request_model(self):
528
+ """Return the planner-specific request model."""
529
+ return PlannerRequest
530
+
531
+ async def prepare_prompt(self, request) -> str:
532
+ """Not used - workflow tools use execute_workflow()."""
533
+ return "" # Workflow tools use execute_workflow() directly