@harness-engineering/mcp-server 0.3.2 → 0.5.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.
- package/dist/src/resources/graph.d.ts +3 -0
- package/dist/src/resources/graph.js +104 -0
- package/dist/src/server.js +38 -0
- package/dist/src/tools/agent-definitions.d.ts +27 -0
- package/dist/src/tools/agent-definitions.js +30 -0
- package/dist/src/tools/architecture.js +19 -1
- package/dist/src/tools/docs.js +15 -0
- package/dist/src/tools/entropy.js +33 -2
- package/dist/src/tools/feedback.d.ts +5 -0
- package/dist/src/tools/feedback.js +64 -2
- package/dist/src/tools/generate-slash-commands.d.ts +5 -0
- package/dist/src/tools/generate-slash-commands.js +5 -0
- package/dist/src/tools/graph.d.ts +268 -0
- package/dist/src/tools/graph.js +495 -0
- package/dist/src/tools/persona.d.ts +6 -0
- package/dist/src/tools/persona.js +26 -13
- package/dist/src/utils/graph-loader.d.ts +1 -0
- package/dist/src/utils/graph-loader.js +10 -0
- package/package.json +5 -4
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { loadGraphStore } from '../utils/graph-loader.js';
|
|
4
|
+
const MAX_ITEMS = 5000;
|
|
5
|
+
function formatStaleness(isoTimestamp) {
|
|
6
|
+
const then = new Date(isoTimestamp).getTime();
|
|
7
|
+
const now = Date.now();
|
|
8
|
+
const diffMs = now - then;
|
|
9
|
+
const seconds = Math.floor(diffMs / 1000);
|
|
10
|
+
const minutes = Math.floor(seconds / 60);
|
|
11
|
+
const hours = Math.floor(minutes / 60);
|
|
12
|
+
const days = Math.floor(hours / 24);
|
|
13
|
+
if (days > 0)
|
|
14
|
+
return `${days} day${days === 1 ? '' : 's'} ago`;
|
|
15
|
+
if (hours > 0)
|
|
16
|
+
return `${hours} hour${hours === 1 ? '' : 's'} ago`;
|
|
17
|
+
if (minutes > 0)
|
|
18
|
+
return `${minutes} minute${minutes === 1 ? '' : 's'} ago`;
|
|
19
|
+
return 'just now';
|
|
20
|
+
}
|
|
21
|
+
export async function getGraphResource(projectRoot) {
|
|
22
|
+
const store = await loadGraphStore(projectRoot);
|
|
23
|
+
if (!store) {
|
|
24
|
+
return JSON.stringify({
|
|
25
|
+
status: 'no_graph',
|
|
26
|
+
message: 'No knowledge graph found. Run harness scan to build one.',
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const graphDir = path.join(projectRoot, '.harness', 'graph');
|
|
30
|
+
const metadataPath = path.join(graphDir, 'metadata.json');
|
|
31
|
+
let lastScanTimestamp = null;
|
|
32
|
+
try {
|
|
33
|
+
const raw = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
|
|
34
|
+
lastScanTimestamp = raw.lastScanTimestamp ?? null;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Ignore missing or malformed metadata
|
|
38
|
+
}
|
|
39
|
+
const allNodes = store.findNodes({});
|
|
40
|
+
const allEdges = store.getEdges({});
|
|
41
|
+
const nodesByType = {};
|
|
42
|
+
for (const node of allNodes) {
|
|
43
|
+
nodesByType[node.type] = (nodesByType[node.type] ?? 0) + 1;
|
|
44
|
+
}
|
|
45
|
+
const edgesByType = {};
|
|
46
|
+
for (const edge of allEdges) {
|
|
47
|
+
edgesByType[edge.type] = (edgesByType[edge.type] ?? 0) + 1;
|
|
48
|
+
}
|
|
49
|
+
let status = 'ok';
|
|
50
|
+
let staleness = 'unknown';
|
|
51
|
+
if (lastScanTimestamp) {
|
|
52
|
+
const ageMs = Date.now() - new Date(lastScanTimestamp).getTime();
|
|
53
|
+
const twentyFourHoursMs = 24 * 60 * 60 * 1000;
|
|
54
|
+
if (ageMs > twentyFourHoursMs) {
|
|
55
|
+
status = 'stale';
|
|
56
|
+
}
|
|
57
|
+
staleness = formatStaleness(lastScanTimestamp);
|
|
58
|
+
}
|
|
59
|
+
return JSON.stringify({
|
|
60
|
+
status,
|
|
61
|
+
nodeCount: store.nodeCount,
|
|
62
|
+
edgeCount: store.edgeCount,
|
|
63
|
+
nodesByType,
|
|
64
|
+
edgesByType,
|
|
65
|
+
lastScanTimestamp,
|
|
66
|
+
staleness,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
export async function getEntitiesResource(projectRoot) {
|
|
70
|
+
const store = await loadGraphStore(projectRoot);
|
|
71
|
+
if (!store) {
|
|
72
|
+
return '[]';
|
|
73
|
+
}
|
|
74
|
+
const nodes = store.findNodes({});
|
|
75
|
+
const entities = nodes.slice(0, MAX_ITEMS).map((n) => ({
|
|
76
|
+
id: n.id,
|
|
77
|
+
type: n.type,
|
|
78
|
+
name: n.name,
|
|
79
|
+
path: n.path,
|
|
80
|
+
metadata: n.metadata,
|
|
81
|
+
}));
|
|
82
|
+
if (nodes.length > MAX_ITEMS) {
|
|
83
|
+
return JSON.stringify({ entities, _truncated: true, _total: nodes.length }, null, 2);
|
|
84
|
+
}
|
|
85
|
+
return JSON.stringify(entities);
|
|
86
|
+
}
|
|
87
|
+
export async function getRelationshipsResource(projectRoot) {
|
|
88
|
+
const store = await loadGraphStore(projectRoot);
|
|
89
|
+
if (!store) {
|
|
90
|
+
return '[]';
|
|
91
|
+
}
|
|
92
|
+
const edges = store.getEdges({});
|
|
93
|
+
const relationships = edges.slice(0, MAX_ITEMS).map((e) => ({
|
|
94
|
+
from: e.from,
|
|
95
|
+
to: e.to,
|
|
96
|
+
type: e.type,
|
|
97
|
+
confidence: e.confidence,
|
|
98
|
+
metadata: e.metadata,
|
|
99
|
+
}));
|
|
100
|
+
if (edges.length > MAX_ITEMS) {
|
|
101
|
+
return JSON.stringify({ relationships, _truncated: true, _total: edges.length }, null, 2);
|
|
102
|
+
}
|
|
103
|
+
return JSON.stringify(relationships);
|
|
104
|
+
}
|
package/dist/src/server.js
CHANGED
|
@@ -20,6 +20,9 @@ import { checkPhaseGateDefinition, handleCheckPhaseGate } from './tools/phase-ga
|
|
|
20
20
|
import { validateCrossCheckDefinition, handleValidateCrossCheck } from './tools/cross-check.js';
|
|
21
21
|
import { generateSlashCommandsDefinition, handleGenerateSlashCommands, } from './tools/generate-slash-commands.js';
|
|
22
22
|
import { getStateResource } from './resources/state.js';
|
|
23
|
+
import { queryGraphDefinition, handleQueryGraph, searchSimilarDefinition, handleSearchSimilar, findContextForDefinition, handleFindContextFor, getRelationshipsDefinition, handleGetRelationships, getImpactDefinition, handleGetImpact, ingestSourceDefinition, handleIngestSource, } from './tools/graph.js';
|
|
24
|
+
import { getGraphResource, getEntitiesResource, getRelationshipsResource, } from './resources/graph.js';
|
|
25
|
+
import { generateAgentDefinitionsDefinition, handleGenerateAgentDefinitions, } from './tools/agent-definitions.js';
|
|
23
26
|
const TOOL_DEFINITIONS = [
|
|
24
27
|
validateToolDefinition,
|
|
25
28
|
checkDependenciesDefinition,
|
|
@@ -45,6 +48,13 @@ const TOOL_DEFINITIONS = [
|
|
|
45
48
|
validateCrossCheckDefinition,
|
|
46
49
|
createSkillDefinition,
|
|
47
50
|
generateSlashCommandsDefinition,
|
|
51
|
+
queryGraphDefinition,
|
|
52
|
+
searchSimilarDefinition,
|
|
53
|
+
findContextForDefinition,
|
|
54
|
+
getRelationshipsDefinition,
|
|
55
|
+
getImpactDefinition,
|
|
56
|
+
ingestSourceDefinition,
|
|
57
|
+
generateAgentDefinitionsDefinition,
|
|
48
58
|
];
|
|
49
59
|
const TOOL_HANDLERS = {
|
|
50
60
|
validate_project: handleValidateProject,
|
|
@@ -71,6 +81,13 @@ const TOOL_HANDLERS = {
|
|
|
71
81
|
validate_cross_check: handleValidateCrossCheck,
|
|
72
82
|
create_skill: handleCreateSkill,
|
|
73
83
|
generate_slash_commands: handleGenerateSlashCommands,
|
|
84
|
+
query_graph: handleQueryGraph,
|
|
85
|
+
search_similar: handleSearchSimilar,
|
|
86
|
+
find_context_for: handleFindContextFor,
|
|
87
|
+
get_relationships: handleGetRelationships,
|
|
88
|
+
get_impact: handleGetImpact,
|
|
89
|
+
ingest_source: handleIngestSource,
|
|
90
|
+
generate_agent_definitions: handleGenerateAgentDefinitions,
|
|
74
91
|
};
|
|
75
92
|
const RESOURCE_DEFINITIONS = [
|
|
76
93
|
{
|
|
@@ -103,6 +120,24 @@ const RESOURCE_DEFINITIONS = [
|
|
|
103
120
|
description: 'Current harness state including position, progress, decisions, and blockers',
|
|
104
121
|
mimeType: 'application/json',
|
|
105
122
|
},
|
|
123
|
+
{
|
|
124
|
+
uri: 'harness://graph',
|
|
125
|
+
name: 'Knowledge Graph',
|
|
126
|
+
description: 'Graph statistics, node/edge counts by type, staleness',
|
|
127
|
+
mimeType: 'application/json',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
uri: 'harness://entities',
|
|
131
|
+
name: 'Graph Entities',
|
|
132
|
+
description: 'All entity nodes with types and metadata',
|
|
133
|
+
mimeType: 'application/json',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
uri: 'harness://relationships',
|
|
137
|
+
name: 'Graph Relationships',
|
|
138
|
+
description: 'All edges with types, confidence scores, and timestamps',
|
|
139
|
+
mimeType: 'application/json',
|
|
140
|
+
},
|
|
106
141
|
];
|
|
107
142
|
const RESOURCE_HANDLERS = {
|
|
108
143
|
'harness://skills': getSkillsResource,
|
|
@@ -110,6 +145,9 @@ const RESOURCE_HANDLERS = {
|
|
|
110
145
|
'harness://project': getProjectResource,
|
|
111
146
|
'harness://learnings': getLearningsResource,
|
|
112
147
|
'harness://state': getStateResource,
|
|
148
|
+
'harness://graph': getGraphResource,
|
|
149
|
+
'harness://entities': getEntitiesResource,
|
|
150
|
+
'harness://relationships': getRelationshipsResource,
|
|
113
151
|
};
|
|
114
152
|
export function getToolDefinitions() {
|
|
115
153
|
return TOOL_DEFINITIONS;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const generateAgentDefinitionsDefinition: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
global: {
|
|
8
|
+
type: string;
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
platform: {
|
|
12
|
+
type: string;
|
|
13
|
+
enum: string[];
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
dryRun: {
|
|
17
|
+
type: string;
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export declare function handleGenerateAgentDefinitions(input: {
|
|
24
|
+
global?: boolean;
|
|
25
|
+
platform?: string;
|
|
26
|
+
dryRun?: boolean;
|
|
27
|
+
}): Promise<import("../utils/result-adapter.js").McpToolResponse>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Ok } from '@harness-engineering/core';
|
|
2
|
+
import { resultToMcpResponse } from '../utils/result-adapter.js';
|
|
3
|
+
export const generateAgentDefinitionsDefinition = {
|
|
4
|
+
name: 'generate_agent_definitions',
|
|
5
|
+
description: 'Generate agent definition files from personas for Claude Code and Gemini CLI',
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
global: { type: 'boolean', description: 'Write to global agent directory' },
|
|
10
|
+
platform: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
enum: ['claude-code', 'gemini-cli', 'all'],
|
|
13
|
+
description: 'Target platform (default: all)',
|
|
14
|
+
},
|
|
15
|
+
dryRun: { type: 'boolean', description: 'Preview without writing' },
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
export async function handleGenerateAgentDefinitions(input) {
|
|
20
|
+
const { generateAgentDefinitions } = await import('@harness-engineering/cli');
|
|
21
|
+
const platforms = input.platform === 'all' || !input.platform
|
|
22
|
+
? ['claude-code', 'gemini-cli']
|
|
23
|
+
: [input.platform];
|
|
24
|
+
const results = generateAgentDefinitions({
|
|
25
|
+
platforms: [...platforms],
|
|
26
|
+
global: input.global ?? false,
|
|
27
|
+
dryRun: input.dryRun ?? false,
|
|
28
|
+
});
|
|
29
|
+
return resultToMcpResponse(Ok(results));
|
|
30
|
+
}
|
|
@@ -28,7 +28,25 @@ export async function handleCheckDependencies(input) {
|
|
|
28
28
|
allowedDependencies: l.allowedDependencies,
|
|
29
29
|
}));
|
|
30
30
|
const parser = new TypeScriptParser();
|
|
31
|
-
|
|
31
|
+
// Attempt to load graph for enhanced validation
|
|
32
|
+
const { loadGraphStore } = await import('../utils/graph-loader.js');
|
|
33
|
+
const store = await loadGraphStore(projectPath);
|
|
34
|
+
let graphDependencyData;
|
|
35
|
+
if (store) {
|
|
36
|
+
const { GraphConstraintAdapter } = await import('@harness-engineering/graph');
|
|
37
|
+
const adapter = new GraphConstraintAdapter(store);
|
|
38
|
+
const graphData = adapter.computeDependencyGraph();
|
|
39
|
+
graphDependencyData = {
|
|
40
|
+
nodes: [...graphData.nodes],
|
|
41
|
+
edges: graphData.edges.map((e) => ({ ...e })),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const result = await validateDependencies({
|
|
45
|
+
layers,
|
|
46
|
+
rootDir: projectPath,
|
|
47
|
+
parser,
|
|
48
|
+
graphDependencyData,
|
|
49
|
+
});
|
|
32
50
|
return resultToMcpResponse(result);
|
|
33
51
|
}
|
|
34
52
|
catch (error) {
|
package/dist/src/tools/docs.js
CHANGED
|
@@ -16,9 +16,24 @@ export async function handleCheckDocs(input) {
|
|
|
16
16
|
try {
|
|
17
17
|
const { checkDocCoverage } = await import('@harness-engineering/core');
|
|
18
18
|
const domain = input.domain ?? 'src';
|
|
19
|
+
// Attempt to load graph for enhanced coverage analysis
|
|
20
|
+
const { loadGraphStore } = await import('../utils/graph-loader.js');
|
|
21
|
+
const store = await loadGraphStore(path.resolve(input.path));
|
|
22
|
+
let graphCoverage;
|
|
23
|
+
if (store) {
|
|
24
|
+
const { Assembler } = await import('@harness-engineering/graph');
|
|
25
|
+
const assembler = new Assembler(store);
|
|
26
|
+
const report = assembler.checkCoverage();
|
|
27
|
+
graphCoverage = {
|
|
28
|
+
documented: [...report.documented],
|
|
29
|
+
undocumented: [...report.undocumented],
|
|
30
|
+
coveragePercentage: report.coveragePercentage,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
19
33
|
const result = await checkDocCoverage(domain, {
|
|
20
34
|
sourceDir: path.resolve(input.path, 'src'),
|
|
21
35
|
docsDir: path.resolve(input.path, 'docs'),
|
|
36
|
+
graphCoverage,
|
|
22
37
|
});
|
|
23
38
|
return resultToMcpResponse(result);
|
|
24
39
|
}
|
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import { Ok } from '@harness-engineering/core';
|
|
3
3
|
import { resultToMcpResponse } from '../utils/result-adapter.js';
|
|
4
|
+
async function loadEntropyGraphOptions(projectPath) {
|
|
5
|
+
const { loadGraphStore } = await import('../utils/graph-loader.js');
|
|
6
|
+
const store = await loadGraphStore(projectPath);
|
|
7
|
+
if (!store)
|
|
8
|
+
return undefined;
|
|
9
|
+
const { GraphEntropyAdapter } = await import('@harness-engineering/graph');
|
|
10
|
+
const adapter = new GraphEntropyAdapter(store);
|
|
11
|
+
const driftData = adapter.computeDriftData();
|
|
12
|
+
const deadCodeData = adapter.computeDeadCodeData();
|
|
13
|
+
return {
|
|
14
|
+
graphDriftData: {
|
|
15
|
+
staleEdges: driftData.staleEdges.map((e) => ({
|
|
16
|
+
docNodeId: e.docNodeId,
|
|
17
|
+
codeNodeId: e.codeNodeId,
|
|
18
|
+
edgeType: e.edgeType,
|
|
19
|
+
})),
|
|
20
|
+
missingTargets: [...driftData.missingTargets],
|
|
21
|
+
},
|
|
22
|
+
graphDeadCodeData: {
|
|
23
|
+
reachableNodeIds: new Set(deadCodeData.reachableNodeIds),
|
|
24
|
+
unreachableNodes: deadCodeData.unreachableNodes.map((n) => ({
|
|
25
|
+
id: n.id,
|
|
26
|
+
type: n.type,
|
|
27
|
+
name: n.name,
|
|
28
|
+
path: n.path,
|
|
29
|
+
})),
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
4
33
|
export const detectEntropyDefinition = {
|
|
5
34
|
name: 'detect_entropy',
|
|
6
35
|
description: 'Detect documentation drift, dead code, and pattern violations',
|
|
@@ -29,7 +58,8 @@ export async function handleDetectEntropy(input) {
|
|
|
29
58
|
patterns: typeFilter === 'all' || typeFilter === 'patterns',
|
|
30
59
|
},
|
|
31
60
|
});
|
|
32
|
-
const
|
|
61
|
+
const graphOptions = await loadEntropyGraphOptions(path.resolve(input.path));
|
|
62
|
+
const result = await analyzer.analyze(graphOptions);
|
|
33
63
|
return resultToMcpResponse(result);
|
|
34
64
|
}
|
|
35
65
|
catch (error) {
|
|
@@ -63,7 +93,8 @@ export async function handleApplyFixes(input) {
|
|
|
63
93
|
rootDir: path.resolve(input.path),
|
|
64
94
|
analyze: { drift: true, deadCode: true, patterns: true },
|
|
65
95
|
});
|
|
66
|
-
const
|
|
96
|
+
const graphOptions = await loadEntropyGraphOptions(path.resolve(input.path));
|
|
97
|
+
const analysisResult = await analyzer.analyze(graphOptions);
|
|
67
98
|
if (!analysisResult.ok)
|
|
68
99
|
return resultToMcpResponse(analysisResult);
|
|
69
100
|
const report = analysisResult.value;
|
|
@@ -48,6 +48,10 @@ export declare const analyzeDiffDefinition: {
|
|
|
48
48
|
type: string;
|
|
49
49
|
description: string;
|
|
50
50
|
};
|
|
51
|
+
path: {
|
|
52
|
+
type: string;
|
|
53
|
+
description: string;
|
|
54
|
+
};
|
|
51
55
|
forbiddenPatterns: {
|
|
52
56
|
type: string;
|
|
53
57
|
items: {
|
|
@@ -69,6 +73,7 @@ export declare const analyzeDiffDefinition: {
|
|
|
69
73
|
};
|
|
70
74
|
export declare function handleAnalyzeDiff(input: {
|
|
71
75
|
diff: string;
|
|
76
|
+
path?: string;
|
|
72
77
|
forbiddenPatterns?: string[];
|
|
73
78
|
maxFileSize?: number;
|
|
74
79
|
maxFileCount?: number;
|
|
@@ -47,7 +47,26 @@ export async function handleCreateSelfReview(input) {
|
|
|
47
47
|
...(input.maxFileCount !== undefined ? { maxChangedFiles: input.maxFileCount } : {}),
|
|
48
48
|
},
|
|
49
49
|
};
|
|
50
|
-
|
|
50
|
+
// Attempt to load graph for enhanced review
|
|
51
|
+
const { loadGraphStore } = await import('../utils/graph-loader.js');
|
|
52
|
+
const store = await loadGraphStore(path.resolve(input.path));
|
|
53
|
+
let graphData;
|
|
54
|
+
if (store) {
|
|
55
|
+
const { GraphFeedbackAdapter } = await import('@harness-engineering/graph');
|
|
56
|
+
const adapter = new GraphFeedbackAdapter(store);
|
|
57
|
+
const changedFiles = parseResult.value.files.map((f) => f.path);
|
|
58
|
+
const impact = adapter.computeImpactData(changedFiles);
|
|
59
|
+
const harness = adapter.computeHarnessCheckData();
|
|
60
|
+
graphData = {
|
|
61
|
+
impact: {
|
|
62
|
+
affectedTests: [...impact.affectedTests],
|
|
63
|
+
affectedDocs: [...impact.affectedDocs],
|
|
64
|
+
impactScope: impact.impactScope,
|
|
65
|
+
},
|
|
66
|
+
harness: { ...harness },
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const result = await createSelfReview(parseResult.value, config, graphData);
|
|
51
70
|
return resultToMcpResponse(result);
|
|
52
71
|
}
|
|
53
72
|
catch (error) {
|
|
@@ -70,6 +89,10 @@ export const analyzeDiffDefinition = {
|
|
|
70
89
|
type: 'object',
|
|
71
90
|
properties: {
|
|
72
91
|
diff: { type: 'string', description: 'Git diff string to analyze' },
|
|
92
|
+
path: {
|
|
93
|
+
type: 'string',
|
|
94
|
+
description: 'Path to project root (enables graph-enhanced analysis)',
|
|
95
|
+
},
|
|
73
96
|
forbiddenPatterns: {
|
|
74
97
|
type: 'array',
|
|
75
98
|
items: { type: 'string' },
|
|
@@ -108,7 +131,28 @@ export async function handleAnalyzeDiff(input) {
|
|
|
108
131
|
...(input.maxFileSize !== undefined ? { maxFileSize: input.maxFileSize } : {}),
|
|
109
132
|
...(input.maxFileCount !== undefined ? { maxChangedFiles: input.maxFileCount } : {}),
|
|
110
133
|
};
|
|
111
|
-
|
|
134
|
+
let graphImpactData;
|
|
135
|
+
if (input.path) {
|
|
136
|
+
try {
|
|
137
|
+
const { loadGraphStore } = await import('../utils/graph-loader.js');
|
|
138
|
+
const store = await loadGraphStore(path.resolve(input.path));
|
|
139
|
+
if (store) {
|
|
140
|
+
const { GraphFeedbackAdapter } = await import('@harness-engineering/graph');
|
|
141
|
+
const adapter = new GraphFeedbackAdapter(store);
|
|
142
|
+
const changedFiles = parseResult.value.files.map((f) => f.path);
|
|
143
|
+
const impact = adapter.computeImpactData(changedFiles);
|
|
144
|
+
graphImpactData = {
|
|
145
|
+
affectedTests: [...impact.affectedTests],
|
|
146
|
+
affectedDocs: [...impact.affectedDocs],
|
|
147
|
+
impactScope: impact.impactScope,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Graph loading is optional — continue without it
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const result = await analyzeDiff(parseResult.value, options, graphImpactData);
|
|
112
156
|
return resultToMcpResponse(result);
|
|
113
157
|
}
|
|
114
158
|
catch (error) {
|
|
@@ -160,6 +204,24 @@ export async function handleRequestPeerReview(input) {
|
|
|
160
204
|
diff: input.diff,
|
|
161
205
|
...(input.context ? { metadata: { context: input.context } } : {}),
|
|
162
206
|
};
|
|
207
|
+
// Attempt to load graph for enhanced context
|
|
208
|
+
try {
|
|
209
|
+
const { loadGraphStore } = await import('../utils/graph-loader.js');
|
|
210
|
+
const store = await loadGraphStore(path.resolve(input.path));
|
|
211
|
+
if (store) {
|
|
212
|
+
const { GraphFeedbackAdapter } = await import('@harness-engineering/graph');
|
|
213
|
+
const adapter = new GraphFeedbackAdapter(store);
|
|
214
|
+
const changedFiles = parseResult.value.files.map((f) => f.path);
|
|
215
|
+
const impactData = adapter.computeImpactData(changedFiles);
|
|
216
|
+
reviewContext.metadata = {
|
|
217
|
+
...reviewContext.metadata,
|
|
218
|
+
graphContext: impactData,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// Graph loading is optional
|
|
224
|
+
}
|
|
163
225
|
const result = await requestPeerReview(input.agentType, reviewContext, {
|
|
164
226
|
timeout: 120_000,
|
|
165
227
|
wait: true,
|
|
@@ -21,6 +21,10 @@ export declare const generateSlashCommandsDefinition: {
|
|
|
21
21
|
type: string;
|
|
22
22
|
description: string;
|
|
23
23
|
};
|
|
24
|
+
includeGlobal: {
|
|
25
|
+
type: string;
|
|
26
|
+
description: string;
|
|
27
|
+
};
|
|
24
28
|
dryRun: {
|
|
25
29
|
type: string;
|
|
26
30
|
description: string;
|
|
@@ -31,6 +35,7 @@ export declare const generateSlashCommandsDefinition: {
|
|
|
31
35
|
export declare function handleGenerateSlashCommands(input: {
|
|
32
36
|
platforms?: string;
|
|
33
37
|
global?: boolean;
|
|
38
|
+
includeGlobal?: boolean;
|
|
34
39
|
output?: string;
|
|
35
40
|
skillsDir?: string;
|
|
36
41
|
dryRun?: boolean;
|
|
@@ -23,6 +23,10 @@ export const generateSlashCommandsDefinition = {
|
|
|
23
23
|
type: 'string',
|
|
24
24
|
description: 'Skills directory to scan',
|
|
25
25
|
},
|
|
26
|
+
includeGlobal: {
|
|
27
|
+
type: 'boolean',
|
|
28
|
+
description: 'Include built-in global skills alongside project skills',
|
|
29
|
+
},
|
|
26
30
|
dryRun: {
|
|
27
31
|
type: 'boolean',
|
|
28
32
|
description: 'Show what would change without writing files',
|
|
@@ -38,6 +42,7 @@ export async function handleGenerateSlashCommands(input) {
|
|
|
38
42
|
const results = generateSlashCommands({
|
|
39
43
|
platforms,
|
|
40
44
|
global: input.global ?? false,
|
|
45
|
+
includeGlobal: input.includeGlobal ?? false,
|
|
41
46
|
output: input.output,
|
|
42
47
|
skillsDir: input.skillsDir ?? '',
|
|
43
48
|
dryRun: input.dryRun ?? false,
|