@magic-ingredients/tiny-brain-local 0.3.10

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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/dist/core/console-logger.d.ts +30 -0
  4. package/dist/core/console-logger.d.ts.map +1 -0
  5. package/dist/core/console-logger.js +101 -0
  6. package/dist/core/console-logger.js.map +1 -0
  7. package/dist/core/file-logger.d.ts +40 -0
  8. package/dist/core/file-logger.d.ts.map +1 -0
  9. package/dist/core/file-logger.js +223 -0
  10. package/dist/core/file-logger.js.map +1 -0
  11. package/dist/core/mcp-server.d.ts +54 -0
  12. package/dist/core/mcp-server.d.ts.map +1 -0
  13. package/dist/core/mcp-server.js +295 -0
  14. package/dist/core/mcp-server.js.map +1 -0
  15. package/dist/index.d.ts +8 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +37 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/prompts/index.d.ts +39 -0
  20. package/dist/prompts/index.d.ts.map +1 -0
  21. package/dist/prompts/index.js +5 -0
  22. package/dist/prompts/index.js.map +1 -0
  23. package/dist/prompts/memory/memory.prompt.d.ts +32 -0
  24. package/dist/prompts/memory/memory.prompt.d.ts.map +1 -0
  25. package/dist/prompts/memory/memory.prompt.js +204 -0
  26. package/dist/prompts/memory/memory.prompt.js.map +1 -0
  27. package/dist/prompts/persona/persona.prompt.d.ts +27 -0
  28. package/dist/prompts/persona/persona.prompt.d.ts.map +1 -0
  29. package/dist/prompts/persona/persona.prompt.js +592 -0
  30. package/dist/prompts/persona/persona.prompt.js.map +1 -0
  31. package/dist/prompts/planning/planning.prompt.d.ts +56 -0
  32. package/dist/prompts/planning/planning.prompt.d.ts.map +1 -0
  33. package/dist/prompts/planning/planning.prompt.js +1016 -0
  34. package/dist/prompts/planning/planning.prompt.js.map +1 -0
  35. package/dist/prompts/prompt-registry.d.ts +25 -0
  36. package/dist/prompts/prompt-registry.d.ts.map +1 -0
  37. package/dist/prompts/prompt-registry.js +68 -0
  38. package/dist/prompts/prompt-registry.js.map +1 -0
  39. package/dist/prompts/thinking/thinking.prompt.d.ts +29 -0
  40. package/dist/prompts/thinking/thinking.prompt.d.ts.map +1 -0
  41. package/dist/prompts/thinking/thinking.prompt.js +171 -0
  42. package/dist/prompts/thinking/thinking.prompt.js.map +1 -0
  43. package/dist/services/UpdateService.d.ts +29 -0
  44. package/dist/services/UpdateService.d.ts.map +1 -0
  45. package/dist/services/UpdateService.js +132 -0
  46. package/dist/services/UpdateService.js.map +1 -0
  47. package/dist/services/plan-watcher.service.d.ts +143 -0
  48. package/dist/services/plan-watcher.service.d.ts.map +1 -0
  49. package/dist/services/plan-watcher.service.js +914 -0
  50. package/dist/services/plan-watcher.service.js.map +1 -0
  51. package/dist/storage/local-filesystem-adapter.d.ts +39 -0
  52. package/dist/storage/local-filesystem-adapter.d.ts.map +1 -0
  53. package/dist/storage/local-filesystem-adapter.js +208 -0
  54. package/dist/storage/local-filesystem-adapter.js.map +1 -0
  55. package/dist/storage/storage-path-builder.d.ts +14 -0
  56. package/dist/storage/storage-path-builder.d.ts.map +1 -0
  57. package/dist/storage/storage-path-builder.js +43 -0
  58. package/dist/storage/storage-path-builder.js.map +1 -0
  59. package/dist/test-setup.d.ts +2 -0
  60. package/dist/test-setup.d.ts.map +1 -0
  61. package/dist/test-setup.js +12 -0
  62. package/dist/test-setup.js.map +1 -0
  63. package/dist/tools/analyse-request/analyse-request.tool.d.ts +8 -0
  64. package/dist/tools/analyse-request/analyse-request.tool.d.ts.map +1 -0
  65. package/dist/tools/analyse-request/analyse-request.tool.js +120 -0
  66. package/dist/tools/analyse-request/analyse-request.tool.js.map +1 -0
  67. package/dist/tools/index.d.ts +69 -0
  68. package/dist/tools/index.d.ts.map +1 -0
  69. package/dist/tools/index.js +24 -0
  70. package/dist/tools/index.js.map +1 -0
  71. package/dist/tools/memory/memory.tool.d.ts +15 -0
  72. package/dist/tools/memory/memory.tool.d.ts.map +1 -0
  73. package/dist/tools/memory/memory.tool.js +110 -0
  74. package/dist/tools/memory/memory.tool.js.map +1 -0
  75. package/dist/tools/persona/as.tool.d.ts +25 -0
  76. package/dist/tools/persona/as.tool.d.ts.map +1 -0
  77. package/dist/tools/persona/as.tool.js +294 -0
  78. package/dist/tools/persona/as.tool.js.map +1 -0
  79. package/dist/tools/persona/persona.tool.d.ts +8 -0
  80. package/dist/tools/persona/persona.tool.d.ts.map +1 -0
  81. package/dist/tools/persona/persona.tool.js +193 -0
  82. package/dist/tools/persona/persona.tool.js.map +1 -0
  83. package/dist/tools/plan/plan.tool.d.ts +18 -0
  84. package/dist/tools/plan/plan.tool.d.ts.map +1 -0
  85. package/dist/tools/plan/plan.tool.js +643 -0
  86. package/dist/tools/plan/plan.tool.js.map +1 -0
  87. package/dist/tools/strategy/strategy.tool.d.ts +13 -0
  88. package/dist/tools/strategy/strategy.tool.d.ts.map +1 -0
  89. package/dist/tools/strategy/strategy.tool.js +199 -0
  90. package/dist/tools/strategy/strategy.tool.js.map +1 -0
  91. package/dist/tools/thinking/thinking.tool.d.ts +13 -0
  92. package/dist/tools/thinking/thinking.tool.d.ts.map +1 -0
  93. package/dist/tools/thinking/thinking.tool.js +226 -0
  94. package/dist/tools/thinking/thinking.tool.js.map +1 -0
  95. package/dist/tools/tool-registry.d.ts +20 -0
  96. package/dist/tools/tool-registry.d.ts.map +1 -0
  97. package/dist/tools/tool-registry.js +61 -0
  98. package/dist/tools/tool-registry.js.map +1 -0
  99. package/dist/tools/update/update.tool.d.ts +15 -0
  100. package/dist/tools/update/update.tool.d.ts.map +1 -0
  101. package/dist/tools/update/update.tool.js +86 -0
  102. package/dist/tools/update/update.tool.js.map +1 -0
  103. package/dist/tools/validate-response/validate-response.tool.d.ts +13 -0
  104. package/dist/tools/validate-response/validate-response.tool.d.ts.map +1 -0
  105. package/dist/tools/validate-response/validate-response.tool.js +142 -0
  106. package/dist/tools/validate-response/validate-response.tool.js.map +1 -0
  107. package/dist/types/request-context.d.ts +7 -0
  108. package/dist/types/request-context.d.ts.map +1 -0
  109. package/dist/types/request-context.js +7 -0
  110. package/dist/types/request-context.js.map +1 -0
  111. package/package.json +77 -0
