@harness-engineering/mcp-server 0.5.3 → 0.6.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/server.js +48 -1
- package/dist/src/tools/agent.d.ts +2 -2
- package/dist/src/tools/agent.js +31 -3
- package/dist/src/tools/architecture.js +16 -2
- package/dist/src/tools/cross-check.js +32 -3
- package/dist/src/tools/docs.js +6 -4
- package/dist/src/tools/entropy.d.ts +9 -0
- package/dist/src/tools/entropy.js +25 -6
- package/dist/src/tools/feedback.js +6 -5
- package/dist/src/tools/generate-slash-commands.js +3 -2
- package/dist/src/tools/graph.js +1 -8
- package/dist/src/tools/init.js +4 -2
- package/dist/src/tools/interaction.d.ts +130 -0
- package/dist/src/tools/interaction.js +235 -0
- package/dist/src/tools/linter.js +6 -2
- package/dist/src/tools/performance.js +7 -6
- package/dist/src/tools/persona.js +2 -1
- package/dist/src/tools/phase-gate.js +2 -2
- package/dist/src/tools/review-pipeline.d.ts +63 -0
- package/dist/src/tools/review-pipeline.js +114 -0
- package/dist/src/tools/roadmap.d.ts +82 -0
- package/dist/src/tools/roadmap.js +390 -0
- package/dist/src/tools/security.js +2 -1
- package/dist/src/tools/skill.js +3 -2
- package/dist/src/tools/state.js +4 -4
- package/dist/src/tools/validate.d.ts +7 -0
- package/dist/src/tools/validate.js +16 -1
- package/dist/src/utils/sanitize-path.d.ts +5 -0
- package/dist/src/utils/sanitize-path.js +12 -0
- package/package.json +5 -5
package/dist/src/server.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import { resolveProjectConfig } from './utils/config-resolver.js';
|
|
4
5
|
import { validateToolDefinition, handleValidateProject } from './tools/validate.js';
|
|
5
6
|
import { checkDependenciesDefinition, handleCheckDependencies } from './tools/architecture.js';
|
|
6
7
|
import { checkDocsDefinition, handleCheckDocs, validateKnowledgeMapDefinition, handleValidateKnowledgeMap, } from './tools/docs.js';
|
|
@@ -25,6 +26,9 @@ import { queryGraphDefinition, handleQueryGraph, searchSimilarDefinition, handle
|
|
|
25
26
|
import { getGraphResource, getEntitiesResource, getRelationshipsResource, } from './resources/graph.js';
|
|
26
27
|
import { generateAgentDefinitionsDefinition, handleGenerateAgentDefinitions, } from './tools/agent-definitions.js';
|
|
27
28
|
import { runSecurityScanDefinition, handleRunSecurityScan } from './tools/security.js';
|
|
29
|
+
import { manageRoadmapDefinition, handleManageRoadmap } from './tools/roadmap.js';
|
|
30
|
+
import { emitInteractionDefinition, handleEmitInteraction } from './tools/interaction.js';
|
|
31
|
+
import { runCodeReviewDefinition, handleRunCodeReview } from './tools/review-pipeline.js';
|
|
28
32
|
const TOOL_DEFINITIONS = [
|
|
29
33
|
validateToolDefinition,
|
|
30
34
|
checkDependenciesDefinition,
|
|
@@ -63,6 +67,9 @@ const TOOL_DEFINITIONS = [
|
|
|
63
67
|
updatePerfBaselinesDefinition,
|
|
64
68
|
getCriticalPathsDefinition,
|
|
65
69
|
listStreamsDefinition,
|
|
70
|
+
manageRoadmapDefinition,
|
|
71
|
+
emitInteractionDefinition,
|
|
72
|
+
runCodeReviewDefinition,
|
|
66
73
|
];
|
|
67
74
|
const TOOL_HANDLERS = {
|
|
68
75
|
validate_project: handleValidateProject,
|
|
@@ -102,6 +109,9 @@ const TOOL_HANDLERS = {
|
|
|
102
109
|
update_perf_baselines: handleUpdatePerfBaselines,
|
|
103
110
|
get_critical_paths: handleGetCriticalPaths,
|
|
104
111
|
list_streams: handleListStreams,
|
|
112
|
+
manage_roadmap: handleManageRoadmap,
|
|
113
|
+
emit_interaction: handleEmitInteraction,
|
|
114
|
+
run_code_review: handleRunCodeReview,
|
|
105
115
|
};
|
|
106
116
|
const RESOURCE_DEFINITIONS = [
|
|
107
117
|
{
|
|
@@ -171,6 +181,7 @@ export function getResourceDefinitions() {
|
|
|
171
181
|
}
|
|
172
182
|
export function createHarnessServer(projectRoot) {
|
|
173
183
|
const resolvedRoot = projectRoot ?? process.cwd();
|
|
184
|
+
let sessionChecked = false;
|
|
174
185
|
const server = new Server({ name: 'harness-engineering', version: '0.1.0' }, { capabilities: { tools: {}, resources: {} } });
|
|
175
186
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
176
187
|
tools: TOOL_DEFINITIONS,
|
|
@@ -181,7 +192,43 @@ export function createHarnessServer(projectRoot) {
|
|
|
181
192
|
if (!handler) {
|
|
182
193
|
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
183
194
|
}
|
|
184
|
-
|
|
195
|
+
const result = await handler(args ?? {});
|
|
196
|
+
// On first tool invocation per session, check for updates
|
|
197
|
+
if (!sessionChecked) {
|
|
198
|
+
sessionChecked = true;
|
|
199
|
+
try {
|
|
200
|
+
const { getUpdateNotification, isUpdateCheckEnabled, shouldRunCheck, readCheckState, spawnBackgroundCheck, VERSION, } = await import('@harness-engineering/core');
|
|
201
|
+
// Read updateCheckInterval from project config (if available)
|
|
202
|
+
let configInterval;
|
|
203
|
+
try {
|
|
204
|
+
const configResult = resolveProjectConfig(resolvedRoot);
|
|
205
|
+
if (configResult.ok) {
|
|
206
|
+
const raw = configResult.value.updateCheckInterval;
|
|
207
|
+
if (typeof raw === 'number' && Number.isInteger(raw) && raw >= 0) {
|
|
208
|
+
configInterval = raw;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// Config read failure is non-fatal for update checks
|
|
214
|
+
}
|
|
215
|
+
const DEFAULT_INTERVAL = 86_400_000; // 24 hours
|
|
216
|
+
if (isUpdateCheckEnabled(configInterval)) {
|
|
217
|
+
const state = readCheckState();
|
|
218
|
+
if (shouldRunCheck(state, configInterval ?? DEFAULT_INTERVAL)) {
|
|
219
|
+
spawnBackgroundCheck(VERSION);
|
|
220
|
+
}
|
|
221
|
+
const notification = getUpdateNotification(VERSION);
|
|
222
|
+
if (notification) {
|
|
223
|
+
result.content.push({ type: 'text', text: `\n---\n${notification}` });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
// Graceful degradation — update check failures must never break tool responses
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
185
232
|
});
|
|
186
233
|
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
187
234
|
resources: RESOURCE_DEFINITIONS,
|
|
@@ -69,11 +69,11 @@ export declare function handleRunAgentTask(input: {
|
|
|
69
69
|
type: "text";
|
|
70
70
|
text: string;
|
|
71
71
|
}[];
|
|
72
|
-
isError
|
|
72
|
+
isError: boolean;
|
|
73
73
|
} | {
|
|
74
74
|
content: {
|
|
75
75
|
type: "text";
|
|
76
76
|
text: string;
|
|
77
77
|
}[];
|
|
78
|
-
isError
|
|
78
|
+
isError?: undefined;
|
|
79
79
|
}>;
|
package/dist/src/tools/agent.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { sanitizePath } from '../utils/sanitize-path.js';
|
|
2
2
|
export const addComponentDefinition = {
|
|
3
3
|
name: 'add_component',
|
|
4
4
|
description: 'Add a component (layer, doc, or component type) to the project using the harness CLI',
|
|
@@ -16,8 +16,9 @@ export const addComponentDefinition = {
|
|
|
16
16
|
required: ['path', 'type', 'name'],
|
|
17
17
|
},
|
|
18
18
|
};
|
|
19
|
+
const COMPONENT_NAME_REGEX = /^[a-z0-9][a-z0-9._-]{0,64}$/i;
|
|
19
20
|
export async function handleAddComponent(input) {
|
|
20
|
-
const projectPath =
|
|
21
|
+
const projectPath = sanitizePath(input.path);
|
|
21
22
|
const ALLOWED_TYPES = new Set(['layer', 'doc', 'component']);
|
|
22
23
|
if (!ALLOWED_TYPES.has(input.type)) {
|
|
23
24
|
return {
|
|
@@ -30,6 +31,19 @@ export async function handleAddComponent(input) {
|
|
|
30
31
|
isError: true,
|
|
31
32
|
};
|
|
32
33
|
}
|
|
34
|
+
if (!COMPONENT_NAME_REGEX.test(input.name)) {
|
|
35
|
+
return {
|
|
36
|
+
content: [
|
|
37
|
+
{
|
|
38
|
+
type: 'text',
|
|
39
|
+
text: JSON.stringify({
|
|
40
|
+
error: `Invalid component name: must match ${COMPONENT_NAME_REGEX} (lowercase alphanumeric, dots, hyphens, underscores, max 65 chars)`,
|
|
41
|
+
}),
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
isError: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
33
47
|
try {
|
|
34
48
|
const { execFileSync } = await import('node:child_process');
|
|
35
49
|
const output = execFileSync('npx', ['harness', 'add', input.type, input.name], {
|
|
@@ -67,8 +81,22 @@ export const runAgentTaskDefinition = {
|
|
|
67
81
|
required: ['task'],
|
|
68
82
|
},
|
|
69
83
|
};
|
|
84
|
+
const ALLOWED_AGENT_TASKS = new Set(['review', 'doc-review', 'test-review']);
|
|
70
85
|
export async function handleRunAgentTask(input) {
|
|
71
|
-
|
|
86
|
+
if (!ALLOWED_AGENT_TASKS.has(input.task)) {
|
|
87
|
+
return {
|
|
88
|
+
content: [
|
|
89
|
+
{
|
|
90
|
+
type: 'text',
|
|
91
|
+
text: JSON.stringify({
|
|
92
|
+
error: `Invalid task: "${input.task}". Allowed tasks: ${[...ALLOWED_AGENT_TASKS].join(', ')}`,
|
|
93
|
+
}),
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
isError: true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const projectPath = input.path ? sanitizePath(input.path) : process.cwd();
|
|
72
100
|
try {
|
|
73
101
|
const { execFileSync } = await import('node:child_process');
|
|
74
102
|
const output = execFileSync('npx', ['harness', 'agent', 'run', input.task], {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
1
|
import { resultToMcpResponse } from '../utils/result-adapter.js';
|
|
3
2
|
import { resolveProjectConfig } from '../utils/config-resolver.js';
|
|
3
|
+
import { sanitizePath } from '../utils/sanitize-path.js';
|
|
4
4
|
export const checkDependenciesDefinition = {
|
|
5
5
|
name: 'check_dependencies',
|
|
6
6
|
description: 'Validate layer boundaries and detect circular dependencies',
|
|
@@ -13,7 +13,21 @@ export const checkDependenciesDefinition = {
|
|
|
13
13
|
},
|
|
14
14
|
};
|
|
15
15
|
export async function handleCheckDependencies(input) {
|
|
16
|
-
|
|
16
|
+
let projectPath;
|
|
17
|
+
try {
|
|
18
|
+
projectPath = sanitizePath(input.path);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: 'text',
|
|
25
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
isError: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
17
31
|
const configResult = resolveProjectConfig(projectPath);
|
|
18
32
|
if (!configResult.ok)
|
|
19
33
|
return resultToMcpResponse(configResult);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
|
+
import { sanitizePath } from '../utils/sanitize-path.js';
|
|
2
3
|
export const validateCrossCheckDefinition = {
|
|
3
4
|
name: 'validate_cross_check',
|
|
4
5
|
description: 'Validate plan-to-implementation coverage: checks that specs have plans and plans have implementations, detects staleness',
|
|
@@ -19,13 +20,41 @@ export const validateCrossCheckDefinition = {
|
|
|
19
20
|
},
|
|
20
21
|
};
|
|
21
22
|
export async function handleValidateCrossCheck(input) {
|
|
22
|
-
|
|
23
|
+
let projectPath;
|
|
24
|
+
try {
|
|
25
|
+
projectPath = sanitizePath(input.path);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return {
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
type: 'text',
|
|
32
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
isError: true,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
23
38
|
try {
|
|
24
39
|
const { runCrossCheck } = await import('@harness-engineering/cli');
|
|
40
|
+
const specsDir = path.resolve(projectPath, input.specsDir ?? 'docs/specs');
|
|
41
|
+
if (!specsDir.startsWith(projectPath)) {
|
|
42
|
+
return {
|
|
43
|
+
content: [{ type: 'text', text: 'Error: specsDir escapes project root' }],
|
|
44
|
+
isError: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const plansDir = path.resolve(projectPath, input.plansDir ?? 'docs/plans');
|
|
48
|
+
if (!plansDir.startsWith(projectPath)) {
|
|
49
|
+
return {
|
|
50
|
+
content: [{ type: 'text', text: 'Error: plansDir escapes project root' }],
|
|
51
|
+
isError: true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
25
54
|
const result = await runCrossCheck({
|
|
26
55
|
projectPath,
|
|
27
|
-
specsDir
|
|
28
|
-
plansDir
|
|
56
|
+
specsDir,
|
|
57
|
+
plansDir,
|
|
29
58
|
});
|
|
30
59
|
if (result.ok) {
|
|
31
60
|
return { content: [{ type: 'text', text: JSON.stringify(result.value) }] };
|
package/dist/src/tools/docs.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import { resultToMcpResponse } from '../utils/result-adapter.js';
|
|
3
|
+
import { sanitizePath } from '../utils/sanitize-path.js';
|
|
3
4
|
export const checkDocsDefinition = {
|
|
4
5
|
name: 'check_docs',
|
|
5
6
|
description: 'Analyze documentation coverage',
|
|
@@ -18,7 +19,8 @@ export async function handleCheckDocs(input) {
|
|
|
18
19
|
const domain = input.domain ?? 'src';
|
|
19
20
|
// Attempt to load graph for enhanced coverage analysis
|
|
20
21
|
const { loadGraphStore } = await import('../utils/graph-loader.js');
|
|
21
|
-
const
|
|
22
|
+
const projectPath = sanitizePath(input.path);
|
|
23
|
+
const store = await loadGraphStore(projectPath);
|
|
22
24
|
let graphCoverage;
|
|
23
25
|
if (store) {
|
|
24
26
|
const { Assembler } = await import('@harness-engineering/graph');
|
|
@@ -31,8 +33,8 @@ export async function handleCheckDocs(input) {
|
|
|
31
33
|
};
|
|
32
34
|
}
|
|
33
35
|
const result = await checkDocCoverage(domain, {
|
|
34
|
-
sourceDir: path.resolve(
|
|
35
|
-
docsDir: path.resolve(
|
|
36
|
+
sourceDir: path.resolve(projectPath, 'src'),
|
|
37
|
+
docsDir: path.resolve(projectPath, 'docs'),
|
|
36
38
|
graphCoverage,
|
|
37
39
|
});
|
|
38
40
|
return resultToMcpResponse(result);
|
|
@@ -63,7 +65,7 @@ export const validateKnowledgeMapDefinition = {
|
|
|
63
65
|
export async function handleValidateKnowledgeMap(input) {
|
|
64
66
|
try {
|
|
65
67
|
const { validateKnowledgeMap } = await import('@harness-engineering/core');
|
|
66
|
-
const result = await validateKnowledgeMap(
|
|
68
|
+
const result = await validateKnowledgeMap(sanitizePath(input.path));
|
|
67
69
|
return resultToMcpResponse(result);
|
|
68
70
|
}
|
|
69
71
|
catch (error) {
|
|
@@ -35,6 +35,14 @@ export declare const applyFixesDefinition: {
|
|
|
35
35
|
type: string;
|
|
36
36
|
description: string;
|
|
37
37
|
};
|
|
38
|
+
fixTypes: {
|
|
39
|
+
type: string;
|
|
40
|
+
items: {
|
|
41
|
+
type: string;
|
|
42
|
+
enum: string[];
|
|
43
|
+
};
|
|
44
|
+
description: string;
|
|
45
|
+
};
|
|
38
46
|
};
|
|
39
47
|
required: string[];
|
|
40
48
|
};
|
|
@@ -42,4 +50,5 @@ export declare const applyFixesDefinition: {
|
|
|
42
50
|
export declare function handleApplyFixes(input: {
|
|
43
51
|
path: string;
|
|
44
52
|
dryRun?: boolean;
|
|
53
|
+
fixTypes?: string[];
|
|
45
54
|
}): Promise<import("../utils/result-adapter.js").McpToolResponse>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
1
|
import { Ok } from '@harness-engineering/core';
|
|
3
2
|
import { resultToMcpResponse } from '../utils/result-adapter.js';
|
|
3
|
+
import { sanitizePath } from '../utils/sanitize-path.js';
|
|
4
4
|
async function loadEntropyGraphOptions(projectPath) {
|
|
5
5
|
const { loadGraphStore } = await import('../utils/graph-loader.js');
|
|
6
6
|
const store = await loadGraphStore(projectPath);
|
|
@@ -51,14 +51,14 @@ export async function handleDetectEntropy(input) {
|
|
|
51
51
|
const { EntropyAnalyzer } = await import('@harness-engineering/core');
|
|
52
52
|
const typeFilter = input.type ?? 'all';
|
|
53
53
|
const analyzer = new EntropyAnalyzer({
|
|
54
|
-
rootDir:
|
|
54
|
+
rootDir: sanitizePath(input.path),
|
|
55
55
|
analyze: {
|
|
56
56
|
drift: typeFilter === 'all' || typeFilter === 'drift',
|
|
57
57
|
deadCode: typeFilter === 'all' || typeFilter === 'dead-code',
|
|
58
58
|
patterns: typeFilter === 'all' || typeFilter === 'patterns',
|
|
59
59
|
},
|
|
60
60
|
});
|
|
61
|
-
const graphOptions = await loadEntropyGraphOptions(
|
|
61
|
+
const graphOptions = await loadEntropyGraphOptions(sanitizePath(input.path));
|
|
62
62
|
const result = await analyzer.analyze(graphOptions);
|
|
63
63
|
return resultToMcpResponse(result);
|
|
64
64
|
}
|
|
@@ -82,6 +82,22 @@ export const applyFixesDefinition = {
|
|
|
82
82
|
properties: {
|
|
83
83
|
path: { type: 'string', description: 'Path to project root' },
|
|
84
84
|
dryRun: { type: 'boolean', description: 'Preview fixes without applying' },
|
|
85
|
+
fixTypes: {
|
|
86
|
+
type: 'array',
|
|
87
|
+
items: {
|
|
88
|
+
type: 'string',
|
|
89
|
+
enum: [
|
|
90
|
+
'unused-imports',
|
|
91
|
+
'dead-files',
|
|
92
|
+
'dead-exports',
|
|
93
|
+
'commented-code',
|
|
94
|
+
'orphaned-deps',
|
|
95
|
+
'forbidden-import-replacement',
|
|
96
|
+
'import-ordering',
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
description: 'Specific fix types to apply (default: all safe types)',
|
|
100
|
+
},
|
|
85
101
|
},
|
|
86
102
|
required: ['path'],
|
|
87
103
|
},
|
|
@@ -90,16 +106,19 @@ export async function handleApplyFixes(input) {
|
|
|
90
106
|
try {
|
|
91
107
|
const { EntropyAnalyzer, createFixes, applyFixes, generateSuggestions } = await import('@harness-engineering/core');
|
|
92
108
|
const analyzer = new EntropyAnalyzer({
|
|
93
|
-
rootDir:
|
|
109
|
+
rootDir: sanitizePath(input.path),
|
|
94
110
|
analyze: { drift: true, deadCode: true, patterns: true },
|
|
95
111
|
});
|
|
96
|
-
const graphOptions = await loadEntropyGraphOptions(
|
|
112
|
+
const graphOptions = await loadEntropyGraphOptions(sanitizePath(input.path));
|
|
97
113
|
const analysisResult = await analyzer.analyze(graphOptions);
|
|
98
114
|
if (!analysisResult.ok)
|
|
99
115
|
return resultToMcpResponse(analysisResult);
|
|
100
116
|
const report = analysisResult.value;
|
|
101
117
|
const deadCode = report.deadCode;
|
|
102
|
-
const
|
|
118
|
+
const fixTypesConfig = input.fixTypes
|
|
119
|
+
? { fixTypes: input.fixTypes }
|
|
120
|
+
: undefined;
|
|
121
|
+
const fixes = deadCode ? createFixes(deadCode, fixTypesConfig) : [];
|
|
103
122
|
const suggestions = generateSuggestions(report.deadCode, report.drift, report.patterns);
|
|
104
123
|
if (input.dryRun) {
|
|
105
124
|
return { content: [{ type: 'text', text: JSON.stringify({ fixes, suggestions }) }] };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
1
|
import { resultToMcpResponse } from '../utils/result-adapter.js';
|
|
2
|
+
import { sanitizePath } from '../utils/sanitize-path.js';
|
|
3
3
|
// ============ create_self_review ============
|
|
4
4
|
export const createSelfReviewDefinition = {
|
|
5
5
|
name: 'create_self_review',
|
|
@@ -33,8 +33,9 @@ export async function handleCreateSelfReview(input) {
|
|
|
33
33
|
if (!parseResult.ok) {
|
|
34
34
|
return resultToMcpResponse(parseResult);
|
|
35
35
|
}
|
|
36
|
+
const projectPath = sanitizePath(input.path);
|
|
36
37
|
const config = {
|
|
37
|
-
rootDir:
|
|
38
|
+
rootDir: projectPath,
|
|
38
39
|
harness: {
|
|
39
40
|
context: true,
|
|
40
41
|
constraints: true,
|
|
@@ -49,7 +50,7 @@ export async function handleCreateSelfReview(input) {
|
|
|
49
50
|
};
|
|
50
51
|
// Attempt to load graph for enhanced review
|
|
51
52
|
const { loadGraphStore } = await import('../utils/graph-loader.js');
|
|
52
|
-
const store = await loadGraphStore(
|
|
53
|
+
const store = await loadGraphStore(projectPath);
|
|
53
54
|
let graphData;
|
|
54
55
|
if (store) {
|
|
55
56
|
const { GraphFeedbackAdapter } = await import('@harness-engineering/graph');
|
|
@@ -135,7 +136,7 @@ export async function handleAnalyzeDiff(input) {
|
|
|
135
136
|
if (input.path) {
|
|
136
137
|
try {
|
|
137
138
|
const { loadGraphStore } = await import('../utils/graph-loader.js');
|
|
138
|
-
const store = await loadGraphStore(
|
|
139
|
+
const store = await loadGraphStore(sanitizePath(input.path));
|
|
139
140
|
if (store) {
|
|
140
141
|
const { GraphFeedbackAdapter } = await import('@harness-engineering/graph');
|
|
141
142
|
const adapter = new GraphFeedbackAdapter(store);
|
|
@@ -207,7 +208,7 @@ export async function handleRequestPeerReview(input) {
|
|
|
207
208
|
// Attempt to load graph for enhanced context
|
|
208
209
|
try {
|
|
209
210
|
const { loadGraphStore } = await import('../utils/graph-loader.js');
|
|
210
|
-
const store = await loadGraphStore(
|
|
211
|
+
const store = await loadGraphStore(sanitizePath(input.path));
|
|
211
212
|
if (store) {
|
|
212
213
|
const { GraphFeedbackAdapter } = await import('@harness-engineering/graph');
|
|
213
214
|
const adapter = new GraphFeedbackAdapter(store);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Ok, Err } from '@harness-engineering/core';
|
|
2
2
|
import { generateSlashCommands } from '@harness-engineering/cli';
|
|
3
3
|
import { resultToMcpResponse } from '../utils/result-adapter.js';
|
|
4
|
+
import { sanitizePath } from '../utils/sanitize-path.js';
|
|
4
5
|
export const generateSlashCommandsDefinition = {
|
|
5
6
|
name: 'generate_slash_commands',
|
|
6
7
|
description: 'Generate native slash commands for Claude Code and Gemini CLI from harness skill metadata',
|
|
@@ -43,8 +44,8 @@ export async function handleGenerateSlashCommands(input) {
|
|
|
43
44
|
platforms,
|
|
44
45
|
global: input.global ?? false,
|
|
45
46
|
includeGlobal: input.includeGlobal ?? false,
|
|
46
|
-
output: input.output,
|
|
47
|
-
skillsDir: input.skillsDir
|
|
47
|
+
output: input.output ? sanitizePath(input.output) : undefined,
|
|
48
|
+
skillsDir: input.skillsDir ? sanitizePath(input.skillsDir) : '',
|
|
48
49
|
dryRun: input.dryRun ?? false,
|
|
49
50
|
yes: true,
|
|
50
51
|
});
|
package/dist/src/tools/graph.js
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import { loadGraphStore } from '../utils/graph-loader.js';
|
|
3
|
-
|
|
4
|
-
function sanitizePath(inputPath) {
|
|
5
|
-
const resolved = path.resolve(inputPath);
|
|
6
|
-
if (resolved === '/' || resolved === path.parse(resolved).root) {
|
|
7
|
-
throw new Error('Invalid project path: cannot use filesystem root');
|
|
8
|
-
}
|
|
9
|
-
return resolved;
|
|
10
|
-
}
|
|
3
|
+
import { sanitizePath } from '../utils/sanitize-path.js';
|
|
11
4
|
function graphNotFoundError() {
|
|
12
5
|
return {
|
|
13
6
|
content: [
|
package/dist/src/tools/init.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import { resultToMcpResponse } from '../utils/result-adapter.js';
|
|
3
3
|
import { resolveTemplatesDir } from '../utils/paths.js';
|
|
4
|
+
import { sanitizePath } from '../utils/sanitize-path.js';
|
|
4
5
|
export const initProjectDefinition = {
|
|
5
6
|
name: 'init_project',
|
|
6
7
|
description: 'Scaffold a new harness engineering project from a template',
|
|
@@ -29,14 +30,15 @@ export async function handleInitProject(input) {
|
|
|
29
30
|
const resolveResult = engine.resolveTemplate(level, input.framework);
|
|
30
31
|
if (!resolveResult.ok)
|
|
31
32
|
return resultToMcpResponse(resolveResult);
|
|
33
|
+
const safePath = sanitizePath(input.path);
|
|
32
34
|
const renderResult = engine.render(resolveResult.value, {
|
|
33
|
-
projectName: input.name ?? path.basename(
|
|
35
|
+
projectName: input.name ?? path.basename(safePath),
|
|
34
36
|
level,
|
|
35
37
|
framework: input.framework,
|
|
36
38
|
});
|
|
37
39
|
if (!renderResult.ok)
|
|
38
40
|
return resultToMcpResponse(renderResult);
|
|
39
|
-
const writeResult = engine.write(renderResult.value,
|
|
41
|
+
const writeResult = engine.write(renderResult.value, safePath, {
|
|
40
42
|
overwrite: false,
|
|
41
43
|
});
|
|
42
44
|
return resultToMcpResponse(writeResult);
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
export declare const emitInteractionDefinition: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
path: {
|
|
8
|
+
type: string;
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
type: {
|
|
12
|
+
type: string;
|
|
13
|
+
enum: string[];
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
stream: {
|
|
17
|
+
type: string;
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
question: {
|
|
21
|
+
type: string;
|
|
22
|
+
description: string;
|
|
23
|
+
properties: {
|
|
24
|
+
text: {
|
|
25
|
+
type: string;
|
|
26
|
+
description: string;
|
|
27
|
+
};
|
|
28
|
+
options: {
|
|
29
|
+
type: string;
|
|
30
|
+
items: {
|
|
31
|
+
type: string;
|
|
32
|
+
};
|
|
33
|
+
description: string;
|
|
34
|
+
};
|
|
35
|
+
default: {
|
|
36
|
+
type: string;
|
|
37
|
+
description: string;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
required: string[];
|
|
41
|
+
};
|
|
42
|
+
confirmation: {
|
|
43
|
+
type: string;
|
|
44
|
+
description: string;
|
|
45
|
+
properties: {
|
|
46
|
+
text: {
|
|
47
|
+
type: string;
|
|
48
|
+
description: string;
|
|
49
|
+
};
|
|
50
|
+
context: {
|
|
51
|
+
type: string;
|
|
52
|
+
description: string;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
required: string[];
|
|
56
|
+
};
|
|
57
|
+
transition: {
|
|
58
|
+
type: string;
|
|
59
|
+
description: string;
|
|
60
|
+
properties: {
|
|
61
|
+
completedPhase: {
|
|
62
|
+
type: string;
|
|
63
|
+
description: string;
|
|
64
|
+
};
|
|
65
|
+
suggestedNext: {
|
|
66
|
+
type: string;
|
|
67
|
+
description: string;
|
|
68
|
+
};
|
|
69
|
+
reason: {
|
|
70
|
+
type: string;
|
|
71
|
+
description: string;
|
|
72
|
+
};
|
|
73
|
+
artifacts: {
|
|
74
|
+
type: string;
|
|
75
|
+
items: {
|
|
76
|
+
type: string;
|
|
77
|
+
};
|
|
78
|
+
description: string;
|
|
79
|
+
};
|
|
80
|
+
requiresConfirmation: {
|
|
81
|
+
type: string;
|
|
82
|
+
description: string;
|
|
83
|
+
};
|
|
84
|
+
summary: {
|
|
85
|
+
type: string;
|
|
86
|
+
description: string;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
required: string[];
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
required: string[];
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
interface EmitInteractionInput {
|
|
96
|
+
path: string;
|
|
97
|
+
type: 'question' | 'confirmation' | 'transition';
|
|
98
|
+
stream?: string;
|
|
99
|
+
question?: {
|
|
100
|
+
text: string;
|
|
101
|
+
options?: string[];
|
|
102
|
+
default?: string;
|
|
103
|
+
};
|
|
104
|
+
confirmation?: {
|
|
105
|
+
text: string;
|
|
106
|
+
context: string;
|
|
107
|
+
};
|
|
108
|
+
transition?: {
|
|
109
|
+
completedPhase: string;
|
|
110
|
+
suggestedNext: string;
|
|
111
|
+
reason: string;
|
|
112
|
+
artifacts: string[];
|
|
113
|
+
requiresConfirmation: boolean;
|
|
114
|
+
summary: string;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export declare function handleEmitInteraction(input: EmitInteractionInput): Promise<{
|
|
118
|
+
content: {
|
|
119
|
+
type: "text";
|
|
120
|
+
text: string;
|
|
121
|
+
}[];
|
|
122
|
+
isError: boolean;
|
|
123
|
+
} | {
|
|
124
|
+
content: {
|
|
125
|
+
type: "text";
|
|
126
|
+
text: string;
|
|
127
|
+
}[];
|
|
128
|
+
isError?: undefined;
|
|
129
|
+
}>;
|
|
130
|
+
export {};
|