@harness-engineering/mcp-server 0.1.2 → 0.2.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/state.d.ts +1 -0
- package/dist/src/resources/state.js +17 -0
- package/dist/src/server.js +29 -1
- package/dist/src/tools/cross-check.d.ts +28 -0
- package/dist/src/tools/cross-check.js +35 -0
- package/dist/src/tools/entropy.d.ts +6 -0
- package/dist/src/tools/entropy.js +27 -23
- package/dist/src/tools/feedback.d.ts +108 -0
- package/dist/src/tools/feedback.js +180 -0
- package/dist/src/tools/phase-gate.d.ts +18 -0
- package/dist/src/tools/phase-gate.js +28 -0
- package/dist/src/tools/skill.d.ts +35 -1
- package/dist/src/tools/skill.js +39 -0
- package/dist/src/tools/state.d.ts +76 -0
- package/dist/src/tools/state.js +168 -0
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getStateResource(projectRoot: string): Promise<string>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export async function getStateResource(projectRoot) {
|
|
2
|
+
try {
|
|
3
|
+
const { loadState } = await import('@harness-engineering/core');
|
|
4
|
+
const result = await loadState(projectRoot);
|
|
5
|
+
if (result.ok) {
|
|
6
|
+
return JSON.stringify(result.value, null, 2);
|
|
7
|
+
}
|
|
8
|
+
return JSON.stringify({
|
|
9
|
+
schemaVersion: 1, position: {}, decisions: [], blockers: [], progress: {},
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return JSON.stringify({
|
|
14
|
+
schemaVersion: 1, position: {}, decisions: [], blockers: [], progress: {},
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
package/dist/src/server.js
CHANGED
|
@@ -9,11 +9,16 @@ import { generateLinterDefinition, handleGenerateLinter, validateLinterConfigDef
|
|
|
9
9
|
import { initProjectDefinition, handleInitProject } from './tools/init.js';
|
|
10
10
|
import { listPersonasDefinition, handleListPersonas, generatePersonaArtifactsDefinition, handleGeneratePersonaArtifacts, runPersonaDefinition, handleRunPersona, } from './tools/persona.js';
|
|
11
11
|
import { addComponentDefinition, handleAddComponent, runAgentTaskDefinition, handleRunAgentTask, } from './tools/agent.js';
|
|
12
|
-
import { runSkillDefinition, handleRunSkill } from './tools/skill.js';
|
|
12
|
+
import { runSkillDefinition, handleRunSkill, createSkillDefinition, handleCreateSkill } from './tools/skill.js';
|
|
13
13
|
import { getSkillsResource } from './resources/skills.js';
|
|
14
14
|
import { getRulesResource } from './resources/rules.js';
|
|
15
15
|
import { getProjectResource } from './resources/project.js';
|
|
16
16
|
import { getLearningsResource } from './resources/learnings.js';
|
|
17
|
+
import { manageStateDefinition, handleManageState, manageHandoffDefinition, handleManageHandoff, } from './tools/state.js';
|
|
18
|
+
import { createSelfReviewDefinition, handleCreateSelfReview, analyzeDiffDefinition, handleAnalyzeDiff, requestPeerReviewDefinition, handleRequestPeerReview, } from './tools/feedback.js';
|
|
19
|
+
import { checkPhaseGateDefinition, handleCheckPhaseGate } from './tools/phase-gate.js';
|
|
20
|
+
import { validateCrossCheckDefinition, handleValidateCrossCheck } from './tools/cross-check.js';
|
|
21
|
+
import { getStateResource } from './resources/state.js';
|
|
17
22
|
const TOOL_DEFINITIONS = [
|
|
18
23
|
validateToolDefinition,
|
|
19
24
|
checkDependenciesDefinition,
|
|
@@ -30,6 +35,14 @@ const TOOL_DEFINITIONS = [
|
|
|
30
35
|
addComponentDefinition,
|
|
31
36
|
runAgentTaskDefinition,
|
|
32
37
|
runSkillDefinition,
|
|
38
|
+
manageStateDefinition,
|
|
39
|
+
manageHandoffDefinition,
|
|
40
|
+
createSelfReviewDefinition,
|
|
41
|
+
analyzeDiffDefinition,
|
|
42
|
+
requestPeerReviewDefinition,
|
|
43
|
+
checkPhaseGateDefinition,
|
|
44
|
+
validateCrossCheckDefinition,
|
|
45
|
+
createSkillDefinition,
|
|
33
46
|
];
|
|
34
47
|
const TOOL_HANDLERS = {
|
|
35
48
|
validate_project: handleValidateProject,
|
|
@@ -47,6 +60,14 @@ const TOOL_HANDLERS = {
|
|
|
47
60
|
add_component: handleAddComponent,
|
|
48
61
|
run_agent_task: handleRunAgentTask,
|
|
49
62
|
run_skill: handleRunSkill,
|
|
63
|
+
manage_state: handleManageState,
|
|
64
|
+
manage_handoff: handleManageHandoff,
|
|
65
|
+
create_self_review: handleCreateSelfReview,
|
|
66
|
+
analyze_diff: handleAnalyzeDiff,
|
|
67
|
+
request_peer_review: handleRequestPeerReview,
|
|
68
|
+
check_phase_gate: handleCheckPhaseGate,
|
|
69
|
+
validate_cross_check: handleValidateCrossCheck,
|
|
70
|
+
create_skill: handleCreateSkill,
|
|
50
71
|
};
|
|
51
72
|
const RESOURCE_DEFINITIONS = [
|
|
52
73
|
{
|
|
@@ -73,12 +94,19 @@ const RESOURCE_DEFINITIONS = [
|
|
|
73
94
|
description: 'Review learnings and anti-pattern log from .harness/',
|
|
74
95
|
mimeType: 'text/markdown',
|
|
75
96
|
},
|
|
97
|
+
{
|
|
98
|
+
uri: 'harness://state',
|
|
99
|
+
name: 'Project State',
|
|
100
|
+
description: 'Current harness state including position, progress, decisions, and blockers',
|
|
101
|
+
mimeType: 'application/json',
|
|
102
|
+
},
|
|
76
103
|
];
|
|
77
104
|
const RESOURCE_HANDLERS = {
|
|
78
105
|
'harness://skills': getSkillsResource,
|
|
79
106
|
'harness://rules': getRulesResource,
|
|
80
107
|
'harness://project': getProjectResource,
|
|
81
108
|
'harness://learnings': getLearningsResource,
|
|
109
|
+
'harness://state': getStateResource,
|
|
82
110
|
};
|
|
83
111
|
export function getToolDefinitions() {
|
|
84
112
|
return TOOL_DEFINITIONS;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { McpToolResponse } from '../utils/result-adapter.js';
|
|
2
|
+
export declare const validateCrossCheckDefinition: {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object";
|
|
7
|
+
properties: {
|
|
8
|
+
path: {
|
|
9
|
+
type: string;
|
|
10
|
+
description: string;
|
|
11
|
+
};
|
|
12
|
+
specsDir: {
|
|
13
|
+
type: string;
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
plansDir: {
|
|
17
|
+
type: string;
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
required: string[];
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export declare function handleValidateCrossCheck(input: {
|
|
25
|
+
path: string;
|
|
26
|
+
specsDir?: string;
|
|
27
|
+
plansDir?: string;
|
|
28
|
+
}): Promise<McpToolResponse>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
export const validateCrossCheckDefinition = {
|
|
3
|
+
name: 'validate_cross_check',
|
|
4
|
+
description: 'Validate plan-to-implementation coverage: checks that specs have plans and plans have implementations, detects staleness',
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: 'object',
|
|
7
|
+
properties: {
|
|
8
|
+
path: { type: 'string', description: 'Path to project root directory' },
|
|
9
|
+
specsDir: { type: 'string', description: 'Specs directory relative to project root (default: docs/specs)' },
|
|
10
|
+
plansDir: { type: 'string', description: 'Plans directory relative to project root (default: docs/plans)' },
|
|
11
|
+
},
|
|
12
|
+
required: ['path'],
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export async function handleValidateCrossCheck(input) {
|
|
16
|
+
const projectPath = path.resolve(input.path);
|
|
17
|
+
try {
|
|
18
|
+
const { runCrossCheck } = await import('@harness-engineering/cli');
|
|
19
|
+
const result = await runCrossCheck({
|
|
20
|
+
projectPath,
|
|
21
|
+
specsDir: path.resolve(projectPath, input.specsDir ?? 'docs/specs'),
|
|
22
|
+
plansDir: path.resolve(projectPath, input.plansDir ?? 'docs/plans'),
|
|
23
|
+
});
|
|
24
|
+
if (result.ok) {
|
|
25
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.value) }] };
|
|
26
|
+
}
|
|
27
|
+
return { content: [{ type: 'text', text: result.error.message }], isError: true };
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return {
|
|
31
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
32
|
+
isError: true,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -8,12 +8,18 @@ export declare const detectEntropyDefinition: {
|
|
|
8
8
|
type: string;
|
|
9
9
|
description: string;
|
|
10
10
|
};
|
|
11
|
+
type: {
|
|
12
|
+
type: string;
|
|
13
|
+
enum: string[];
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
11
16
|
};
|
|
12
17
|
required: string[];
|
|
13
18
|
};
|
|
14
19
|
};
|
|
15
20
|
export declare function handleDetectEntropy(input: {
|
|
16
21
|
path: string;
|
|
22
|
+
type?: string;
|
|
17
23
|
}): Promise<import("../utils/result-adapter.js").McpToolResponse>;
|
|
18
24
|
export declare const applyFixesDefinition: {
|
|
19
25
|
name: string;
|
|
@@ -8,6 +8,11 @@ export const detectEntropyDefinition = {
|
|
|
8
8
|
type: 'object',
|
|
9
9
|
properties: {
|
|
10
10
|
path: { type: 'string', description: 'Path to project root' },
|
|
11
|
+
type: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
enum: ['drift', 'dead-code', 'patterns', 'all'],
|
|
14
|
+
description: 'Type of entropy to detect (default: all)',
|
|
15
|
+
},
|
|
11
16
|
},
|
|
12
17
|
required: ['path'],
|
|
13
18
|
},
|
|
@@ -15,28 +20,28 @@ export const detectEntropyDefinition = {
|
|
|
15
20
|
export async function handleDetectEntropy(input) {
|
|
16
21
|
try {
|
|
17
22
|
const { EntropyAnalyzer } = await import('@harness-engineering/core');
|
|
23
|
+
const typeFilter = input.type ?? 'all';
|
|
18
24
|
const analyzer = new EntropyAnalyzer({
|
|
19
25
|
rootDir: path.resolve(input.path),
|
|
20
|
-
analyze: {
|
|
26
|
+
analyze: {
|
|
27
|
+
drift: typeFilter === 'all' || typeFilter === 'drift',
|
|
28
|
+
deadCode: typeFilter === 'all' || typeFilter === 'dead-code',
|
|
29
|
+
patterns: typeFilter === 'all' || typeFilter === 'patterns',
|
|
30
|
+
},
|
|
21
31
|
});
|
|
22
32
|
const result = await analyzer.analyze();
|
|
23
33
|
return resultToMcpResponse(result);
|
|
24
34
|
}
|
|
25
35
|
catch (error) {
|
|
26
36
|
return {
|
|
27
|
-
content: [
|
|
28
|
-
{
|
|
29
|
-
type: 'text',
|
|
30
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
31
|
-
},
|
|
32
|
-
],
|
|
37
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
33
38
|
isError: true,
|
|
34
39
|
};
|
|
35
40
|
}
|
|
36
41
|
}
|
|
37
42
|
export const applyFixesDefinition = {
|
|
38
43
|
name: 'apply_fixes',
|
|
39
|
-
description: 'Auto-fix detected entropy issues',
|
|
44
|
+
description: 'Auto-fix detected entropy issues and return actionable suggestions for remaining issues',
|
|
40
45
|
inputSchema: {
|
|
41
46
|
type: 'object',
|
|
42
47
|
properties: {
|
|
@@ -48,7 +53,7 @@ export const applyFixesDefinition = {
|
|
|
48
53
|
};
|
|
49
54
|
export async function handleApplyFixes(input) {
|
|
50
55
|
try {
|
|
51
|
-
const { EntropyAnalyzer, createFixes, applyFixes } = await import('@harness-engineering/core');
|
|
56
|
+
const { EntropyAnalyzer, createFixes, applyFixes, generateSuggestions } = await import('@harness-engineering/core');
|
|
52
57
|
const analyzer = new EntropyAnalyzer({
|
|
53
58
|
rootDir: path.resolve(input.path),
|
|
54
59
|
analyze: { drift: true, deadCode: true, patterns: true },
|
|
@@ -56,25 +61,24 @@ export async function handleApplyFixes(input) {
|
|
|
56
61
|
const analysisResult = await analyzer.analyze();
|
|
57
62
|
if (!analysisResult.ok)
|
|
58
63
|
return resultToMcpResponse(analysisResult);
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const fixes = createFixes(deadCode, {});
|
|
64
|
+
const report = analysisResult.value;
|
|
65
|
+
const deadCode = report.deadCode;
|
|
66
|
+
const fixes = deadCode ? createFixes(deadCode, {}) : [];
|
|
67
|
+
const suggestions = generateSuggestions(report.deadCode, report.drift, report.patterns);
|
|
64
68
|
if (input.dryRun) {
|
|
65
|
-
return { content: [{ type: 'text', text: JSON.stringify(fixes) }] };
|
|
69
|
+
return { content: [{ type: 'text', text: JSON.stringify({ fixes, suggestions }) }] };
|
|
70
|
+
}
|
|
71
|
+
if (fixes.length > 0) {
|
|
72
|
+
const applied = await applyFixes(fixes, {});
|
|
73
|
+
if (!applied.ok)
|
|
74
|
+
return resultToMcpResponse(applied);
|
|
75
|
+
return { content: [{ type: 'text', text: JSON.stringify({ ...applied.value, suggestions }) }] };
|
|
66
76
|
}
|
|
67
|
-
|
|
68
|
-
return resultToMcpResponse(applied);
|
|
77
|
+
return resultToMcpResponse(Ok({ fixes: [], applied: 0, suggestions }));
|
|
69
78
|
}
|
|
70
79
|
catch (error) {
|
|
71
80
|
return {
|
|
72
|
-
content: [
|
|
73
|
-
{
|
|
74
|
-
type: 'text',
|
|
75
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
76
|
-
},
|
|
77
|
-
],
|
|
81
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
78
82
|
isError: true,
|
|
79
83
|
};
|
|
80
84
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export declare const createSelfReviewDefinition: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
path: {
|
|
8
|
+
type: string;
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
diff: {
|
|
12
|
+
type: string;
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
15
|
+
customRules: {
|
|
16
|
+
type: string;
|
|
17
|
+
items: {
|
|
18
|
+
type: string;
|
|
19
|
+
};
|
|
20
|
+
description: string;
|
|
21
|
+
};
|
|
22
|
+
maxFileSize: {
|
|
23
|
+
type: string;
|
|
24
|
+
description: string;
|
|
25
|
+
};
|
|
26
|
+
maxFileCount: {
|
|
27
|
+
type: string;
|
|
28
|
+
description: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
required: string[];
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
export declare function handleCreateSelfReview(input: {
|
|
35
|
+
path: string;
|
|
36
|
+
diff: string;
|
|
37
|
+
customRules?: Array<Record<string, unknown>>;
|
|
38
|
+
maxFileSize?: number;
|
|
39
|
+
maxFileCount?: number;
|
|
40
|
+
}): Promise<import("../utils/result-adapter.js").McpToolResponse>;
|
|
41
|
+
export declare const analyzeDiffDefinition: {
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: "object";
|
|
46
|
+
properties: {
|
|
47
|
+
diff: {
|
|
48
|
+
type: string;
|
|
49
|
+
description: string;
|
|
50
|
+
};
|
|
51
|
+
forbiddenPatterns: {
|
|
52
|
+
type: string;
|
|
53
|
+
items: {
|
|
54
|
+
type: string;
|
|
55
|
+
};
|
|
56
|
+
description: string;
|
|
57
|
+
};
|
|
58
|
+
maxFileSize: {
|
|
59
|
+
type: string;
|
|
60
|
+
description: string;
|
|
61
|
+
};
|
|
62
|
+
maxFileCount: {
|
|
63
|
+
type: string;
|
|
64
|
+
description: string;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
required: string[];
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
export declare function handleAnalyzeDiff(input: {
|
|
71
|
+
diff: string;
|
|
72
|
+
forbiddenPatterns?: string[];
|
|
73
|
+
maxFileSize?: number;
|
|
74
|
+
maxFileCount?: number;
|
|
75
|
+
}): Promise<import("../utils/result-adapter.js").McpToolResponse>;
|
|
76
|
+
export declare const requestPeerReviewDefinition: {
|
|
77
|
+
name: string;
|
|
78
|
+
description: string;
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: "object";
|
|
81
|
+
properties: {
|
|
82
|
+
path: {
|
|
83
|
+
type: string;
|
|
84
|
+
description: string;
|
|
85
|
+
};
|
|
86
|
+
agentType: {
|
|
87
|
+
type: string;
|
|
88
|
+
enum: string[];
|
|
89
|
+
description: string;
|
|
90
|
+
};
|
|
91
|
+
diff: {
|
|
92
|
+
type: string;
|
|
93
|
+
description: string;
|
|
94
|
+
};
|
|
95
|
+
context: {
|
|
96
|
+
type: string;
|
|
97
|
+
description: string;
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
required: string[];
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
export declare function handleRequestPeerReview(input: {
|
|
104
|
+
path: string;
|
|
105
|
+
agentType: 'architecture-enforcer' | 'documentation-maintainer' | 'test-reviewer' | 'entropy-cleaner' | 'custom';
|
|
106
|
+
diff: string;
|
|
107
|
+
context?: string;
|
|
108
|
+
}): Promise<import("../utils/result-adapter.js").McpToolResponse>;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { resultToMcpResponse } from '../utils/result-adapter.js';
|
|
3
|
+
// ============ create_self_review ============
|
|
4
|
+
export const createSelfReviewDefinition = {
|
|
5
|
+
name: 'create_self_review',
|
|
6
|
+
description: 'Generate a checklist-based code review from a git diff, checking harness constraints, custom rules, and diff patterns',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
path: { type: 'string', description: 'Path to project root' },
|
|
11
|
+
diff: { type: 'string', description: 'Git diff string to review' },
|
|
12
|
+
customRules: {
|
|
13
|
+
type: 'array',
|
|
14
|
+
items: { type: 'object' },
|
|
15
|
+
description: 'Optional custom rules to apply during review',
|
|
16
|
+
},
|
|
17
|
+
maxFileSize: {
|
|
18
|
+
type: 'number',
|
|
19
|
+
description: 'Maximum number of lines changed per file before flagging',
|
|
20
|
+
},
|
|
21
|
+
maxFileCount: {
|
|
22
|
+
type: 'number',
|
|
23
|
+
description: 'Maximum number of changed files before flagging',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
required: ['path', 'diff'],
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
export async function handleCreateSelfReview(input) {
|
|
30
|
+
try {
|
|
31
|
+
const { parseDiff, createSelfReview } = await import('@harness-engineering/core');
|
|
32
|
+
const parseResult = parseDiff(input.diff);
|
|
33
|
+
if (!parseResult.ok) {
|
|
34
|
+
return resultToMcpResponse(parseResult);
|
|
35
|
+
}
|
|
36
|
+
const config = {
|
|
37
|
+
rootDir: path.resolve(input.path),
|
|
38
|
+
harness: {
|
|
39
|
+
context: true,
|
|
40
|
+
constraints: true,
|
|
41
|
+
entropy: true,
|
|
42
|
+
},
|
|
43
|
+
...(input.customRules ? { customRules: input.customRules } : {}),
|
|
44
|
+
diffAnalysis: {
|
|
45
|
+
enabled: true,
|
|
46
|
+
...(input.maxFileSize !== undefined ? { maxFileSize: input.maxFileSize } : {}),
|
|
47
|
+
...(input.maxFileCount !== undefined ? { maxChangedFiles: input.maxFileCount } : {}),
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
const result = await createSelfReview(parseResult.value, config);
|
|
51
|
+
return resultToMcpResponse(result);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return {
|
|
55
|
+
content: [
|
|
56
|
+
{
|
|
57
|
+
type: 'text',
|
|
58
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
isError: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// ============ analyze_diff ============
|
|
66
|
+
export const analyzeDiffDefinition = {
|
|
67
|
+
name: 'analyze_diff',
|
|
68
|
+
description: 'Parse a git diff and check for forbidden patterns, oversized files, and missing test coverage',
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: 'object',
|
|
71
|
+
properties: {
|
|
72
|
+
diff: { type: 'string', description: 'Git diff string to analyze' },
|
|
73
|
+
forbiddenPatterns: {
|
|
74
|
+
type: 'array',
|
|
75
|
+
items: { type: 'string' },
|
|
76
|
+
description: 'List of regex patterns that are forbidden in the diff',
|
|
77
|
+
},
|
|
78
|
+
maxFileSize: {
|
|
79
|
+
type: 'number',
|
|
80
|
+
description: 'Maximum number of lines changed per file before flagging',
|
|
81
|
+
},
|
|
82
|
+
maxFileCount: {
|
|
83
|
+
type: 'number',
|
|
84
|
+
description: 'Maximum number of changed files before flagging',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ['diff'],
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
export async function handleAnalyzeDiff(input) {
|
|
91
|
+
try {
|
|
92
|
+
const { parseDiff, analyzeDiff } = await import('@harness-engineering/core');
|
|
93
|
+
const parseResult = parseDiff(input.diff);
|
|
94
|
+
if (!parseResult.ok) {
|
|
95
|
+
return resultToMcpResponse(parseResult);
|
|
96
|
+
}
|
|
97
|
+
const options = {
|
|
98
|
+
enabled: true,
|
|
99
|
+
...(input.forbiddenPatterns
|
|
100
|
+
? {
|
|
101
|
+
forbiddenPatterns: input.forbiddenPatterns.map((pattern) => ({
|
|
102
|
+
pattern,
|
|
103
|
+
message: `Forbidden pattern matched: ${pattern}`,
|
|
104
|
+
severity: 'warning',
|
|
105
|
+
})),
|
|
106
|
+
}
|
|
107
|
+
: {}),
|
|
108
|
+
...(input.maxFileSize !== undefined ? { maxFileSize: input.maxFileSize } : {}),
|
|
109
|
+
...(input.maxFileCount !== undefined ? { maxChangedFiles: input.maxFileCount } : {}),
|
|
110
|
+
};
|
|
111
|
+
const result = await analyzeDiff(parseResult.value, options);
|
|
112
|
+
return resultToMcpResponse(result);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
return {
|
|
116
|
+
content: [
|
|
117
|
+
{
|
|
118
|
+
type: 'text',
|
|
119
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
isError: true,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// ============ request_peer_review ============
|
|
127
|
+
export const requestPeerReviewDefinition = {
|
|
128
|
+
name: 'request_peer_review',
|
|
129
|
+
description: 'Spawn an agent subprocess to perform code review. Returns structured feedback with approval status. Timeout: 120 seconds.',
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
properties: {
|
|
133
|
+
path: { type: 'string', description: 'Path to project root' },
|
|
134
|
+
agentType: {
|
|
135
|
+
type: 'string',
|
|
136
|
+
enum: [
|
|
137
|
+
'architecture-enforcer',
|
|
138
|
+
'documentation-maintainer',
|
|
139
|
+
'test-reviewer',
|
|
140
|
+
'entropy-cleaner',
|
|
141
|
+
'custom',
|
|
142
|
+
],
|
|
143
|
+
description: 'Type of agent to use for the peer review',
|
|
144
|
+
},
|
|
145
|
+
diff: { type: 'string', description: 'Git diff string to review' },
|
|
146
|
+
context: { type: 'string', description: 'Optional additional context for the reviewer' },
|
|
147
|
+
},
|
|
148
|
+
required: ['path', 'agentType', 'diff'],
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
export async function handleRequestPeerReview(input) {
|
|
152
|
+
try {
|
|
153
|
+
const { parseDiff, requestPeerReview } = await import('@harness-engineering/core');
|
|
154
|
+
const parseResult = parseDiff(input.diff);
|
|
155
|
+
if (!parseResult.ok) {
|
|
156
|
+
return resultToMcpResponse(parseResult);
|
|
157
|
+
}
|
|
158
|
+
const reviewContext = {
|
|
159
|
+
files: parseResult.value.files.map((f) => f.path),
|
|
160
|
+
diff: input.diff,
|
|
161
|
+
...(input.context ? { metadata: { context: input.context } } : {}),
|
|
162
|
+
};
|
|
163
|
+
const result = await requestPeerReview(input.agentType, reviewContext, {
|
|
164
|
+
timeout: 120_000,
|
|
165
|
+
wait: true,
|
|
166
|
+
});
|
|
167
|
+
return resultToMcpResponse(result);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
return {
|
|
171
|
+
content: [
|
|
172
|
+
{
|
|
173
|
+
type: 'text',
|
|
174
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
isError: true,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { McpToolResponse } from '../utils/result-adapter.js';
|
|
2
|
+
export declare const checkPhaseGateDefinition: {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object";
|
|
7
|
+
properties: {
|
|
8
|
+
path: {
|
|
9
|
+
type: string;
|
|
10
|
+
description: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
required: string[];
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export declare function handleCheckPhaseGate(input: {
|
|
17
|
+
path: string;
|
|
18
|
+
}): Promise<McpToolResponse>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
export const checkPhaseGateDefinition = {
|
|
3
|
+
name: 'check_phase_gate',
|
|
4
|
+
description: 'Verify implementation-to-spec mappings: checks that each implementation file has a corresponding spec document',
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: 'object',
|
|
7
|
+
properties: {
|
|
8
|
+
path: { type: 'string', description: 'Path to project root directory' },
|
|
9
|
+
},
|
|
10
|
+
required: ['path'],
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
export async function handleCheckPhaseGate(input) {
|
|
14
|
+
try {
|
|
15
|
+
const { runCheckPhaseGate } = await import('@harness-engineering/cli');
|
|
16
|
+
const result = await runCheckPhaseGate({ cwd: path.resolve(input.path) });
|
|
17
|
+
if (result.ok) {
|
|
18
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.value) }] };
|
|
19
|
+
}
|
|
20
|
+
return { content: [{ type: 'text', text: result.error.message }], isError: true };
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
25
|
+
isError: true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { McpToolResponse } from '../utils/result-adapter.js';
|
|
1
2
|
export declare const runSkillDefinition: {
|
|
2
3
|
name: string;
|
|
3
4
|
description: string;
|
|
@@ -35,4 +36,37 @@ export declare function handleRunSkill(input: {
|
|
|
35
36
|
complexity?: 'auto' | 'light' | 'full';
|
|
36
37
|
phase?: string;
|
|
37
38
|
party?: boolean;
|
|
38
|
-
}): Promise<
|
|
39
|
+
}): Promise<McpToolResponse>;
|
|
40
|
+
export declare const createSkillDefinition: {
|
|
41
|
+
name: string;
|
|
42
|
+
description: string;
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: "object";
|
|
45
|
+
properties: {
|
|
46
|
+
path: {
|
|
47
|
+
type: string;
|
|
48
|
+
description: string;
|
|
49
|
+
};
|
|
50
|
+
name: {
|
|
51
|
+
type: string;
|
|
52
|
+
description: string;
|
|
53
|
+
};
|
|
54
|
+
description: {
|
|
55
|
+
type: string;
|
|
56
|
+
description: string;
|
|
57
|
+
};
|
|
58
|
+
cognitiveMode: {
|
|
59
|
+
type: string;
|
|
60
|
+
enum: string[];
|
|
61
|
+
description: string;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
required: string[];
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
export declare function handleCreateSkill(input: {
|
|
68
|
+
path: string;
|
|
69
|
+
name: string;
|
|
70
|
+
description: string;
|
|
71
|
+
cognitiveMode?: string;
|
|
72
|
+
}): Promise<McpToolResponse>;
|
package/dist/src/tools/skill.js
CHANGED
|
@@ -44,3 +44,42 @@ export async function handleRunSkill(input) {
|
|
|
44
44
|
}
|
|
45
45
|
return resultToMcpResponse(Ok(content));
|
|
46
46
|
}
|
|
47
|
+
export const createSkillDefinition = {
|
|
48
|
+
name: 'create_skill',
|
|
49
|
+
description: 'Scaffold a new harness skill with skill.yaml and SKILL.md',
|
|
50
|
+
inputSchema: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
path: { type: 'string', description: 'Path to project root directory' },
|
|
54
|
+
name: { type: 'string', description: 'Skill name in kebab-case (e.g., my-new-skill)' },
|
|
55
|
+
description: { type: 'string', description: 'Skill description' },
|
|
56
|
+
cognitiveMode: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
enum: [
|
|
59
|
+
'adversarial-reviewer', 'constructive-architect', 'meticulous-implementer',
|
|
60
|
+
'diagnostic-investigator', 'advisory-guide', 'meticulous-verifier',
|
|
61
|
+
],
|
|
62
|
+
description: 'Cognitive mode (default: constructive-architect)',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
required: ['path', 'name', 'description'],
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
export async function handleCreateSkill(input) {
|
|
69
|
+
try {
|
|
70
|
+
const { generateSkillFiles } = await import('@harness-engineering/cli');
|
|
71
|
+
const result = generateSkillFiles({
|
|
72
|
+
name: input.name,
|
|
73
|
+
description: input.description,
|
|
74
|
+
cognitiveMode: input.cognitiveMode ?? 'constructive-architect',
|
|
75
|
+
outputDir: path.resolve(input.path),
|
|
76
|
+
});
|
|
77
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
82
|
+
isError: true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export declare const manageStateDefinition: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
path: {
|
|
8
|
+
type: string;
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
action: {
|
|
12
|
+
type: string;
|
|
13
|
+
enum: string[];
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
learning: {
|
|
17
|
+
type: string;
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
skillName: {
|
|
21
|
+
type: string;
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
outcome: {
|
|
25
|
+
type: string;
|
|
26
|
+
description: string;
|
|
27
|
+
};
|
|
28
|
+
description: {
|
|
29
|
+
type: string;
|
|
30
|
+
description: string;
|
|
31
|
+
};
|
|
32
|
+
failureType: {
|
|
33
|
+
type: string;
|
|
34
|
+
description: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
required: string[];
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
export declare function handleManageState(input: {
|
|
41
|
+
path: string;
|
|
42
|
+
action: 'show' | 'learn' | 'failure' | 'archive' | 'reset' | 'gate';
|
|
43
|
+
learning?: string;
|
|
44
|
+
skillName?: string;
|
|
45
|
+
outcome?: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
failureType?: string;
|
|
48
|
+
}): Promise<import("../utils/result-adapter.js").McpToolResponse>;
|
|
49
|
+
export declare const manageHandoffDefinition: {
|
|
50
|
+
name: string;
|
|
51
|
+
description: string;
|
|
52
|
+
inputSchema: {
|
|
53
|
+
type: "object";
|
|
54
|
+
properties: {
|
|
55
|
+
path: {
|
|
56
|
+
type: string;
|
|
57
|
+
description: string;
|
|
58
|
+
};
|
|
59
|
+
action: {
|
|
60
|
+
type: string;
|
|
61
|
+
enum: string[];
|
|
62
|
+
description: string;
|
|
63
|
+
};
|
|
64
|
+
handoff: {
|
|
65
|
+
type: string;
|
|
66
|
+
description: string;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
required: string[];
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
export declare function handleManageHandoff(input: {
|
|
73
|
+
path: string;
|
|
74
|
+
action: 'save' | 'load';
|
|
75
|
+
handoff?: unknown;
|
|
76
|
+
}): Promise<import("../utils/result-adapter.js").McpToolResponse>;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { Ok } from '@harness-engineering/core';
|
|
3
|
+
import { resultToMcpResponse } from '../utils/result-adapter.js';
|
|
4
|
+
// ── manage_state ──────────────────────────────────────────────────────
|
|
5
|
+
export const manageStateDefinition = {
|
|
6
|
+
name: 'manage_state',
|
|
7
|
+
description: 'Manage harness project state: show current state, record learnings/failures, archive failures, reset state, or run mechanical gate checks',
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
path: { type: 'string', description: 'Path to project root' },
|
|
12
|
+
action: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
enum: ['show', 'learn', 'failure', 'archive', 'reset', 'gate'],
|
|
15
|
+
description: 'Action to perform',
|
|
16
|
+
},
|
|
17
|
+
learning: { type: 'string', description: 'Learning text to record (required for learn)' },
|
|
18
|
+
skillName: { type: 'string', description: 'Skill name associated with the entry' },
|
|
19
|
+
outcome: { type: 'string', description: 'Outcome associated with the learning' },
|
|
20
|
+
description: { type: 'string', description: 'Failure description (required for failure)' },
|
|
21
|
+
failureType: { type: 'string', description: 'Type of failure (required for failure)' },
|
|
22
|
+
},
|
|
23
|
+
required: ['path', 'action'],
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
export async function handleManageState(input) {
|
|
27
|
+
try {
|
|
28
|
+
const { loadState, saveState, appendLearning, appendFailure, archiveFailures, runMechanicalGate, DEFAULT_STATE, } = await import('@harness-engineering/core');
|
|
29
|
+
const projectPath = path.resolve(input.path);
|
|
30
|
+
switch (input.action) {
|
|
31
|
+
case 'show': {
|
|
32
|
+
const result = await loadState(projectPath);
|
|
33
|
+
return resultToMcpResponse(result);
|
|
34
|
+
}
|
|
35
|
+
case 'learn': {
|
|
36
|
+
if (!input.learning) {
|
|
37
|
+
return {
|
|
38
|
+
content: [
|
|
39
|
+
{ type: 'text', text: 'Error: learning is required for learn action' },
|
|
40
|
+
],
|
|
41
|
+
isError: true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const result = await appendLearning(projectPath, input.learning, input.skillName, input.outcome);
|
|
45
|
+
if (!result.ok)
|
|
46
|
+
return resultToMcpResponse(result);
|
|
47
|
+
return resultToMcpResponse(Ok({ recorded: true }));
|
|
48
|
+
}
|
|
49
|
+
case 'failure': {
|
|
50
|
+
if (!input.description) {
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{ type: 'text', text: 'Error: description is required for failure action' },
|
|
54
|
+
],
|
|
55
|
+
isError: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (!input.failureType) {
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: 'text',
|
|
63
|
+
text: 'Error: failureType is required for failure action',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
isError: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const result = await appendFailure(projectPath, input.description, input.skillName ?? 'unknown', input.failureType);
|
|
70
|
+
if (!result.ok)
|
|
71
|
+
return resultToMcpResponse(result);
|
|
72
|
+
return resultToMcpResponse(Ok({ recorded: true }));
|
|
73
|
+
}
|
|
74
|
+
case 'archive': {
|
|
75
|
+
const result = await archiveFailures(projectPath);
|
|
76
|
+
if (!result.ok)
|
|
77
|
+
return resultToMcpResponse(result);
|
|
78
|
+
return resultToMcpResponse(Ok({ archived: true }));
|
|
79
|
+
}
|
|
80
|
+
case 'reset': {
|
|
81
|
+
const result = await saveState(projectPath, { ...DEFAULT_STATE });
|
|
82
|
+
if (!result.ok)
|
|
83
|
+
return resultToMcpResponse(result);
|
|
84
|
+
return resultToMcpResponse(Ok({ reset: true }));
|
|
85
|
+
}
|
|
86
|
+
case 'gate': {
|
|
87
|
+
const result = await runMechanicalGate(projectPath);
|
|
88
|
+
return resultToMcpResponse(result);
|
|
89
|
+
}
|
|
90
|
+
default: {
|
|
91
|
+
return {
|
|
92
|
+
content: [{ type: 'text', text: `Error: unknown action` }],
|
|
93
|
+
isError: true,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
return {
|
|
100
|
+
content: [
|
|
101
|
+
{
|
|
102
|
+
type: 'text',
|
|
103
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
isError: true,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// ── manage_handoff ────────────────────────────────────────────────────
|
|
111
|
+
export const manageHandoffDefinition = {
|
|
112
|
+
name: 'manage_handoff',
|
|
113
|
+
description: 'Save or load session handoff context for agent continuity across sessions',
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: {
|
|
117
|
+
path: { type: 'string', description: 'Path to project root' },
|
|
118
|
+
action: {
|
|
119
|
+
type: 'string',
|
|
120
|
+
enum: ['save', 'load'],
|
|
121
|
+
description: 'Action to perform',
|
|
122
|
+
},
|
|
123
|
+
handoff: { type: 'object', description: 'Handoff data to save (required for save)' },
|
|
124
|
+
},
|
|
125
|
+
required: ['path', 'action'],
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
export async function handleManageHandoff(input) {
|
|
129
|
+
try {
|
|
130
|
+
const { saveHandoff, loadHandoff } = await import('@harness-engineering/core');
|
|
131
|
+
const projectPath = path.resolve(input.path);
|
|
132
|
+
switch (input.action) {
|
|
133
|
+
case 'save': {
|
|
134
|
+
if (!input.handoff) {
|
|
135
|
+
return {
|
|
136
|
+
content: [
|
|
137
|
+
{ type: 'text', text: 'Error: handoff is required for save action' },
|
|
138
|
+
],
|
|
139
|
+
isError: true,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const result = await saveHandoff(projectPath, input.handoff);
|
|
143
|
+
return resultToMcpResponse(result.ok ? Ok({ saved: true }) : result);
|
|
144
|
+
}
|
|
145
|
+
case 'load': {
|
|
146
|
+
const result = await loadHandoff(projectPath);
|
|
147
|
+
return resultToMcpResponse(result);
|
|
148
|
+
}
|
|
149
|
+
default: {
|
|
150
|
+
return {
|
|
151
|
+
content: [{ type: 'text', text: `Error: unknown action` }],
|
|
152
|
+
isError: true,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
return {
|
|
159
|
+
content: [
|
|
160
|
+
{
|
|
161
|
+
type: 'text',
|
|
162
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
isError: true,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harness-engineering/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server for Harness Engineering toolkit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"zod": "^3.22.0",
|
|
24
24
|
"yaml": "^2.3.0",
|
|
25
25
|
"handlebars": "^4.7.0",
|
|
26
|
-
"@harness-engineering/cli": "1.0.2",
|
|
27
26
|
"@harness-engineering/core": "0.5.0",
|
|
27
|
+
"@harness-engineering/cli": "1.1.1",
|
|
28
28
|
"@harness-engineering/types": "0.0.0",
|
|
29
29
|
"@harness-engineering/linter-gen": "0.1.0"
|
|
30
30
|
},
|