@@ -0,0 +1,643 @@
1
+ import { z } from 'zod';
2
+ import { createSuccessResult, createErrorResult } from '../index.js';
3
+ import { PlanningService } from '@magic-ingredients/tiny-brain-core';
4
+ import { PlanWatcherService } from '../../services/plan-watcher.service.js';
5
+ const PlanArgsSchema = z.object({
6
+ operation: z.enum(['accept', 'update', 'status', 'report', 'list', 'archive', 'switch', 'watch', 'stop-watch']),
7
+ // For 'accept' operation
8
+ planName: z.string().optional(),
9
+ planDetails: z.string().optional(),
10
+ // For 'update' operation
11
+ planId: z.string().optional(),
12
+ updates: z
13
+ .object({
14
+ overview: z.string().optional(),
15
+ updateBlockers: z
16
+ .array(z.object({
17
+ description: z.string(),
18
+ severity: z.enum(['low', 'medium', 'high']),
19
+ todoId: z.string().optional(),
20
+ }))
21
+ .optional(),
22
+ addPhase: z
23
+ .object({
24
+ title: z.string(),
25
+ description: z.string(),
26
+ })
27
+ .optional(),
28
+ updatePhase: z
29
+ .object({
30
+ phaseNumber: z.number(),
31
+ status: z.enum(['pending', 'in_progress', 'completed']).optional(),
32
+ addTodo: z.string().optional(),
33
+ completeTodo: z.string().optional(),
34
+ // Bulk operations
35
+ addTodos: z.array(z.string()).optional(),
36
+ completeTodos: z.array(z.string()).optional(),
37
+ })
38
+ .optional(),
39
+ // Support for updating multiple phases
40
+ updatePhases: z
41
+ .array(z.object({
42
+ phaseNumber: z.number(),
43
+ status: z.enum(['pending', 'in_progress', 'completed']).optional(),
44
+ addTodo: z.string().optional(),
45
+ completeTodo: z.string().optional(),
46
+ // Bulk operations
47
+ addTodos: z.array(z.string()).optional(),
48
+ completeTodos: z.array(z.string()).optional(),
49
+ }))
50
+ .optional(),
51
+ })
52
+ .optional(),
53
+ // For 'status' operation
54
+ verbose: z.boolean().optional(),
55
+ // For 'report' operation
56
+ saveToFile: z.boolean().optional(),
57
+ // For 'list' operation
58
+ listStatus: z.enum(['active', 'completed', 'archived', 'all']).optional(),
59
+ // For 'archive' operation
60
+ reason: z.string().optional(),
61
+ // For 'switch' operation
62
+ // planId is reused from update operation
63
+ // Note: switchToPlan automatically unarchives if needed
64
+ // For 'watch' operation
65
+ port: z.number().optional().default(8765),
66
+ autoOpen: z.boolean().optional().default(true),
67
+ showAll: z.boolean().optional().default(false),
68
+ });
69
+ // Singleton watcher instance
70
+ let watcherInstance = null;
71
+ // For testing - clear the singleton
72
+ export function clearWatcherInstance() {
73
+ watcherInstance = null;
74
+ }
75
+ export class PlanTool {
76
+ static getToolDefinition() {
77
+ return {
78
+ name: 'plan',
79
+ description: `📋 LIVING DOCUMENT PLANNER 📋
80
+
81
+ ⚡ COLLABORATIVE PLANNING: Captures user-AI planning discussions and creates structured, trackable plans.
82
+
83
+ ✅ OPERATIONS:
84
+ • accept: Create plan from collaborative discussion (planName + planDetails)
85
+ • update: Add phases, todos, or update status (usually automatic)
86
+ • status: Show current plan progress and summary
87
+ • report: Generate and display comprehensive markdown report
88
+ • list: List all plans (active, completed, archived)
89
+ • archive: Archive a completed plan
90
+ • switch: Switch to a different plan (automatically unarchives if needed)
91
+ • watch: Launch web dashboard to monitor plans in real-time
92
+ • stop-watch: Stop the running plan watcher dashboard
93
+
94
+ 💡 WORKFLOW:
95
+ 1. User & AI collaborate on planning in chat
96
+ 2. AI invokes 'accept' with planName + full planning discussion
97
+ 3. Tool extracts phases, todos, and structure automatically
98
+ 4. Creates persona-specific JSON plan document
99
+ 5. Tracks updates and progress across conversations
100
+
101
+ 🎯 FEATURES:
102
+ • Automatic extraction of phases and todos from discussions
103
+ • Smart parsing of collaborative planning conversations
104
+ • Cross-chat plan continuity and progress tracking
105
+ • Living documents that evolve with your project
106
+
107
+ 📝 UPDATE BEST PRACTICES:
108
+ • After accepting a plan, use the returned JSON structure to make precise updates
109
+ • Match todo content exactly as shown in the structure
110
+ • Update phases based on actual progress made
111
+ • Complete todos by referencing their exact content from the plan structure
112
+ • Use bulk operations to update multiple items at once:
113
+ - completeTodos: ["todo1", "todo2"] to complete multiple todos
114
+ - addTodos: ["task1", "task2"] to add multiple todos
115
+ - updatePhases: [{...}, {...}] to update multiple phases`,
116
+ inputSchema: {
117
+ type: 'object',
118
+ properties: {
119
+ operation: {
120
+ type: 'string',
121
+ enum: ['accept', 'update', 'status', 'report', 'list', 'archive', 'switch', 'watch', 'stop-watch'],
122
+ description: 'The planning operation to perform',
123
+ },
124
+ // For 'accept' operation
125
+ planName: {
126
+ type: 'string',
127
+ description: 'Name/title for the new plan (required for accept)',
128
+ },
129
+ planDetails: {
130
+ type: 'string',
131
+ description: 'Large chunk of text containing the collaborative planning discussion between user and AI (required for accept)',
132
+ },
133
+ // For 'update' operation
134
+ planId: {
135
+ type: 'string',
136
+ description: 'Plan ID to update (optional, uses current active if not provided)',
137
+ },
138
+ updates: {
139
+ type: 'object',
140
+ properties: {
141
+ overview: { type: 'string' },
142
+ updateBlockers: {
143
+ type: 'array',
144
+ items: {
145
+ type: 'object',
146
+ properties: {
147
+ description: { type: 'string' },
148
+ severity: { type: 'string', enum: ['low', 'medium', 'high'] },
149
+ todoId: { type: 'string' },
150
+ },
151
+ required: ['description', 'severity'],
152
+ },
153
+ },
154
+ addPhase: {
155
+ type: 'object',
156
+ properties: {
157
+ title: { type: 'string' },
158
+ description: { type: 'string' },
159
+ },
160
+ },
161
+ updatePhase: {
162
+ type: 'object',
163
+ properties: {
164
+ phaseNumber: { type: 'number' },
165
+ status: { type: 'string', enum: ['pending', 'in_progress', 'completed'] },
166
+ addTodo: { type: 'string' },
167
+ completeTodo: { type: 'string' },
168
+ },
169
+ },
170
+ },
171
+ description: 'Updates to apply to the plan. Use exact todo content from plan structure for completeTodo. Update phases based on actual work completed.',
172
+ },
173
+ // For 'status' operation
174
+ verbose: {
175
+ type: 'boolean',
176
+ description: 'Show detailed todo lists in status',
177
+ },
178
+ // For 'report' operation
179
+ saveToFile: {
180
+ type: 'boolean',
181
+ description: 'Save markdown report to file (optional for report)',
182
+ },
183
+ // For 'list' operation
184
+ listStatus: {
185
+ type: 'string',
186
+ enum: ['active', 'completed', 'archived', 'all'],
187
+ description: 'Filter plans by status',
188
+ },
189
+ // For 'archive' operation
190
+ reason: {
191
+ type: 'string',
192
+ description: 'Reason for archiving',
193
+ },
194
+ // For 'switch' operation
195
+ // planId is reused from update operation
196
+ force: {
197
+ type: 'boolean',
198
+ description: 'Force switch to archived plans (optional)',
199
+ },
200
+ // For 'watch' operation
201
+ port: {
202
+ type: 'number',
203
+ description: 'Port number for the dashboard (default: 8765)',
204
+ },
205
+ autoOpen: {
206
+ type: 'boolean',
207
+ description: 'Auto-open browser when dashboard starts',
208
+ },
209
+ showAll: {
210
+ type: 'boolean',
211
+ description: 'Show all plans including archived (default: false)',
212
+ },
213
+ },
214
+ required: ['operation'],
215
+ },
216
+ };
217
+ }
218
+ static async execute(args, context) {
219
+ try {
220
+ const validatedArgs = PlanArgsSchema.parse(args);
221
+ // All plan operations except watch/stop-watch require an active persona
222
+ if (validatedArgs.operation !== 'watch' && validatedArgs.operation !== 'stop-watch' && !context.activePersona?.id) {
223
+ return createErrorResult('No active persona found. Plan operations require an active persona. Use the "as" tool to switch to a persona first.');
224
+ }
225
+ switch (validatedArgs.operation) {
226
+ case 'accept':
227
+ return await PlanTool.handleAccept(validatedArgs, context);
228
+ case 'update':
229
+ return await PlanTool.handleUpdate(validatedArgs, context);
230
+ case 'status':
231
+ return await PlanTool.handleStatus(validatedArgs, context);
232
+ case 'report':
233
+ return await PlanTool.handleReport(validatedArgs, context);
234
+ case 'list':
235
+ return await PlanTool.handleList(validatedArgs, context);
236
+ case 'archive':
237
+ return await PlanTool.handleArchive(validatedArgs, context);
238
+ case 'switch':
239
+ return await PlanTool.handleSwitch(validatedArgs, context);
240
+ case 'watch':
241
+ return await PlanTool.handleWatch(validatedArgs, context);
242
+ case 'stop-watch':
243
+ return await PlanTool.handleStopWatch(validatedArgs, context);
244
+ default:
245
+ return createErrorResult(`Unknown operation: ${validatedArgs.operation}`);
246
+ }
247
+ }
248
+ catch (error) {
249
+ if (error instanceof z.ZodError) {
250
+ return createErrorResult(`Invalid arguments: ${error.errors.map((e) => e.message).join(', ')}`);
251
+ }
252
+ return createErrorResult(error instanceof Error ? error.message : 'Unknown error occurred');
253
+ }
254
+ }
255
+ static async handleAccept(args, context) {
256
+ if (!args.planName || !args.planDetails) {
257
+ return createErrorResult('Plan name and plan details are required to accept a new plan.');
258
+ }
259
+ try {
260
+ // Create the plan using service
261
+ if (!context.activePersona) {
262
+ return createErrorResult('Active persona is required');
263
+ }
264
+ // Create context with plan input including planDetails for phase extraction
265
+ // Create service with RequestContext
266
+ const planningService = new PlanningService(context);
267
+ const plan = await planningService.createPlan({
268
+ title: args.planName,
269
+ overview: args.planDetails,
270
+ planDetails: args.planDetails, // Service will extract phases from this
271
+ });
272
+ // Set the new plan as active
273
+ const setActiveResult = await planningService.setActivePlan({ planId: plan.id });
274
+ if (!setActiveResult) {
275
+ console.warn(`Warning: Failed to set plan ${plan.id} as active for persona ${context.activePersona?.id}`);
276
+ }
277
+ // Format response using service method
278
+ const planStructureJson = await planningService.formatPlanStructure(plan);
279
+ const planStructure = JSON.parse(planStructureJson);
280
+ const response = [
281
+ `✅ Created plan: "${plan.title}"`,
282
+ `Plan ID: ${plan.id}`,
283
+ `Persona: ${context.activePersona?.id || 'unknown'}`,
284
+ '',
285
+ `Extracted ${plan.phases.length} phases:`,
286
+ ...plan.phases.map((phase, i) => `${i + 1}. ${phase.title}`),
287
+ '',
288
+ '## Plan Structure (for AI reference)',
289
+ '```json',
290
+ JSON.stringify(planStructure, null, 2),
291
+ '```',
292
+ '',
293
+ '## How to Update This Plan',
294
+ 'When calling plan update, reference the structure above to:',
295
+ '1. Complete todos by exact content match: `completeTodo: "exact todo text"`',
296
+ '2. Update phase status: `updatePhase: { phaseNumber: 1, status: "completed" }`',
297
+ '3. Add new todos: `updatePhase: { phaseNumber: 1, addTodo: "new task" }`',
298
+ '',
299
+ 'Example update calls:',
300
+ '',
301
+ '**Single operations (backward compatible):**',
302
+ '```json',
303
+ JSON.stringify({
304
+ operation: 'update',
305
+ updates: {
306
+ updatePhase: {
307
+ phaseNumber: 1,
308
+ status: 'in_progress',
309
+ completeTodo: 'First todo content from structure above',
310
+ },
311
+ },
312
+ }, null, 2),
313
+ '```',
314
+ '',
315
+ '**Bulk operations (NEW):**',
316
+ '```json',
317
+ JSON.stringify({
318
+ operation: 'update',
319
+ updates: {
320
+ updatePhase: {
321
+ phaseNumber: 1,
322
+ status: 'completed',
323
+ completeTodos: ['First todo content', 'Second todo content', 'Third todo content'],
324
+ },
325
+ },
326
+ }, null, 2),
327
+ '```',
328
+ '',
329
+ '**Multiple phase updates:**',
330
+ '```json',
331
+ JSON.stringify({
332
+ operation: 'update',
333
+ updates: {
334
+ updatePhases: [
335
+ {
336
+ phaseNumber: 1,
337
+ status: 'completed',
338
+ completeTodos: ['Todo 1', 'Todo 2'],
339
+ },
340
+ {
341
+ phaseNumber: 2,
342
+ status: 'in_progress',
343
+ addTodos: ['New task 1', 'New task 2'],
344
+ },
345
+ ],
346
+ },
347
+ }, null, 2),
348
+ '```',
349
+ '',
350
+ 'Use "plan operation=status" to see the current status.',
351
+ ].join('\n');
352
+ return createSuccessResult(response);
353
+ }
354
+ catch (error) {
355
+ return createErrorResult(`Failed to create plan: ${error instanceof Error ? error.message : 'Unknown error'}`);
356
+ }
357
+ }
358
+ static async handleUpdate(args, context) {
359
+ try {
360
+ // Get the plan to update
361
+ let plan;
362
+ // Create service with RequestContext
363
+ const planningService = new PlanningService(context);
364
+ if (args.planId) {
365
+ if (!context.activePersona) {
366
+ return createErrorResult('Active persona is required');
367
+ }
368
+ plan = await planningService.loadPlan({ planId: args.planId });
369
+ if (!plan) {
370
+ return createErrorResult(`Plan not found: ${args.planId}`);
371
+ }
372
+ }
373
+ else {
374
+ if (!context.activePersona) {
375
+ return createErrorResult('Active persona is required');
376
+ }
377
+ plan = await planningService.getActivePlan();
378
+ if (!plan) {
379
+ return createErrorResult('No active plan found. Specify a planId or accept a new plan.');
380
+ }
381
+ }
382
+ // Apply updates using service
383
+ const updatedPlan = await planningService.updatePlan({
384
+ planId: plan.id,
385
+ updates: args.updates || {},
386
+ });
387
+ // Generate status report using service
388
+ const statusReport = await planningService.generateStatusReport(updatedPlan);
389
+ const response = [
390
+ `✅ Updated plan: "${updatedPlan.title}"`,
391
+ '',
392
+ 'Plan updated successfully.',
393
+ '',
394
+ statusReport,
395
+ ].join('\n');
396
+ return createSuccessResult(response);
397
+ }
398
+ catch (error) {
399
+ return createErrorResult(`Failed to update plan: ${error instanceof Error ? error.message : 'Unknown error'}`);
400
+ }
401
+ }
402
+ static async handleStatus(args, context) {
403
+ try {
404
+ if (!context.activePersona) {
405
+ return createErrorResult('No active persona found. Please select a persona first.');
406
+ }
407
+ // For non-verbose, use the summary method for better performance
408
+ if (!args.verbose) {
409
+ // Create service with RequestContext
410
+ const planningService = new PlanningService(context);
411
+ const planSummary = await planningService.getActivePlanSummary();
412
+ if (!planSummary) {
413
+ return createErrorResult(`No active plan found for persona "${context.activePersona.id}". Use "plan operation=list" to see all plans.`);
414
+ }
415
+ // Generate status report using a temporary plan object from summary
416
+ const tempPlan = {
417
+ ...planSummary,
418
+ overview: '',
419
+ phases: [],
420
+ metadata: {
421
+ totalPhases: planSummary.currentState.overallProgress.totalPhases,
422
+ completedPhases: planSummary.currentState.overallProgress.completedPhases,
423
+ totalTodos: planSummary.currentState.overallProgress.totalTodos,
424
+ completedTodos: planSummary.currentState.overallProgress.completedTodos,
425
+ lastChanges: [],
426
+ contributors: [],
427
+ tags: [],
428
+ },
429
+ created: '',
430
+ };
431
+ const statusReport = await planningService.generateStatusReport(tempPlan);
432
+ return createSuccessResult(statusReport);
433
+ }
434
+ else {
435
+ // For verbose, load the full plan
436
+ // Create service with RequestContext
437
+ const planningService = new PlanningService(context);
438
+ const plan = await planningService.getActivePlan();
439
+ if (!plan) {
440
+ return createErrorResult(`No active plan found for persona "${context.activePersona.id}". Use "plan operation=list" to see all plans.`);
441
+ }
442
+ const formattedPlan = await planningService.formatPlan(plan, true);
443
+ return createSuccessResult(formattedPlan);
444
+ }
445
+ }
446
+ catch (error) {
447
+ return createErrorResult(`Failed to get plan status: ${error instanceof Error ? error.message : 'Unknown error'}`);
448
+ }
449
+ }
450
+ static async handleReport(args, context) {
451
+ try {
452
+ if (!context.activePersona) {
453
+ return createErrorResult('No active persona found. Please select a persona first.');
454
+ }
455
+ // Create service with RequestContext
456
+ const planningService = new PlanningService(context);
457
+ const plan = await planningService.getActivePlan();
458
+ if (!plan) {
459
+ return createErrorResult('No active plan found.');
460
+ }
461
+ const report = await planningService.formatPlan(plan, true);
462
+ if (args.saveToFile) {
463
+ // Save report to storage
464
+ const filename = `plan-report-${plan.id}-${new Date().toISOString().split('T')[0]}.md`;
465
+ const path = `reports/${filename}`;
466
+ await context.storage?.storeUserData(path, report, context.userId);
467
+ const response = [`Report saved to: ${path}`, '', report].join('\n');
468
+ return createSuccessResult(response);
469
+ }
470
+ const response = report;
471
+ return createSuccessResult(response);
472
+ }
473
+ catch (error) {
474
+ return createErrorResult(`Failed to generate report: ${error instanceof Error ? error.message : 'Unknown error'}`);
475
+ }
476
+ }
477
+ static async handleList(args, context) {
478
+ try {
479
+ if (!context.activePersona) {
480
+ return createErrorResult('No active persona found. Please select a persona first.');
481
+ }
482
+ const status = args.listStatus || 'active';
483
+ // Create service with RequestContext
484
+ const planningService = new PlanningService(context);
485
+ // Map 'completed' to 'all' since we need to load all plans to filter by status
486
+ const serviceStatus = status === 'completed' ? 'all' : status;
487
+ const allPlans = await planningService.listPlans({
488
+ type: serviceStatus === 'all' ? undefined : serviceStatus
489
+ });
490
+ // Filter by actual plan status if we're looking for completed plans
491
+ const plans = status === 'completed'
492
+ ? allPlans.filter(plan => plan.status === 'complete')
493
+ : allPlans;
494
+ if (plans.length === 0) {
495
+ return createSuccessResult(`No ${status} plans found for persona "${context.activePersona.id}".`);
496
+ }
497
+ // Get the active plan to mark it
498
+ const activePlan = await planningService.getActivePlan();
499
+ const activePlanId = activePlan?.id;
500
+ // Format the plan list with active plan indicator
501
+ const lines = [`📋 Plans for persona "${context.activePersona?.id || 'unknown'}":`];
502
+ lines.push('');
503
+ if (plans.length === 0) {
504
+ lines.push('No plans found. Create one with: plan operation="accept"');
505
+ }
506
+ else {
507
+ lines.push('Available plans:');
508
+ lines.push('');
509
+ for (const plan of plans) {
510
+ const isActive = plan.id === activePlanId;
511
+ const statusEmoji = plan.type === 'active' ? '🟢' : plan.status === 'complete' ? '✅' : '📦';
512
+ const activeMarker = isActive ? ' ⭐ [ACTIVE]' : '';
513
+ lines.push(`${statusEmoji} **${plan.title}** (${plan.id})${activeMarker}`);
514
+ lines.push(` Progress: ${plan.currentState.overallProgress.percentComplete}% - ${plan.currentState.overallProgress.completedPhases}/${plan.phases.length} phases, ${plan.currentState.overallProgress.completedTodos}/${plan.currentState.overallProgress.totalTodos} tasks`);
515
+ lines.push(` Last updated: ${new Date(plan.lastUpdated).toLocaleDateString()}`);
516
+ lines.push('');
517
+ }
518
+ }
519
+ return createSuccessResult(lines.join('\n'));
520
+ }
521
+ catch (error) {
522
+ return createErrorResult(`Failed to list plans: ${error instanceof Error ? error.message : 'Unknown error'}`);
523
+ }
524
+ }
525
+ static async handleArchive(args, context) {
526
+ try {
527
+ if (!context.activePersona) {
528
+ return createErrorResult('No active persona found. Please select a persona first.');
529
+ }
530
+ // planId is required for archive operation
531
+ if (!args.planId) {
532
+ return createErrorResult('Plan ID is required for archive operation. Use "plan operation=list" to see available plans.');
533
+ }
534
+ // Create service with RequestContext
535
+ const planningService = new PlanningService(context);
536
+ // Load the specific plan to archive
537
+ const plan = await planningService.loadPlan({ planId: args.planId });
538
+ if (!plan) {
539
+ return createErrorResult(`Plan not found: ${args.planId}. Use "plan operation=list" to see available plans.`);
540
+ }
541
+ // Archive the plan with explicit args
542
+ const archived = await planningService.archivePlan({
543
+ planId: plan.id,
544
+ reason: args.reason
545
+ });
546
+ if (!archived) {
547
+ return createErrorResult('Failed to archive plan.');
548
+ }
549
+ return createSuccessResult(`✅ Archived plan: "${plan.title}"\nReason: ${args.reason || 'Completed'}`);
550
+ }
551
+ catch (error) {
552
+ return createErrorResult(`Failed to archive plan: ${error instanceof Error ? error.message : 'Unknown error'}`);
553
+ }
554
+ }
555
+ static async handleSwitch(args, context) {
556
+ try {
557
+ if (!context.activePersona) {
558
+ return createErrorResult('No active persona found. Please select a persona first.');
559
+ }
560
+ if (!args.planId) {
561
+ return createErrorResult('Plan ID is required for switch operation.');
562
+ }
563
+ // Create service with RequestContext
564
+ const planningService = new PlanningService(context);
565
+ // Use the new switchToPlan method which handles both active and archived plans
566
+ // It will automatically unarchive if needed
567
+ const switchedPlan = await planningService.switchToPlan(args.planId);
568
+ // Generate a success message with plan summary
569
+ const state = switchedPlan.currentState;
570
+ const response = [
571
+ `✅ Switched active plan to: "${switchedPlan.title}"`,
572
+ `Plan ID: ${switchedPlan.id}`,
573
+ `Status: ${switchedPlan.status}`,
574
+ `Progress: ${state.overallProgress.percentComplete}% (${state.overallProgress.completedPhases}/${state.overallProgress.totalPhases} phases, ${state.overallProgress.completedTodos}/${state.overallProgress.totalTodos} tasks)`,
575
+ '',
576
+ 'Use "plan operation=status" to see the current status.',
577
+ ].join('\n');
578
+ return createSuccessResult(response);
579
+ }
580
+ catch (error) {
581
+ return createErrorResult(`Failed to switch plan: ${error instanceof Error ? error.message : 'Unknown error'}`);
582
+ }
583
+ }
584
+ static async handleWatch(args, context) {
585
+ try {
586
+ // Check if we have an existing watcher
587
+ if (watcherInstance) {
588
+ const isRunning = watcherInstance.isRunning();
589
+ if (isRunning) {
590
+ // Stop the old watcher before creating a new one
591
+ await watcherInstance.stop();
592
+ }
593
+ }
594
+ // Always create a new instance with the current context
595
+ // This ensures we get the latest activePersona from the MCP server
596
+ watcherInstance = new PlanWatcherService(context);
597
+ // Start the watcher with provided options
598
+ const options = {
599
+ port: args.port,
600
+ autoOpen: args.autoOpen,
601
+ showAll: args.showAll,
602
+ };
603
+ const result = await watcherInstance.start(options);
604
+ if (!result.success) {
605
+ return createErrorResult(result.error.message);
606
+ }
607
+ const { url, port, autoOpen, plansLoaded } = result.data;
608
+ const response = [
609
+ '🔍 Plan watcher started successfully!',
610
+ '',
611
+ `📊 Dashboard URL: ${url}`,
612
+ `🔌 Port: ${port}`,
613
+ `📂 Plans loaded: ${plansLoaded}`,
614
+ autoOpen ? '🌐 Browser will open automatically' : '🔗 Open the URL in your browser',
615
+ '',
616
+ 'The dashboard will auto-refresh when plans change.',
617
+ 'Use "plan operation=stop-watch" to stop the dashboard.',
618
+ ].join('\n');
619
+ return createSuccessResult(response);
620
+ }
621
+ catch (error) {
622
+ return createErrorResult(`Failed to start plan watcher: ${error instanceof Error ? error.message : 'Unknown error'}`);
623
+ }
624
+ }
625
+ static async handleStopWatch(_args, _context) {
626
+ try {
627
+ if (!watcherInstance) {
628
+ return createErrorResult('Plan watcher is not running');
629
+ }
630
+ const result = await watcherInstance.stop();
631
+ if (!result.success) {
632
+ return createErrorResult(result.error.message);
633
+ }
634
+ // Clear the singleton instance
635
+ watcherInstance = null;
636
+ return createSuccessResult('✅ Plan watcher stopped successfully');
637
+ }
638
+ catch (error) {
639
+ return createErrorResult(`Failed to stop plan watcher: ${error instanceof Error ? error.message : 'Unknown error'}`);
640
+ }
641
+ }
642
+ }
643
+ //# sourceMappingURL=plan.tool.js.map