nightshift-mcp 2.0.1 → 2.1.0

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 (49) hide show
  1. package/dist/agent-spawner.d.ts +9 -0
  2. package/dist/agent-spawner.d.ts.map +1 -1
  3. package/dist/agent-spawner.js +88 -5
  4. package/dist/agent-spawner.js.map +1 -1
  5. package/dist/daemon.d.ts +19 -5
  6. package/dist/daemon.d.ts.map +1 -1
  7. package/dist/daemon.js +212 -35
  8. package/dist/daemon.js.map +1 -1
  9. package/dist/database.d.ts +2 -2
  10. package/dist/database.d.ts.map +1 -1
  11. package/dist/database.js +4 -4
  12. package/dist/database.js.map +1 -1
  13. package/dist/index.js +219 -1146
  14. package/dist/index.js.map +1 -1
  15. package/dist/orchestrator.d.ts +10 -1
  16. package/dist/orchestrator.d.ts.map +1 -1
  17. package/dist/orchestrator.js +190 -8
  18. package/dist/orchestrator.js.map +1 -1
  19. package/dist/pipeline.d.ts +61 -0
  20. package/dist/pipeline.d.ts.map +1 -0
  21. package/dist/pipeline.js +227 -0
  22. package/dist/pipeline.js.map +1 -0
  23. package/dist/project-context.d.ts +28 -0
  24. package/dist/project-context.d.ts.map +1 -0
  25. package/dist/project-context.js +45 -0
  26. package/dist/project-context.js.map +1 -0
  27. package/dist/run-logger.d.ts +61 -0
  28. package/dist/run-logger.d.ts.map +1 -0
  29. package/dist/run-logger.js +219 -0
  30. package/dist/run-logger.js.map +1 -0
  31. package/dist/tools/agents.d.ts +3 -0
  32. package/dist/tools/agents.d.ts.map +1 -1
  33. package/dist/tools/agents.js +148 -5
  34. package/dist/tools/agents.js.map +1 -1
  35. package/dist/tools/bugs.d.ts.map +1 -1
  36. package/dist/tools/bugs.js +48 -22
  37. package/dist/tools/bugs.js.map +1 -1
  38. package/dist/tools/chat.d.ts.map +1 -1
  39. package/dist/tools/chat.js +47 -20
  40. package/dist/tools/chat.js.map +1 -1
  41. package/dist/tools/prd.d.ts.map +1 -1
  42. package/dist/tools/prd.js +67 -31
  43. package/dist/tools/prd.js.map +1 -1
  44. package/dist/tools/progress.d.ts.map +1 -1
  45. package/dist/tools/progress.js +22 -7
  46. package/dist/tools/progress.js.map +1 -1
  47. package/dist/tools/utility.js +2 -2
  48. package/dist/tools/utility.js.map +1 -1
  49. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,45 +4,58 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
  import { z } from "zod";
5
5
  import * as fs from "fs";
6
6
  import * as path from "path";
7
- import { ChatManager } from "./chat-manager.js";
8
- import { RalphManager } from "./ralph-manager.js";
9
- import { WorkflowManager } from "./workflow-manager.js";
10
- import { ContextStore } from "./context-store.js";
11
- import { TraceManager } from "./trace-manager.js";
12
7
  import { spawnAgent, spawnAgentBackground, getAvailableAgents, getAgentStatus, initAgentRegistry, } from "./agent-spawner.js";
13
8
  import { toolRegistry } from "./tool-registry.js";
14
9
  import { allTools, toolCounts } from "./tools/index.js";
10
+ import { createProjectManagers, getProjectFilesReport, validateProjectPath, } from "./project-context.js";
15
11
  // Get project path from environment, command line argument, or default to current directory
16
- const PROJECT_PATH = process.env.ROBOT_CHAT_PROJECT_PATH || process.argv[2] || process.cwd();
12
+ let PROJECT_PATH = process.env.ROBOT_CHAT_PROJECT_PATH || process.argv[2] || process.cwd();
17
13
  /**
18
14
  * Tool registration mode:
19
- * - 'minimal': Only nightshift + nightshift_help (2 tools, ~200 tokens)
20
- * - 'hybrid': Meta-tools + individual tools (41 tools) - default for backwards compat
21
- * - 'legacy': Only individual tools (39 tools, ~2,300 tokens)
15
+ * - 'minimal': Meta-tools + set_project_path (3 tools, ~250 tokens)
16
+ * - 'hybrid': Meta-tools + individual tools + set_project_path - default
17
+ * - 'legacy': Individual tools + set_project_path
22
18
  */
23
19
  const REGISTRATION_MODE = (process.env.NIGHTSHIFT_TOOLS || "hybrid");
24
- const chatManager = new ChatManager(PROJECT_PATH);
25
- const ralphManager = new RalphManager(PROJECT_PATH);
26
- const workflowManager = new WorkflowManager(PROJECT_PATH);
27
- // Initialize agent registry for cross-session persistence
28
- initAgentRegistry(PROJECT_PATH);
29
- const contextStore = new ContextStore(PROJECT_PATH);
30
- const traceManager = new TraceManager(PROJECT_PATH);
20
+ let chatManager;
21
+ let ralphManager;
22
+ let workflowManager;
23
+ let contextStore;
24
+ let traceManager;
31
25
  // Tool execution context
32
- const toolContext = {
33
- chatManager,
34
- ralphManager,
35
- workflowManager,
36
- contextStore,
37
- traceManager,
38
- projectPath: PROJECT_PATH,
39
- };
26
+ const toolContext = {};
27
+ function syncToolContext() {
28
+ toolContext.chatManager = chatManager;
29
+ toolContext.ralphManager = ralphManager;
30
+ toolContext.workflowManager = workflowManager;
31
+ toolContext.contextStore = contextStore;
32
+ toolContext.traceManager = traceManager;
33
+ toolContext.projectPath = PROJECT_PATH;
34
+ }
35
+ function reinitializeProjectState(projectPath) {
36
+ const resolvedPath = validateProjectPath(projectPath);
37
+ const files = getProjectFilesReport(resolvedPath);
38
+ const managers = createProjectManagers(resolvedPath);
39
+ PROJECT_PATH = managers.projectPath;
40
+ chatManager = managers.chatManager;
41
+ ralphManager = managers.ralphManager;
42
+ workflowManager = managers.workflowManager;
43
+ contextStore = managers.contextStore;
44
+ traceManager = managers.traceManager;
45
+ initAgentRegistry(PROJECT_PATH);
46
+ syncToolContext();
47
+ return {
48
+ projectPath: PROJECT_PATH,
49
+ files,
50
+ };
51
+ }
52
+ reinitializeProjectState(PROJECT_PATH);
40
53
  // Register all tools with the registry
41
54
  toolRegistry.registerAll(allTools);
42
55
  // Create MCP server
43
56
  const server = new McpServer({
44
57
  name: "nightshift",
45
- version: "1.0.0",
58
+ version: "2.1.0",
46
59
  });
47
60
  // Valid message types for schema validation
48
61
  const MESSAGE_TYPES = [
@@ -89,6 +102,38 @@ function registerMetaTools() {
89
102
  };
90
103
  });
91
104
  }
105
+ function registerProjectPathTool() {
106
+ server.tool("set_project_path", "Set the active project path for this MCP server session and reinitialize all project managers.", {
107
+ projectPath: z.string().describe("Absolute or relative path to the project directory"),
108
+ }, async ({ projectPath }) => {
109
+ try {
110
+ const result = reinitializeProjectState(projectPath);
111
+ return {
112
+ content: [
113
+ {
114
+ type: "text",
115
+ text: JSON.stringify({
116
+ message: "Project path updated successfully.",
117
+ projectPath: result.projectPath,
118
+ files: result.files,
119
+ }, null, 2),
120
+ },
121
+ ],
122
+ };
123
+ }
124
+ catch (error) {
125
+ return {
126
+ content: [
127
+ {
128
+ type: "text",
129
+ text: `Error setting project path: ${error instanceof Error ? error.message : String(error)}`,
130
+ },
131
+ ],
132
+ isError: true,
133
+ };
134
+ }
135
+ });
136
+ }
92
137
  // ============================================
