Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,47 @@ The `docs/` folder contains two types of documentation:
| `agent-resources/troubleshooting/*.md` | Language-specific debugging tips (Python, JavaScript, Java, C#) |

These resource files are loaded by `DebugMCPServer` and exposed as MCP resources that AI agents can read to learn how to use the debugging tools effectively.

<!-- gitnexus:start -->
# GitNexus — Code Intelligence

This project is indexed by GitNexus as **DebugMCP** (1057 symbols, 2730 relationships, 82 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.

> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

## Always Do

- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user.
- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows.
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`.

## Never Do

- NEVER edit a function, class, or method without first running `gitnexus_impact` on it.
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph.
- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope.

## Resources

| Resource | Use for |
|----------|---------|
| `gitnexus://repo/DebugMCP/context` | Codebase overview, check index freshness |
| `gitnexus://repo/DebugMCP/clusters` | All functional areas |
| `gitnexus://repo/DebugMCP/processes` | All execution flows |
| `gitnexus://repo/DebugMCP/process/{name}` | Step-by-step execution trace |

## CLI

| Task | Read this skill file |
|------|---------------------|
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |

<!-- gitnexus:end -->
43 changes: 43 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<!-- gitnexus:start -->
# GitNexus — Code Intelligence

This project is indexed by GitNexus as **DebugMCP** (1057 symbols, 2730 relationships, 82 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.

> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

## Always Do

- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user.
- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows.
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`.

## Never Do

- NEVER edit a function, class, or method without first running `gitnexus_impact` on it.
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph.
- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope.

## Resources

| Resource | Use for |
|----------|---------|
| `gitnexus://repo/DebugMCP/context` | Codebase overview, check index freshness |
| `gitnexus://repo/DebugMCP/clusters` | All functional areas |
| `gitnexus://repo/DebugMCP/processes` | All execution flows |
| `gitnexus://repo/DebugMCP/process/{name}` | Step-by-step execution trace |

## CLI

| Task | Read this skill file |
|------|---------------------|
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |

<!-- gitnexus:end -->
8 changes: 0 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/controlServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export class ControlServer {
case 'handleStepOut':
return this.handler.handleStepOut();
case 'handleContinue':
return this.handler.handleContinue();
return this.handler.handleContinue(args);
case 'handlePause':
return this.handler.handlePause();
case 'handleRestart':
Expand All @@ -109,6 +109,8 @@ export class ControlServer {
return this.handler.handleGetVariables(args);
case 'handleEvaluateExpression':
return this.handler.handleEvaluateExpression(args);
case 'handleGetDebugState':
return this.handler.handleGetDebugState();
default:
return Promise.reject(new Error(`Unknown control op: ${op}`));
}
Expand Down
15 changes: 13 additions & 2 deletions src/debugMCPServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,15 +220,23 @@ export class DebugMCPServer {
'Optional debug configuration name from launch.json. ' +
'If omitted, DebugMCP uses its default generated configuration.'
),
noWait: z.boolean().optional().describe(
'If true, returns immediately after starting instead of waiting for a breakpoint.'
),
},
}, async (args: { fileFullPath: string; workingDirectory: string; testName?: string; configurationName?: string }) =>
}, async (args: { fileFullPath: string; workingDirectory: string; testName?: string; configurationName?: string; noWait?: boolean }) =>
this.runTool('start_debugging', () => debuggingHandler.handleStartDebugging(args)));

// Stop debugging tool
server.registerTool('stop_debugging', {
description: 'Stop the current debug session',
}, async () => this.runTool('stop_debugging', () => debuggingHandler.handleStopDebugging()));

// Get debug state tool
server.registerTool('get_debug_state', {
description: 'Get the current state of the debug session (running, paused, or inactive), including current breakpoint location and pause reason if paused.',
}, async () => this.runTool('get_debug_state', () => debuggingHandler.handleGetDebugState()));