93
138
  // Individual Tool Registration (Legacy)
94
139
  // ============================================
@@ -111,50 +156,12 @@ function registerIndividualTools() {
111
156
  .enum(MESSAGE_TYPES)
112
157
  .optional()
113
158
  .describe("Filter messages by type"),
114
- }, async ({ limit, agent, type }) => {
115
- try {
116
- const messages = chatManager.readMessages({
117
- limit: limit ?? 20,
118
- agent,
119
- type: type,
120
- });
121
- if (messages.length === 0) {
122
- return {
123
- content: [
124
- {
125
- type: "text",
126
- text: "No messages found in robot chat.",
127
- },
128
- ],
129
- };
130
- }
131
- const formatted = messages.map((m) => ({
132
- agent: m.agent,
133
- timestamp: m.timestamp,
134
- type: m.type,
135
- content: m.content,
136
- lineNumber: m.lineNumber,
137
- }));
138
- return {
139
- content: [
140
- {
141
- type: "text",
142
- text: JSON.stringify(formatted, null, 2),
143
- },
144
- ],
145
- };
146
- }
147
- catch (error) {
148
- return {
149
- content: [
150
- {
151
- type: "text",
152
- text: `Error reading chat: ${error instanceof Error ? error.message : String(error)}`,
153
- },
154
- ],
155
- isError: true,
156
- };
157
- }
159
+ projectPath: z
160
+ .string()
161
+ .optional()
162
+ .describe("Optional project path override for this call only"),
163
+ }, async ({ limit, agent, type, projectPath }) => {
164
+ return toolRegistry.execute("read_robot_chat", { limit, agent, type, projectPath }, toolContext);
158
165
  });
159
166
  // Tool: write_robot_chat
160
167
  server.tool("write_robot_chat", "Write a message to the robot chat file. The message will be formatted with your agent name, current timestamp, and message type.", {
@@ -167,76 +174,18 @@ function registerIndividualTools() {
167
174
  content: z
168
175
  .string()
169
176
  .describe("Message content. For FAILOVER_NEEDED, include: Status, Current Task, Progress, Files Modified."),
170
- }, async ({ agent, type, content }) => {
171
- try {
172
- const message = await chatManager.writeMessage({
173
- agent,
174
- type: type,
175
- content,
176
- });
177
- return {
178
- content: [
179
- {
180
- type: "text",
181
- text: `Message written successfully:\n\n${message.raw}`,
182
- },
183
- ],
184
- };
185
- }
186
- catch (error) {
187
- return {
188
- content: [
189
- {
190
- type: "text",
191
- text: `Error writing message: ${error instanceof Error ? error.message : String(error)}`,
192
- },
193
- ],
194
- isError: true,
195
- };
196
- }
177
+ projectPath: z
178
+ .string()
179
+ .optional()
180
+ .describe("Optional project path override for this call only"),
181
+ }, async ({ agent, type, content, projectPath }) => {
182
+ return toolRegistry.execute("write_robot_chat", { agent, type, content, projectPath }, toolContext);
197
183
  });
198
184
  // Tool: check_failovers
199
- server.tool("check_failovers", "Find unclaimed FAILOVER_NEEDED messages. Use this to see if another agent needs help continuing a task.", {}, async () => {
200
- try {
201
- const failovers = chatManager.findUnclaimedFailovers();
202
- if (failovers.length === 0) {
203
- return {
204
- content: [
205
- {
206
- type: "text",
207
- text: "No unclaimed failovers found.",
208
- },
209
- ],
210
- };
211
- }
212
- const formatted = failovers.map((f) => ({
213
- requestingAgent: f.requestingAgent,
214
- timestamp: f.message.timestamp,
215
- task: f.task || "Not specified",
216
- progress: f.progress || "Not specified",
217
- filesModified: f.filesModified || [],
218
- fullContent: f.message.content,
219
- }));
220
- return {
221
- content: [
222
- {
223
- type: "text",
224
- text: `Found ${failovers.length} unclaimed failover(s):\n\n${JSON.stringify(formatted, null, 2)}`,
225
- },
226
- ],
227
- };
228
- }
229
- catch (error) {
230
- return {
231
- content: [
232
- {
233
- type: "text",
234
- text: `Error checking failovers: ${error instanceof Error ? error.message : String(error)}`,
235
- },
236
- ],
237
- isError: true,
238
- };
239
- }
185
+ server.tool("check_failovers", "Find unclaimed FAILOVER_NEEDED messages. Use this to see if another agent needs help continuing a task.", {
186
+ projectPath: z.string().optional().describe("Optional project path override for this call only"),
187
+ }, async ({ projectPath }) => {
188
+ return toolRegistry.execute("check_failovers", { projectPath }, toolContext);
240
189
  });
241
190
  // Tool: claim_failover
242
191
  server.tool("claim_failover", "Claim a failover request by posting a FAILOVER_CLAIMED message. Use this after checking for failovers with check_failovers.", {
@@ -250,89 +199,24 @@ function registerIndividualTools() {
250
199
  .string()
251
200
  .optional()
252
201
  .describe("Description of the task you're continuing (optional)"),
253
- }, async ({ agent, originalAgent, task }) => {
254
- try {
255
- const message = await chatManager.claimFailover(agent, originalAgent, task);
256
- return {
257
- content: [
258
- {
259
- type: "text",
260
- text: `Failover claimed successfully:\n\n${message.raw}`,
261
- },
262
- ],
263
- };
264
- }
265
- catch (error) {
266
- return {
267
- content: [
268
- {
269
- type: "text",
270
- text: `Error claiming failover: ${error instanceof Error ? error.message : String(error)}`,
271
- },
272
- ],
273
- isError: true,
274
- };
275
- }
202
+ projectPath: z
203
+ .string()
204
+ .optional()
205
+ .describe("Optional project path override for this call only"),
206
+ }, async ({ agent, originalAgent, task, projectPath }) => {
207
+ return toolRegistry.execute("claim_failover", { agent, originalAgent, task, projectPath }, toolContext);
276
208
  });
277
209
  // Tool: get_chat_path
278
- server.tool("get_chat_path", "Get the path to the robot chat file. Useful for debugging or manual inspection.", {}, async () => {
279
- try {
280
- const chatPath = chatManager.getChatFilePath();
281
- return {
282
- content: [
283
- {
284
- type: "text",
285
- text: `Robot chat file: ${chatPath}`,
286
- },
287
- ],
288
- };
289
- }
290
- catch (error) {
291
- return {
292
- content: [
293
- {
294
- type: "text",
295
- text: `Error getting chat path: ${error instanceof Error ? error.message : String(error)}`,
296
- },
297
- ],
298
- isError: true,
299
- };
300
- }
210
+ server.tool("get_chat_path", "Get the path to the robot chat file. Useful for debugging or manual inspection.", {
211
+ projectPath: z.string().optional().describe("Optional project path override for this call only"),
212
+ }, async ({ projectPath }) => {
213
+ return toolRegistry.execute("get_chat_path", { projectPath }, toolContext);
301
214
  });
302
215
  // Tool: list_agents
303
- server.tool("list_agents", "List all agents who have posted to the chat, with their last activity time and message count.", {}, async () => {
304
- try {
305
- const agents = chatManager.listAgents();
306
- if (agents.length === 0) {
307
- return {
308
- content: [
309
- {
310
- type: "text",
311
- text: "No agents have posted yet.",
312
- },
313
- ],
314
- };
315
- }
316
- return {
317
- content: [
318
- {
319
- type: "text",
320
- text: JSON.stringify(agents, null, 2),
321
- },
322
- ],
323
- };
324
- }
325
- catch (error) {
326
- return {
327
- content: [
328
- {
329
- type: "text",
330
- text: `Error listing agents: ${error instanceof Error ? error.message : String(error)}`,
331
- },
332
- ],
333
- isError: true,
334
- };
335
- }
216
+ server.tool("list_agents", "List all agents who have posted to the chat, with their last activity time and message count.", {
217
+ projectPath: z.string().optional().describe("Optional project path override for this call only"),
218
+ }, async ({ projectPath }) => {
219
+ return toolRegistry.execute("list_agents", { projectPath }, toolContext);
336
220
  });
337
221
  // Tool: watch_chat
338
222
  server.tool("watch_chat", "Get new messages since a specific cursor (line number). Use this for polling/watching the chat. Returns new messages and the updated cursor.", {
@@ -340,56 +224,12 @@ function registerIndividualTools() {
340
224
  .number()
341
225
  .optional()
342
226
  .describe("Line number cursor from previous watch call. Omit to get current cursor without messages."),
343
- }, async ({ cursor }) => {
344
- try {
345
- const currentCursor = chatManager.getLastLineNumber();
346
- // If no cursor provided, just return current cursor position
347
- if (cursor === undefined) {
348
- return {
349
- content: [
350
- {
351
- type: "text",
352
- text: JSON.stringify({
353
- cursor: currentCursor,
354
- messages: [],
355
- info: "Use this cursor value in your next watch_chat call to get new messages.",
356
- }, null, 2),
357
- },
358
- ],
359
- };
360
- }
361
- const newMessages = chatManager.getMessagesSince(cursor);
362
- const formatted = newMessages.map((m) => ({
363
- agent: m.agent,
364
- timestamp: m.timestamp,
365
- type: m.type,
366
- content: m.content,
367
- lineNumber: m.lineNumber,
368
- }));
369
- return {
370
- content: [
371
- {
372
- type: "text",
373
- text: JSON.stringify({
374
- cursor: currentCursor,
375
- messageCount: newMessages.length,
376
- messages: formatted,
377
- }, null, 2),
378
- },
379
- ],
380
- };
381
- }
382
- catch (error) {
383
- return {
384
- content: [
385
- {
386
- type: "text",
387
- text: `Error watching chat: ${error instanceof Error ? error.message : String(error)}`,
388
- },
389
- ],
390
- isError: true,
391
- };
392
- }
227
+ projectPath: z
228
+ .string()
229
+ .optional()
230
+ .describe("Optional project path override for this call only"),
231
+ }, async ({ cursor, projectPath }) => {
232
+ return toolRegistry.execute("watch_chat", { cursor, projectPath }, toolContext);
393
233
  });
394
234
  // Tool: archive_chat
395
235
  server.tool("archive_chat", "Archive old messages to a separate file, keeping only recent messages in the main chat. Useful for managing chat file size.", {
@@ -397,39 +237,12 @@ function registerIndividualTools() {
397
237
  .number()
398
238
  .optional()
399
239
  .describe("Number of recent messages to keep in main chat (default: 50)"),
400
- }, async ({ keepRecent }) => {
401
- try {
402
- const result = await chatManager.archiveMessages(keepRecent ?? 50);
403
- if (result.archived === 0) {
404
- return {
405
- content: [
406
- {
407
- type: "text",
408
- text: "No messages to archive. Chat file is small enough.",
409
- },
410
- ],
411
- };
412
- }
413
- return {
414
- content: [
415
- {
416
- type: "text",
417
- text: `Archived ${result.archived} messages to: ${result.archiveFile}`,
418
- },
419
- ],
420
- };
421
- }
422
- catch (error) {
423
- return {
424
- content: [
425
- {
426
- type: "text",
427
- text: `Error archiving chat: ${error instanceof Error ? error.message : String(error)}`,
428
- },
429
- ],
430
- isError: true,
431
- };
432
- }
240
+ projectPath: z
241
+ .string()
242
+ .optional()
243
+ .describe("Optional project path override for this call only"),
244
+ }, async ({ keepRecent, projectPath }) => {
245
+ return toolRegistry.execute("archive_chat", { keepRecent, projectPath }, toolContext);
433
246
  });
434
247
  // ============================================
435
248
  // Agent Spawning Tools
@@ -482,236 +295,34 @@ function registerIndividualTools() {
482
295
  .number()
483
296
  .optional()
484
297
  .describe("Timeout in seconds (default: 300 = 5 minutes)"),
485
- }, async ({ agent, prompt, timeout }) => {
486
- try {
487
- // Post to chat that we're spawning an agent
488
- chatManager.writeMessage({
489
- agent: "NightShift",
490
- type: "INFO",
491
- content: `Spawning ${agent} agent to work on task...`,
492
- });
493
- const result = await spawnAgent({
494
- agent: agent,
495
- prompt,
496
- projectPath: PROJECT_PATH,
497
- timeout: timeout ? timeout * 1000 : undefined,
498
- });
499
- // Post result to chat
500
- chatManager.writeMessage({
501
- agent: "NightShift",
502
- type: result.success ? "INFO" : "ERROR",
503
- content: `${agent} agent ${result.success ? "completed" : "failed"}: ${result.output.substring(0, 500)}${result.output.length > 500 ? "..." : ""}`,
504
- });
505
- return {
506
- content: [
507
- {
508
- type: "text",
509
- text: JSON.stringify({
510
- success: result.success,
511
- agent,
512
- output: result.output,
513
- exitCode: result.exitCode,
514
- error: result.error,
515
- }, null, 2),
516
- },
517
- ],
518
- isError: !result.success,
519
- };
520
- }
521
- catch (error) {
522
- return {
523
- content: [
524
- {
525
- type: "text",
526
- text: `Error spawning agent: ${error instanceof Error ? error.message : String(error)}`,
527
- },
528
- ],
529
- isError: true,
530
- };
531
- }
532
- });
533
- // Tool: spawn_agent_background
534
- server.tool("spawn_agent_background", "Spawn an AI agent in the background (non-blocking). Returns immediately with the output file path. Use this when you want agents to work in parallel.", {
535
- agent: z.enum(AGENT_TYPES).describe("Which agent to spawn (claude, codex, gemini, vibe, or goose)"),
536
- prompt: z.string().describe("The prompt/task to send to the agent"),
537
- }, async ({ agent, prompt }) => {
538
- try {
539
- const result = spawnAgentBackground({
540
- agent: agent,
541
- prompt,
542
- projectPath: PROJECT_PATH,
543
- });
544
- // Post to chat
545
- chatManager.writeMessage({
546
- agent: "NightShift",
547
- type: "INFO",
548
- content: `Spawned ${agent} in background (PID: ${result.pid})\nOutput: ${result.outputFile}`,
549
- });
550
- return {
551
- content: [
552
- {
553
- type: "text",
554
- text: JSON.stringify({
555
- agent,
556
- pid: result.pid,
557
- outputFile: result.outputFile,
558
- status: "running in background",
559
- }, null, 2),
560
- },
561
- ],
562
- };
563
- }
564
- catch (error) {
565
- return {
566
- content: [
567
- {
568
- type: "text",
569
- text: `Error spawning background agent: ${error instanceof Error ? error.message : String(error)}`,
570
- },
571
- ],
572
- isError: true,
573
- };
574
- }
575
- });
576
- // Tool: delegate_story
577
- server.tool("delegate_story", "Delegate a user story to another AI agent. Claims the story, spawns the agent with full context, and tracks completion. The spawned agent works autonomously.", {
578
- agent: z.enum(AGENT_TYPES).describe("Which agent to delegate to"),
579
- storyId: z.string().optional().describe("Specific story ID (defaults to next available)"),
580
- background: z.boolean().optional().describe("Run in background (default: false)"),
581
- }, async ({ agent, storyId, background }) => {
582
- try {
583
- // Get the story
584
- let story;
585
- if (storyId) {
586
- const prd = ralphManager.readPRD();
587
- story = prd?.userStories.find((s) => s.id === storyId && !s.passes);
588
- }
589
- else {
590
- story = ralphManager.getNextStory();
591
- }
592
- if (!story) {
593
- const summary = ralphManager.getCompletionSummary();
594
- if (summary.incomplete === 0) {
595
- return {
596
- content: [
597
- {
598
- type: "text",
599
- text: "<promise>COMPLETE</promise>\nAll stories are complete!",
600
- },
601
- ],
602
- };
603
- }
604
- return {
605
- content: [
606
- {
607
- type: "text",
608
- text: storyId ? `Story ${storyId} not found or already complete.` : "No stories available.",
609
- },
610
- ],
611
- isError: true,
612
- };
613
- }
614
- // Read progress for context
615
- const progress = ralphManager.readProgress();
616
- const recentChat = chatManager.readMessages({ limit: 5 });
617
- // Build delegation prompt
618
- const delegationPrompt = `You are an autonomous coding agent. Complete this user story:
619
-
620
- ## Story: ${story.id} - ${story.title}
621
-
622
- ${story.description}
623
-
624
- ### Acceptance Criteria:
625
- ${story.acceptanceCriteria.map((c) => `- ${c}`).join("\n")}
626
-
627
- ### Context from Progress File:
628
- ${progress ? progress.substring(0, 2000) : "No previous progress."}
629
-
630
- ### Recent Chat:
631
- ${recentChat.map((m) => `[${m.agent}] ${m.type}: ${m.content.substring(0, 200)}`).join("\n")}
632
-
633
- ### Instructions:
634
- 1. Implement the acceptance criteria
635
- 2. Run quality checks (typecheck, lint, test)
636
- 3. Commit with message: "feat: ${story.id} - ${story.title}"
637
- 4. Use the nightshift MCP tools to:
638
- - Post STATUS_UPDATE messages as you work
639
- - Use complete_story when done (include learnings)
640
- - Post FAILOVER_NEEDED if you hit limits
641
-
642
- Begin implementation now.`;
643
- // Post delegation to chat
644
- chatManager.writeMessage({
645
- agent: "NightShift",
646
- type: "TASK_ASSIGNMENT",
647
- content: `Delegating ${story.id} to ${agent}:\n${story.title}`,
648
- });
649
- if (background) {
650
- const result = spawnAgentBackground({
651
- agent: agent,
652
- prompt: delegationPrompt,
653
- projectPath: PROJECT_PATH,
654
- });
655
- return {
656
- content: [
657
- {
658
- type: "text",
659
- text: JSON.stringify({
660
- status: "delegated",
661
- agent,
662
- story: story.id,
663
- title: story.title,
664
- background: true,
665
- pid: result.pid,
666
- outputFile: result.outputFile,
667
- }, null, 2),
668
- },
669
- ],
670
- };
671
- }
672
- else {
673
- const result = await spawnAgent({
674
- agent: agent,
675
- prompt: delegationPrompt,
676
- projectPath: PROJECT_PATH,
677
- timeout: 10 * 60 * 1000, // 10 minutes for story work
678
- });
679
- return {
680
- content: [
681
- {
682
- type: "text",
683
- text: JSON.stringify({
684
- status: result.success ? "completed" : "failed",
685
- agent,
686
- story: story.id,
687
- title: story.title,
688
- output: result.output,
689
- error: result.error,
690
- }, null, 2),
691
- },
692
- ],
693
- isError: !result.success,
694
- };
695
- }
696
- }
697
- catch (error) {
698
- return {
699
- content: [
700
- {
701
- type: "text",
702
- text: `Error delegating story: ${error instanceof Error ? error.message : String(error)}`,
703
- },
704
- ],
705
- isError: true,
706
- };
707
- }
708
- });
709
- // Tool: delegate_research
710
- server.tool("delegate_research", "Delegate a research or planning task to Gemini. Gemini excels at read-only tasks: codebase analysis, architecture planning, code review, and documentation. Returns findings that can inform implementation decisions.", {
711
- task: z.string().describe("The research/planning task (e.g., 'Analyze authentication patterns in codebase', 'Review PR for security issues', 'Plan architecture for feature X')"),
712
- context: z.string().optional().describe("Additional context to provide"),
713
- background: z.boolean().optional().describe("Run in background (default: false)"),
714
- }, async ({ task, context, background }) => {
298
+ role: z.string().optional().describe("Agent role for audit trail (e.g., 'planner', 'coder', 'fixer', 'verifier', 'reviewer')"),
299
+ storyId: z.string().optional().describe("Story ID being worked on (for run log tracking)"),
300
+ }, async ({ agent, prompt, timeout, role, storyId }) => {
301
+ return toolRegistry.execute("spawn_agent", { agent, prompt, timeout, role, storyId }, toolContext);
302
+ });
303
+ // Tool: spawn_agent_background
304
+ server.tool("spawn_agent_background", "Spawn an AI agent in the background (non-blocking). Returns immediately with the output file path. Use this when you want agents to work in parallel.", {
305
+ agent: z.enum(AGENT_TYPES).describe("Which agent to spawn (claude, codex, gemini, vibe, or goose)"),
306
+ prompt: z.string().describe("The prompt/task to send to the agent"),
307
+ role: z.string().optional().describe("Agent role for audit trail (e.g., 'planner', 'coder', 'fixer', 'verifier')"),
308
+ }, async ({ agent, prompt, role }) => {
309
+ return toolRegistry.execute("spawn_agent_background", { agent, prompt, role }, toolContext);
310
+ });
311
+ // Tool: delegate_story
312
+ server.tool("delegate_story", "Delegate a user story to another AI agent. Claims the story, spawns the agent with full context, and tracks completion. The spawned agent works autonomously.", {
313
+ agent: z.enum(AGENT_TYPES).describe("Which agent to delegate to"),
314
+ storyId: z.string().optional().describe("Specific story ID (defaults to next available)"),
315
+ background: z.boolean().optional().describe("Run in background (default: false)"),
316
+ role: z.string().optional().describe("Agent role for audit trail (default: 'implementer')"),
317
+ }, async ({ agent, storyId, background, role }) => {
318
+ return toolRegistry.execute("delegate_story", { agent, storyId, background, role }, toolContext);
319
+ });
320
+ // Tool: delegate_research
321
+ server.tool("delegate_research", "Delegate a research or planning task to Gemini. Gemini excels at read-only tasks: codebase analysis, architecture planning, code review, and documentation. Returns findings that can inform implementation decisions.", {
322
+ task: z.string().describe("The research/planning task (e.g., 'Analyze authentication patterns in codebase', 'Review PR for security issues', 'Plan architecture for feature X')"),
323
+ context: z.string().optional().describe("Additional context to provide"),
324
+ background: z.boolean().optional().describe("Run in background (default: false)"),
325
+ }, async ({ task, context, background }) => {
715
326
  try {
716
327
  // Read codebase context
717
328
  const progress = ralphManager.readProgress();
@@ -803,261 +414,61 @@ Focus on analysis and recommendations. Do NOT attempt to write or modify files.`
803
414
  content: [
804
415
  {
805
416
  type: "text",
806
- text: `Error delegating research: ${error instanceof Error ? error.message : String(error)}`,
807
- },
808
- ],
809
- isError: true,
810
- };
811
- }
812
- });
813
- // ============================================
814
- // Ralph PRD/Progress Tools
815
- // ============================================
816
- // Tool: read_prd
817
- server.tool("read_prd", "Read the PRD (Product Requirements Document) file. Returns structured task list with user stories and their completion status.", {}, async () => {
818
- try {
819
- if (!ralphManager.hasPRD()) {
820
- return {
821
- content: [
822
- {
823
- type: "text",
824
- text: "No prd.json found in project. Create one to use Ralph-style task management.",
825
- },
826
- ],
827
- };
828
- }
829
- const prd = ralphManager.readPRD();
830
- const summary = ralphManager.getCompletionSummary();
831
- return {
832
- content: [
833
- {
834
- type: "text",
835
- text: JSON.stringify({ prd, summary }, null, 2),
836
- },
837
- ],
838
- };
839
- }
840
- catch (error) {
841
- return {
842
- content: [
843
- {
844
- type: "text",
845
- text: `Error reading PRD: ${error instanceof Error ? error.message : String(error)}`,
846
- },
847
- ],
848
- isError: true,
849
- };
850
- }
851
- });
852
- // Tool: get_next_story
853
- server.tool("get_next_story", "Get the next user story to work on (highest priority incomplete story). Use this to claim your next task.", {}, async () => {
854
- try {
855
- const story = ralphManager.getNextStory();
856
- if (!story) {
857
- const summary = ralphManager.getCompletionSummary();
858
- if (summary.total === 0) {
859
- return {
860
- content: [
861
- {
862
- type: "text",
863
- text: "No PRD found or no stories defined.",
864
- },
865
- ],
866
- };
867
- }
868
- return {
869
- content: [
870
- {
871
- type: "text",
872
- text: "<promise>COMPLETE</promise>\nAll stories are complete!",
873
- },
874
- ],
875
- };
876
- }
877
- return {
878
- content: [
879
- {
880
- type: "text",
881
- text: JSON.stringify(story, null, 2),
882
- },
883
- ],
884
- };
885
- }
886
- catch (error) {
887
- return {
888
- content: [
889
- {
890
- type: "text",
891
- text: `Error getting next story: ${error instanceof Error ? error.message : String(error)}`,
892
- },
893
- ],
894
- isError: true,
895
- };
896
- }
897
- });
898
- // Tool: mark_story_complete
899
- server.tool("mark_story_complete", "Mark a user story as complete (sets passes: true). Use after successfully implementing and testing a story.", {
900
- storyId: z.string().describe("The story ID to mark complete (e.g., 'US-001')"),
901
- notes: z.string().optional().describe("Optional notes about the implementation"),
902
- }, async ({ storyId, notes }) => {
903
- try {
904
- const story = ralphManager.markStoryComplete(storyId, notes);
905
- if (!story) {
906
- return {
907
- content: [
908
- {
909
- type: "text",
910
- text: `Story ${storyId} not found in PRD.`,
911
- },
912
- ],
913
- isError: true,
914
- };
915
- }
916
- const summary = ralphManager.getCompletionSummary();
917
- return {
918
- content: [
919
- {
920
- type: "text",
921
- text: `Story ${storyId} marked complete!\n\nProgress: ${summary.complete}/${summary.total} (${summary.percentComplete}%)${summary.incomplete === 0 ? "\n\n<promise>COMPLETE</promise>\nAll stories are complete!" : ""}`,
922
- },
923
- ],
924
- };
925
- }
926
- catch (error) {
927
- return {
928
- content: [
929
- {
930
- type: "text",
931
- text: `Error marking story complete: ${error instanceof Error ? error.message : String(error)}`,
932
- },
933
- ],
934
- isError: true,
935
- };
936
- }
937
- });
938
- // Tool: get_incomplete_stories
939
- server.tool("get_incomplete_stories", "Get all incomplete stories from the PRD, sorted by priority.", {}, async () => {
940
- try {
941
- const stories = ralphManager.getIncompleteStories();
942
- if (stories.length === 0) {
943
- return {
944
- content: [
945
- {
946
- type: "text",
947
- text: "No incomplete stories found. All done!",
948
- },
949
- ],
950
- };
951
- }
952
- return {
953
- content: [
954
- {
955
- type: "text",
956
- text: JSON.stringify(stories, null, 2),
957
- },
958
- ],
959
- };
960
- }
961
- catch (error) {
962
- return {
963
- content: [
964
- {
965
- type: "text",
966
- text: `Error getting incomplete stories: ${error instanceof Error ? error.message : String(error)}`,
967
- },
968
- ],
969
- isError: true,
970
- };
971
- }
972
- });
973
- // Tool: read_progress
974
- server.tool("read_progress", "Read the progress.txt file containing learnings and patterns from previous iterations.", {}, async () => {
975
- try {
976
- const content = ralphManager.readProgress();
977
- if (!content) {
978
- return {
979
- content: [
980
- {
981
- type: "text",
982
- text: "No progress.txt found. One will be created when you append progress.",
983
- },
984
- ],
985
- };
986
- }
987
- return {
988
- content: [
989
- {
990
- type: "text",
991
- text: content,
992
- },
993
- ],
994
- };
995
- }
996
- catch (error) {
997
- return {
998
- content: [
999
- {
1000
- type: "text",
1001
- text: `Error reading progress: ${error instanceof Error ? error.message : String(error)}`,
1002
- },
1003
- ],
1004
- isError: true,
1005
- };
1006
- }
1007
- });
1008
- // Tool: append_progress
1009
- server.tool("append_progress", "Append a progress entry to progress.txt. Include what was implemented, files changed, and learnings for future iterations.", {
1010
- content: z.string().describe("Progress entry content including: what was implemented, files changed, learnings/gotchas discovered"),
1011
- }, async ({ content }) => {
1012
- try {
1013
- ralphManager.appendProgress(content);
1014
- return {
1015
- content: [
1016
- {
1017
- type: "text",
1018
- text: "Progress entry added successfully.",
1019
- },
1020
- ],
1021
- };
1022
- }
1023
- catch (error) {
1024
- return {
1025
- content: [
1026
- {
1027
- type: "text",
1028
- text: `Error appending progress: ${error instanceof Error ? error.message : String(error)}`,
417
+ text: `Error delegating research: ${error instanceof Error ? error.message : String(error)}`,
1029
418
  },
1030
419
  ],
1031
420
  isError: true,
1032
421
  };
1033
422
  }
1034
423
  });
424
+ // ============================================
425
+ // Ralph PRD/Progress Tools
426
+ // ============================================
427
+ // Tool: read_prd
428
+ server.tool("read_prd", "Read the PRD (Product Requirements Document) file. Returns structured task list with user stories and their completion status.", {
429
+ projectPath: z.string().optional().describe("Optional project path override for this call only"),
430
+ }, async ({ projectPath }) => {
431
+ return toolRegistry.execute("read_prd", { projectPath }, toolContext);
432
+ });
433
+ // Tool: get_next_story
434
+ server.tool("get_next_story", "Get the next user story to work on (highest priority incomplete story). Use this to claim your next task.", {
435
+ projectPath: z.string().optional().describe("Optional project path override for this call only"),
436
+ }, async ({ projectPath }) => {
437
+ return toolRegistry.execute("get_next_story", { projectPath }, toolContext);
438
+ });
439
+ // Tool: mark_story_complete
440
+ server.tool("mark_story_complete", "Mark a user story as complete (sets passes: true). Use after successfully implementing and testing a story.", {
441
+ storyId: z.string().describe("The story ID to mark complete (e.g., 'US-001')"),
442
+ notes: z.string().optional().describe("Optional notes about the implementation"),
443
+ projectPath: z.string().optional().describe("Optional project path override for this call only"),
444
+ }, async ({ storyId, notes, projectPath }) => {
445
+ return toolRegistry.execute("mark_story_complete", { storyId, notes, projectPath }, toolContext);
446
+ });
447
+ // Tool: get_incomplete_stories
448
+ server.tool("get_incomplete_stories", "Get all incomplete stories from the PRD, sorted by priority.", {
449
+ projectPath: z.string().optional().describe("Optional project path override for this call only"),
450
+ }, async ({ projectPath }) => {
451
+ return toolRegistry.execute("get_incomplete_stories", { projectPath }, toolContext);
452
+ });
453
+ // Tool: read_progress
454
+ server.tool("read_progress", "Read the progress.txt file containing learnings and patterns from previous iterations.", {
455
+ projectPath: z.string().optional().describe("Optional project path override for this call only"),
456
+ }, async ({ projectPath }) => {
457
+ return toolRegistry.execute("read_progress", { projectPath }, toolContext);
458
+ });
459
+ // Tool: append_progress
460
+ server.tool("append_progress", "Append a progress entry to progress.txt. Include what was implemented, files changed, and learnings for future iterations.", {
461
+ content: z.string().describe("Progress entry content including: what was implemented, files changed, learnings/gotchas discovered"),
462
+ projectPath: z.string().optional().describe("Optional project path override for this call only"),
463
+ }, async ({ content, projectPath }) => {
464
+ return toolRegistry.execute("append_progress", { content, projectPath }, toolContext);
465
+ });
1035
466
  // Tool: add_codebase_pattern
1036
467
  server.tool("add_codebase_pattern", "Add a reusable pattern to the Codebase Patterns section of progress.txt. Use for general patterns future iterations should know.", {
1037
468
  pattern: z.string().describe("A reusable pattern (e.g., 'Use sql<number> template for aggregations')"),
1038
- }, async ({ pattern }) => {
1039
- try {
1040
- ralphManager.addCodebasePattern(pattern);
1041
- return {
1042
- content: [
1043
- {
1044
- type: "text",
1045
- text: `Pattern added: ${pattern}`,
1046
- },
1047
- ],
1048
- };
1049
- }
1050
- catch (error) {
1051
- return {
1052
- content: [
1053
- {
1054
- type: "text",
1055
- text: `Error adding pattern: ${error instanceof Error ? error.message : String(error)}`,
1056
- },
1057
- ],
1058
- isError: true,
1059
- };
1060
- }
469
+ projectPath: z.string().optional().describe("Optional project path override for this call only"),
470
+ }, async ({ pattern, projectPath }) => {
471
+ return toolRegistry.execute("add_codebase_pattern", { pattern, projectPath }, toolContext);
1061
472
  });
1062
473
  // =============================================================================
1063
474
  // Workflow Management Tools
@@ -1429,6 +840,12 @@ Focus on analysis and recommendations. Do NOT attempt to write or modify files.`
1429
840
  };
1430
841
  }
1431
842
  });
843
+ // Tool: get_run_stats
844
+ server.tool("get_run_stats", "Get token usage and performance stats from the run log.", {
845
+ limit: z.number().optional().describe("Number of recent runs to show in detail (default: 10)"),
846
+ }, async ({ limit }) => {
847
+ return toolRegistry.execute("get_run_stats", { limit }, toolContext);
848
+ });
1432
849
  // =============================================================================
1433
850
  // Story Management Tools
1434
851
  // =============================================================================
@@ -1436,78 +853,9 @@ Focus on analysis and recommendations. Do NOT attempt to write or modify files.`
1436
853
  server.tool("claim_story", "Claim a story to work on and notify other agents via chat. Combines getting next story and posting STORY_CLAIMED message.", {
1437
854
  agent: z.string().describe("Your agent name (e.g., 'Claude', 'Codex', 'Gemini')"),
1438
855
  storyId: z.string().optional().describe("Specific story ID to claim (optional - defaults to next available)"),
1439
- }, async ({ agent, storyId }) => {
1440
- try {
1441
- let story;
1442
- if (storyId) {
1443
- const prd = ralphManager.readPRD();
1444
- story = prd?.userStories.find((s) => s.id === storyId && !s.passes);
1445
- if (!story) {
1446
- return {
1447
- content: [
1448
- {
1449
- type: "text",
1450
- text: `Story ${storyId} not found or already complete.`,
1451
- },
1452
- ],
1453
- isError: true,
1454
- };
1455
- }
1456
- }
1457
- else {
1458
- story = ralphManager.getNextStory();
1459
- }
1460
- if (!story) {
1461
- return {
1462
- content: [
1463
- {
1464
- type: "text",
1465
- text: "<promise>COMPLETE</promise>\nAll stories are complete!",
1466
- },
1467
- ],
1468
- };
1469
- }
1470
- // Assign in workflow state (atomic operation to prevent race conditions)
1471
- const assignResult = await workflowManager.assignStory(story.id, agent);
1472
- if (!assignResult.success) {
1473
- return {
1474
- content: [
1475
- {
1476
- type: "text",
1477
- text: assignResult.existingAgent
1478
- ? `Story ${story.id} is already being worked on by ${assignResult.existingAgent}.`
1479
- : assignResult.error || `Failed to assign story ${story.id} - it may have been claimed by another agent.`,
1480
- },
1481
- ],
1482
- isError: true,
1483
- };
1484
- }
1485
- // Post claim message to chat
1486
- await chatManager.writeMessage({
1487
- agent,
1488
- type: "STORY_CLAIMED",
1489
- content: `Claiming story: ${story.id} - ${story.title}\n\nDescription: ${story.description}\n\nAcceptance Criteria:\n${story.acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`,
1490
- });
1491
- return {
1492
- content: [
1493
- {
1494
- type: "text",
1495
- text: `Claimed story ${story.id}: ${story.title}\n\n${JSON.stringify(story, null, 2)}`,
1496
- },
1497
- ],
1498
- };
1499
- }
1500
- catch (error) {
1501
- return {
1502
- content: [
1503
- {
1504
- type: "text",
1505
- text: `Error claiming story: ${error instanceof Error ? error.message : String(error)}`,
1506
- },
1507
- ],
1508
- isError: true,
1509
- };
1510
- }
856
+ projectPath: z.string().optional().describe("Optional project path override for this call only"),
857
+ }, async ({ agent, storyId, projectPath }) => {
858
+ return toolRegistry.execute("claim_story", { agent, storyId, projectPath }, toolContext);
1511
859
  });
1512
860
  // Tool: complete_story
1513
861
  server.tool("complete_story", "Mark a story complete and notify other agents via chat. Combines marking complete and posting STORY_COMPLETE message.", {
@@ -1516,241 +864,32 @@ Focus on analysis and recommendations. Do NOT attempt to write or modify files.`
1516
864
  summary: z.string().describe("Brief summary of what was implemented"),
1517
865
  filesModified: z.array(z.string()).optional().describe("List of files modified"),
1518
866
  learnings: z.string().optional().describe("Learnings/gotchas for future iterations"),
1519
- }, async ({ agent, storyId, summary, filesModified, learnings }) => {
1520
- try {
1521
- // Validate that this agent owns the story assignment
1522
- const isOwner = workflowManager.isAgentOwner(storyId, agent);
1523
- const existingAssignment = workflowManager.isStoryAssigned(storyId);
1524
- if (existingAssignment && !isOwner) {
1525
- return {
1526
- content: [
1527
- {
1528
- type: "text",
1529
- text: `Cannot complete story ${storyId} - it is assigned to ${existingAssignment.agent}, not ${agent}.`,
1530
- },
1531
- ],
1532
- isError: true,
1533
- };
1534
- }
1535
- const story = ralphManager.markStoryComplete(storyId, summary);
1536
- if (!story) {
1537
- return {
1538
- content: [
1539
- {
1540
- type: "text",
1541
- text: `Story ${storyId} not found.`,
1542
- },
1543
- ],
1544
- isError: true,
1545
- };
1546
- }
1547
- // Mark assignment as completed in workflow state
1548
- await workflowManager.completeAssignment(storyId);
1549
- // Build progress entry
1550
- let progressEntry = `Story: ${storyId} - ${story.title}\nAgent: ${agent}\n\n${summary}`;
1551
- if (filesModified && filesModified.length > 0) {
1552
- progressEntry += `\n\nFiles Modified:\n${filesModified.map((f) => `- ${f}`).join("\n")}`;
1553
- }
1554
- if (learnings) {
1555
- progressEntry += `\n\n**Learnings for future iterations:**\n${learnings}`;
1556
- }
1557
- // Append to progress file
1558
- ralphManager.appendProgress(progressEntry);
1559
- // Post completion message to chat
1560
- let chatContent = `Completed: ${storyId} - ${story.title}\n\n${summary}`;
1561
- if (filesModified && filesModified.length > 0) {
1562
- chatContent += `\n\nFiles: ${filesModified.join(", ")}`;
1563
- }
1564
- await chatManager.writeMessage({
1565
- agent,
1566
- type: "STORY_COMPLETE",
1567
- content: chatContent,
1568
- });
1569
- // Auto-create savepoint
1570
- const savepoint = ralphManager.createSavepoint(storyId, `Completed: ${storyId} - ${story.title}`);
1571
- const prdSummary = ralphManager.getCompletionSummary();
1572
- const allWorkDone = ralphManager.isAllWorkComplete();
1573
- // If all work is done, notify READY_TO_TEST
1574
- if (allWorkDone) {
1575
- await chatManager.writeMessage({
1576
- agent: "NightShift",
1577
- type: "READY_TO_TEST",
1578
- content: `All stories and bugs complete!\n\nStories: ${prdSummary.complete}/${prdSummary.total}\n\nReady for human review and testing.`,
1579
- });
1580
- }
1581
- let response = `Story ${storyId} completed!\n\nProgress: ${prdSummary.complete}/${prdSummary.total} (${prdSummary.percentComplete}%)`;
1582
- if (savepoint.success) {
1583
- response += `\nSavepoint: ${savepoint.tag}`;
1584
- }
1585
- if (allWorkDone) {
1586
- response += "\n\n<promise>COMPLETE</promise>\nAll work complete! Ready to test.";
1587
- }
1588
- return {
1589
- content: [
1590
- {
1591
- type: "text",
1592
- text: response,
1593
- },
1594
- ],
1595
- };
1596
- }
1597
- catch (error) {
1598
- return {
1599
- content: [
1600
- {
1601
- type: "text",
1602
- text: `Error completing story: ${error instanceof Error ? error.message : String(error)}`,
1603
- },
1604
- ],
1605
- isError: true,
1606
- };
1607
- }
867
+ projectPath: z.string().optional().describe("Optional project path override for this call only"),
868
+ }, async ({ agent, storyId, summary, filesModified, learnings, projectPath }) => {
869
+ return toolRegistry.execute("complete_story", { agent, storyId, summary, filesModified, learnings, projectPath }, toolContext);
1608
870
  });
1609
871
  // =============================================================================
1610
872
  // Bug Management Tools
1611
873
  // =============================================================================
1612
874
  // Tool: read_bugs
1613
- server.tool("read_bugs", "Read the bugs.json file. Returns list of bugs with their fixed status.", {}, async () => {
1614
- try {
1615
- if (!ralphManager.hasBugs()) {
1616
- return {
1617
- content: [
1618
- {
1619
- type: "text",
1620
- text: "No bugs.json found. Create one to track bugs.",
1621
- },
1622
- ],
1623
- };
1624
- }
1625
- const bugList = ralphManager.readBugs();
1626
- const summary = ralphManager.getBugsSummary();
1627
- return {
1628
- content: [
1629
- {
1630
- type: "text",
1631
- text: JSON.stringify({ bugs: bugList, summary }, null, 2),
1632
- },
1633
- ],
1634
- };
1635
- }
1636
- catch (error) {
1637
- return {
1638
- content: [
1639
- {
1640
- type: "text",
1641
- text: `Error reading bugs: ${error instanceof Error ? error.message : String(error)}`,
1642
- },
1643
- ],
1644
- isError: true,
1645
- };
1646
- }
875
+ server.tool("read_bugs", "Read the bugs.json file. Returns list of bugs with their fixed status.", {
876
+ projectPath: z.string().optional().describe("Optional project path override for this call only"),
877
+ }, async ({ projectPath }) => {
878
+ return toolRegistry.execute("read_bugs", { projectPath }, toolContext);
1647
879
  });
1648
880
  // Tool: get_next_bug
1649
- server.tool("get_next_bug", "Get the next bug to fix (highest priority unfixed bug).", {}, async () => {
1650
- try {
1651
- const bug = ralphManager.getNextBug();
1652
- if (!bug) {
1653
- const summary = ralphManager.getBugsSummary();
1654
- if (summary.total === 0) {
1655
- return {
1656
- content: [
1657
- {
1658
- type: "text",
1659
- text: "No bugs.json found or no bugs defined.",
1660
- },
1661
- ],
1662
- };
1663
- }
1664
- return {
1665
- content: [
1666
- {
1667
- type: "text",
1668
- text: "All bugs are fixed!",
1669
- },
1670
- ],
1671
- };
1672
- }
1673
- return {
1674
- content: [
1675
- {
1676
- type: "text",
1677
- text: JSON.stringify(bug, null, 2),
1678
- },
1679
- ],
1680
- };
1681
- }
1682
- catch (error) {
1683
- return {
1684
- content: [
1685
- {
1686
- type: "text",
1687
- text: `Error getting next bug: ${error instanceof Error ? error.message : String(error)}`,
1688
- },
1689
- ],
1690
- isError: true,
1691
- };
1692
- }
881
+ server.tool("get_next_bug", "Get the next bug to fix (highest priority unfixed bug).", {
882
+ projectPath: z.string().optional().describe("Optional project path override for this call only"),
883
+ }, async ({ projectPath }) => {
884
+ return toolRegistry.execute("get_next_bug", { projectPath }, toolContext);
1693
885
  });
1694
886
  // Tool: claim_bug
1695
887
  server.tool("claim_bug", "Claim a bug to work on and notify other agents via chat.", {
1696
888
  agent: z.string().describe("Your agent name"),
1697
889
  bugId: z.string().optional().describe("Specific bug ID to claim (optional - defaults to next available)"),
1698
- }, async ({ agent, bugId }) => {
1699
- try {
1700
- let bug;
1701
- if (bugId) {
1702
- bug = ralphManager.getBug(bugId);
1703
- if (!bug || bug.fixed) {
1704
- return {
1705
- content: [
1706
- {
1707
- type: "text",
1708
- text: `Bug ${bugId} not found or already fixed.`,
1709
- },
1710
- ],
1711
- isError: true,
1712
- };
1713
- }
1714
- }
1715
- else {
1716
- bug = ralphManager.getNextBug();
1717
- }
1718
- if (!bug) {
1719
- return {
1720
- content: [
1721
- {
1722
- type: "text",
1723
- text: "No unfixed bugs available.",
1724
- },
1725
- ],
1726
- };
1727
- }
1728
- // Post claim message to chat
1729
- await chatManager.writeMessage({
1730
- agent,
1731
- type: "BUG_CLAIMED",
1732
- content: `Claiming bug: ${bug.id} - ${bug.title}\n\nDescription: ${bug.description}${bug.stepsToReproduce ? `\n\nSteps to reproduce:\n${bug.stepsToReproduce.map((s) => `- ${s}`).join("\n")}` : ""}`,
1733
- });
1734
- return {
1735
- content: [
1736
- {
1737
- type: "text",
1738
- text: `Claimed bug ${bug.id}: ${bug.title}\n\n${JSON.stringify(bug, null, 2)}`,
1739
- },
1740
- ],
1741
- };
1742
- }
1743
- catch (error) {
1744
- return {
1745
- content: [
1746
- {
1747
- type: "text",
1748
- text: `Error claiming bug: ${error instanceof Error ? error.message : String(error)}`,
1749
- },
1750
- ],
1751
- isError: true,
1752
- };
1753
- }
890
+ projectPath: z.string().optional().describe("Optional project path override for this call only"),
891
+ }, async ({ agent, bugId, projectPath }) => {
892
+ return toolRegistry.execute("claim_bug", { agent, bugId, projectPath }, toolContext);
1754
893
  });
1755
894
  // Tool: mark_bug_fixed
1756
895
  server.tool("mark_bug_fixed", "Mark a bug as fixed, create savepoint, and notify via chat. If all work is done, sends READY_TO_TEST.", {
@@ -1758,76 +897,9 @@ Focus on analysis and recommendations. Do NOT attempt to write or modify files.`
1758
897
  bugId: z.string().describe("The bug ID to mark fixed"),
1759
898
  summary: z.string().describe("Brief summary of the fix"),
1760
899
  filesModified: z.array(z.string()).optional().describe("List of files modified"),
1761
- }, async ({ agent, bugId, summary, filesModified }) => {
1762
- try {
1763
- const bug = ralphManager.markBugFixed(bugId, summary);
1764
- if (!bug) {
1765
- return {
1766
- content: [
1767
- {
1768
- type: "text",
1769
- text: `Bug ${bugId} not found.`,
1770
- },
1771
- ],
1772
- isError: true,
1773
- };
1774
- }
1775
- // Log to progress
1776
- let progressEntry = `Bug Fixed: ${bugId} - ${bug.title}\nAgent: ${agent}\n\n${summary}`;
1777
- if (filesModified && filesModified.length > 0) {
1778
- progressEntry += `\n\nFiles Modified:\n${filesModified.map((f) => `- ${f}`).join("\n")}`;
1779
- }
1780
- ralphManager.appendProgress(progressEntry);
1781
- // Post to chat
1782
- let chatContent = `Fixed: ${bugId} - ${bug.title}\n\n${summary}`;
1783
- if (filesModified && filesModified.length > 0) {
1784
- chatContent += `\n\nFiles: ${filesModified.join(", ")}`;
1785
- }
1786
- await chatManager.writeMessage({
1787
- agent,
1788
- type: "BUG_FIXED",
1789
- content: chatContent,
1790
- });
1791
- // Auto-create savepoint
1792
- const savepoint = ralphManager.createSavepoint(bugId, `Fixed: ${bugId} - ${bug.title}`);
1793
- const bugsSummary = ralphManager.getBugsSummary();
1794
- const allWorkDone = ralphManager.isAllWorkComplete();
1795
- // If all work is done, notify READY_TO_TEST
1796
- if (allWorkDone) {
1797
- const prdSummary = ralphManager.getCompletionSummary();
1798
- await chatManager.writeMessage({
1799
- agent: "NightShift",
1800
- type: "READY_TO_TEST",
1801
- content: `All stories and bugs complete!\n\nStories: ${prdSummary.complete}/${prdSummary.total}\nBugs: ${bugsSummary.fixed}/${bugsSummary.total}\n\nReady for human review and testing.`,
1802
- });
1803
- }
1804
- let response = `Bug ${bugId} fixed!\n\nBugs: ${bugsSummary.fixed}/${bugsSummary.total} (${bugsSummary.percentFixed}%)`;
1805
- if (savepoint.success) {
1806
- response += `\nSavepoint: ${savepoint.tag}`;
1807
- }
1808
- if (allWorkDone) {
1809
- response += "\n\n<promise>COMPLETE</promise>\nAll work complete! Ready to test.";
1810
- }
1811
- return {
1812
- content: [
1813
- {
1814
- type: "text",
1815
- text: response,
1816
- },
1817
- ],
1818
- };
1819
- }
1820
- catch (error) {
1821
- return {
1822
- content: [
1823
- {
1824
- type: "text",
1825
- text: `Error marking bug fixed: ${error instanceof Error ? error.message : String(error)}`,
1826
- },
1827
- ],
1828
- isError: true,
1829
- };
1830
- }
900
+ projectPath: z.string().optional().describe("Optional project path override for this call only"),
901
+ }, async ({ agent, bugId, summary, filesModified, projectPath }) => {
902
+ return toolRegistry.execute("mark_bug_fixed", { agent, bugId, summary, filesModified, projectPath }, toolContext);
1831
903
  });
1832
904
  // =============================================================================
1833
905
  // Savepoint Tools
@@ -1962,7 +1034,7 @@ Focus on analysis and recommendations. Do NOT attempt to write or modify files.`
1962
1034
  const issues = [];
1963
1035
  const suggestions = [];
1964
1036
  // Check project path
1965
- checks.push(`Project path: ${PROJECT_PATH}`);
1037
+ checks.push(`Current project path: ${PROJECT_PATH}`);
1966
1038
  // Check for prd.json
1967
1039
  if (ralphManager.hasPRD()) {
1968
1040
  const summary = ralphManager.getCompletionSummary();
@@ -2118,7 +1190,7 @@ args = ["${PROJECT_PATH}"]
2118
1190
  const fixes = [];
2119
1191
  diagnostics.push(`# NightShift Debug Report\n`);
2120
1192
  diagnostics.push(`Timestamp: ${new Date().toISOString()}`);
2121
- diagnostics.push(`Project: ${PROJECT_PATH}\n`);
1193
+ diagnostics.push(`Current project path: ${PROJECT_PATH}\n`);
2122
1194
  // 1. Check file system
2123
1195
  diagnostics.push(`## File System Checks`);
2124
1196
  // Check project directory exists and is writable
@@ -2371,7 +1443,7 @@ ${c.dim} The responsible kind of multi-agent chaos.${c.reset}
2371
1443
  ${c.yellow}${c.bold} ⚡ Project${c.reset}
2372
1444
  ${c.dim}Path:${c.reset} ${PROJECT_PATH}
2373
1445
  ${c.dim}Chat:${c.reset} .robot-chat/chat.txt
2374
- ${c.dim}Mode:${c.reset} ${REGISTRATION_MODE} (${toolCounts.total} actions via ${REGISTRATION_MODE === "minimal" ? "2" : REGISTRATION_MODE === "legacy" ? toolCounts.total : toolCounts.total + 2} tools)
1446
+ ${c.dim}Mode:${c.reset} ${REGISTRATION_MODE} (${toolCounts.total} actions via ${REGISTRATION_MODE === "minimal" ? "3" : REGISTRATION_MODE === "legacy" ? toolCounts.total + 1 : toolCounts.total + 3} tools)
2375
1447
  `;
2376
1448
  const agentSection = `
2377
1449
  ${c.green}${c.bold} 🤖 Agents${c.reset} ${c.dim}(${installed.length}/${agents.length} available)${c.reset}
@@ -2408,18 +1480,19 @@ ${c.dim} Ready for multi-agent collaboration!${c.reset}
2408
1480
  }
2409
1481
  // Register tools based on mode
2410
1482
  function registerTools() {
1483
+ registerProjectPathTool();
2411
1484
  switch (REGISTRATION_MODE) {
2412
1485
  case "minimal":
2413
- // Only meta-tools (2 tools, ~200 tokens)
1486
+ // Meta-tools + set_project_path
2414
1487
  registerMetaTools();
2415
1488
  break;
2416
1489
  case "legacy":
2417
- // Only individual tools (39 tools, ~2,300 tokens)
1490
+ // Individual tools + set_project_path
2418
1491
  registerIndividualTools();
2419
1492
  break;
2420
1493
  case "hybrid":
2421
1494
  default:
2422
- // Both meta-tools and individual tools (41 tools)
1495
+ // Meta-tools + individual tools + set_project_path
2423
1496
  registerMetaTools();
2424
1497
  registerIndividualTools();
2425
1498
  break;