// Step over tool
server.registerTool('step_over', {
description: 'Execute the current line of code without diving into it.',
Expand All @@ -247,7 +255,10 @@ export class DebugMCPServer {
// Continue execution tool
server.registerTool('continue_execution', {
description: 'Resume program execution until the next breakpoint is hit or the program completes.',
}, async () => this.runTool('continue_execution', () => debuggingHandler.handleContinue()));
inputSchema: {
noWait: z.boolean().optional().describe('If true, returns immediately instead of waiting for the next breakpoint.'),
},
}, async (args: { noWait?: boolean } = {}) => this.runTool('continue_execution', () => debuggingHandler.handleContinue(args)));

// Pause execution tool
server.registerTool('pause_execution', {
Expand Down
32 changes: 27 additions & 5 deletions src/debuggingHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import { logger } from './utils/logger';
* Interface for debugging handler operations
*/
export interface IDebuggingHandler {
handleStartDebugging(args: { fileFullPath: string; workingDirectory: string; testName?: string; configurationName?: string }): Promise<string>;
handleStartDebugging(args: { fileFullPath: string; workingDirectory: string; testName?: string; configurationName?: string; noWait?: boolean }): Promise<string>;
handleStopDebugging(): Promise<string>;
handleStepOver(): Promise<string>;
handleStepInto(): Promise<string>;
handleStepOut(): Promise<string>;
handleContinue(): Promise<string>;
handleContinue(args?: { noWait?: boolean }): Promise<string>;
handlePause(): Promise<string>;
handleRestart(): Promise<string>;
handleAddBreakpoint(args: { fileFullPath: string; line: number; condition?: string }): Promise<string>;
Expand All @@ -25,6 +25,7 @@ export interface IDebuggingHandler {
handleListBreakpoints(): Promise<string>;
handleGetVariables(args: { scope?: 'local' | 'global' | 'all' }): Promise<string>;
handleEvaluateExpression(args: { expression: string }): Promise<string>;
handleGetDebugState(): Promise<string>;
}

/**
Expand All @@ -51,8 +52,9 @@ export class DebuggingHandler implements IDebuggingHandler {
workingDirectory: string;
testName?: string;
configurationName?: string;
noWait?: boolean;
}): Promise<string> {
const { fileFullPath, workingDirectory, testName, configurationName } = args;
const { fileFullPath, workingDirectory, testName, configurationName, noWait } = args;
const hasExplicitConfig = !!configurationName &&
configurationName.trim() !== '' &&
configurationName !== DebugConfigurationManager.getAutoLaunchConfigName();
Expand Down Expand Up @@ -90,6 +92,10 @@ export class DebuggingHandler implements IDebuggingHandler {
}

if (started) {
if (noWait) {
return `Debug session started (noWait=true) for: ${fileFullPath} using ${configDescription}. The session is running in the background.`;
}

// Race the readiness signal against the test run completion. For .NET
// (and any runner where onDidTerminateDebugSession doesn't fire
// reliably for parent/child sessions), the test-run-complete signal
Expand Down Expand Up @@ -232,7 +238,7 @@ export class DebuggingHandler implements IDebuggingHandler {
/**
* Continue execution
*/
public async handleContinue(): Promise<string> {
public async handleContinue(args?: { noWait?: boolean }): Promise<string> {
try {
if (!(await this.executor.hasActiveSession())) {
throw new Error('Debug session is not ready. Please wait for initialization to complete.');
Expand All @@ -243,6 +249,10 @@ export class DebuggingHandler implements IDebuggingHandler {

await this.executor.continue();

if (args?.noWait) {
return "Execution continued (noWait=true). The program is running in the background.";
}

// Wait for debugger state to change
const afterState = await this.waitForStateChange(beforeState);

Expand Down Expand Up @@ -505,7 +515,19 @@ export class DebuggingHandler implements IDebuggingHandler {
}

/**
* Get current debug state
* Get current debug state as string
*/
public async handleGetDebugState(): Promise<string> {
try {
const state = await this.executor.getCurrentDebugState(this.numNextLines);
return state.toString();
} catch (error) {
throw new Error(`Error getting debug state: ${error}`);
}
}

/**
* Get current debug state object
*/
public async getCurrentDebugState(): Promise<DebugState> {
return await this.executor.getCurrentDebugState(this.numNextLines);
Expand Down
9 changes: 7 additions & 2 deletions src/routingDebuggingHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export class RoutingDebuggingHandler implements IDebuggingHandler {
workingDirectory: string;
testName?: string;
configurationName?: string;
noWait?: boolean;
}): Promise<string> {
return this.forward('handleStartDebugging', args, args.workingDirectory || args.fileFullPath);
}
Expand All @@ -188,8 +189,8 @@ export class RoutingDebuggingHandler implements IDebuggingHandler {
return this.forward('handleStepOut', {});
}

public handleContinue(): Promise<string> {
return this.forward('handleContinue', {});
public handleContinue(args?: { noWait?: boolean }): Promise<string> {
return this.forward('handleContinue', args || {});
}

public handlePause(): Promise<string> {
Expand Down Expand Up @@ -227,4 +228,8 @@ export class RoutingDebuggingHandler implements IDebuggingHandler {
public handleEvaluateExpression(args: { expression: string }): Promise<string> {
return this.forward('handleEvaluateExpression', args);
}

public handleGetDebugState(): Promise<string> {
return this.forward('handleGetDebugState', {});
}
}
1 change: 1 addition & 0 deletions src/test/routing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class RecordingHandler implements IDebuggingHandler {
handleListBreakpoints() { return this.record('listBp', {}); }
handleGetVariables(args: any) { return this.record('vars', args); }
handleEvaluateExpression(args: any) { return this.record('eval', args); }
handleGetDebugState() { return this.record('getDebugState', {}); }
}

suite('Multi-window routing', () => {
Expand Down