@liangshanli/mcp-server-project-standards 3.0.1 → 5.0.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/README.md +62 -1
- package/README.zh-CN.md +62 -1
- package/bin/cli.js +1 -1
- package/package.json +1 -1
- package/src/server-final.js +102 -9
- package/src/utils/api_common.js +3 -3
- package/src/utils/api_config.js +22 -22
- package/src/utils/api_debug.js +51 -44
- package/src/utils/api_execute.js +56 -36
- package/src/utils/api_login.js +24 -24
- package/src/utils/database_standards.js +9 -9
- package/src/utils/generate_cursorrules.js +172 -0
- package/src/utils/get_api_standards.js +17 -17
- package/src/utils/get_development_standards.js +9 -9
- package/src/utils/get_project_info.js +7 -7
- package/src/utils/get_project_structure.js +13 -13
- package/src/utils/list_directory.js +85 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generate rules content based on project standards
|
|
6
|
+
* @param {Object} params - Parameters
|
|
7
|
+
* @param {boolean} params.save - Whether to save to file (default: false)
|
|
8
|
+
* @param {Object} config - Server configuration
|
|
9
|
+
* @returns {Object} Content, savePath and status
|
|
10
|
+
*/
|
|
11
|
+
async function generate_cursorrules(params, config) {
|
|
12
|
+
const { save = false } = params || {};
|
|
13
|
+
const projectPath = process.env.PROJECT_PATH || (global.isCursor ? '.' : null);
|
|
14
|
+
|
|
15
|
+
if (!projectPath) {
|
|
16
|
+
throw new Error('Rules generation is disabled: PROJECT_PATH is not set and client is not Cursor.');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!config) {
|
|
20
|
+
throw new Error('Configuration not found');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let rules = `---
|
|
24
|
+
alwaysApply: true
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Project Standards and Rules
|
|
28
|
+
|
|
29
|
+
## AI Assistant Guidelines
|
|
30
|
+
You are an expert AI developer assistant. Your primary goal is to maintain and evolve this project while strictly adhering to the standards defined below.
|
|
31
|
+
|
|
32
|
+
### Core Principles
|
|
33
|
+
1. **Always Discover**: Before making changes, use \`list_directory\` and \`project_structure\` to understand the current context.
|
|
34
|
+
2. **Follow Standards**: Consult \`api_standards\`, \`development_standards\`, and \`database_standards\` before implementing new logic.
|
|
35
|
+
3. **API Integrity**: Use \`api_debug\` to test APIs and ensure they match the \`api_standards\`.
|
|
36
|
+
4. **Consistency**: Match existing code patterns, naming conventions, and architectural styles.
|
|
37
|
+
|
|
38
|
+
### MCP Tool Usage Workflow
|
|
39
|
+
- **Information**: Use \`project_info\` to get/set high-level project metadata.
|
|
40
|
+
- **Navigation**: Use \`list_directory\` (with depth) to explore the file system.
|
|
41
|
+
- **Compliance**: Use \`api_standards\`, \`development_standards\`, and \`database_standards\` to get specific requirements.
|
|
42
|
+
- **Testing**: Use \`api_debug\` for direct testing and \`api_execute\` for regression testing of configured APIs.
|
|
43
|
+
- **Configuration**: Use \`api_config\` to manage the API list and base URLs.
|
|
44
|
+
|
|
45
|
+
## MCP-Driven Project Standards Enforcement Rule
|
|
46
|
+
|
|
47
|
+
### 1. Core Roles and Principles
|
|
48
|
+
- **Role Positioning**: You are a Chief Architect strictly governed by the \`mcp-server-project-standards\` specifications.
|
|
49
|
+
- **Supreme Principle**: **Tool Data > Documentation Content > Memory Hallucinations**. It is forbidden to assume project structure, API specifications, or development standards from memory without calling MCP tools.
|
|
50
|
+
|
|
51
|
+
### 2. Mandatory Tool Call (Tool-First Policy)
|
|
52
|
+
- **No Hard Reading of Long Documents**: When obtaining project specifications, architectural standards, or API definitions, **it is forbidden** to read full \`.md\` files in the \`docs/\` directory or long documents exceeding 100 lines.
|
|
53
|
+
- **Mandatory Initialization**: When a conversation starts or a task switches, tools provided by \`mcp-server-project-standards\` (e.g., \`project_info\`, \`project_structure\`, \`api_standards\`) must be prioritized.
|
|
54
|
+
- **Request on Demand**: Only apply to read specific file code snippets when the summary information returned by MCP tools is insufficient to support current code writing.
|
|
55
|
+
|
|
56
|
+
### 3. Context Anti-Forgetfulness and Alignment Mechanism (Context Maintenance)
|
|
57
|
+
- **Reread After Compression (Refresh Trigger)**: When you perceive that context is compressed (Summarized), conversations exceed 10 rounds, or project background becomes blurred, **you must proactively re-call** MCP tools to refresh the latest standard data.
|
|
58
|
+
- **Confirmation Before Action**: Before executing refactoring or generating new modules, please declare: "Aligning project specifications via MCP..." and operate based on the results returned by the tools.
|
|
59
|
+
- **Reject Deviation**: If the user's instructions violate the \`standards\` returned by MCP tools, be sure to point out the conflict points and provide correction plans that comply with the specifications.
|
|
60
|
+
|
|
61
|
+
### 4. Token Consumption and Cost Optimization (Gemini 3 Flash Optimized)
|
|
62
|
+
- **Cache-First Strategy**: Obtain short, structured results via MCP tools. This high-repetition short text can significantly trigger Gemini's Context Caching, reducing reading costs to $0.05/1M.
|
|
63
|
+
- **Incremental Output**: Full rewriting is strictly prohibited. You must only output the affected code Diff or minimized code blocks (Output cost is $3/1M, must be streamlined).
|
|
64
|
+
- **No Redundant Replies**: No need for polite language; directly output tool call results and code change suggestions.
|
|
65
|
+
|
|
66
|
+
### 5. Automated Documentation Synchronization
|
|
67
|
+
- **State Awareness**: After modifying core logic, you must ask whether to call MCP tools to synchronously update project standard descriptions or API snapshots, ensuring "code is the standard".
|
|
68
|
+
|
|
69
|
+
## Dynamic MCP Tool Call Logic
|
|
70
|
+
- **Project Identification**: Before executing any task, please first determine the current working sub-project path (e.g., \`./apps/user-service\`).
|
|
71
|
+
- **Parameter Binding**: When calling tools of \`mcp-server-project-standards\`, the \`path\` or \`project_name\` parameters **must** be automatically bound to the currently detected sub-project.
|
|
72
|
+
- **Tool Mapping Guidance**:
|
|
73
|
+
- To understand architectural layering -> Call \`project_structure\`
|
|
74
|
+
- To understand API interface standards -> Call \`api_standards\`
|
|
75
|
+
- To perform compliance checks -> Call \`development_standards\`
|
|
76
|
+
- **Auto Alignment**: When the model finds that context is compressed or project background is lost, it must **force** a re-call of \`project_info\` and specify the current sub-project path to ensure that standards do not cross-project contaminate.
|
|
77
|
+
|
|
78
|
+
## Context Management and Anti-Forgetfulness Mechanism
|
|
79
|
+
- **Reread After Compression**: Whenever you (AI) perceive that context is compressed, conversation rounds are too many (e.g., more than 10), or "Context Drift" begins to appear, **you must proactively re-call** core tools of \`mcp-server-project-standards\` (e.g., \`project_info\`).
|
|
80
|
+
- **Synchronization Declaration**: Before executing the first code generation after compression, please briefly declare at the beginning of the reply: "Project specification tools re-aligned" to ensure logical consistency.
|
|
81
|
+
- **Mandatory Alignment**: Any time code is generated, if you are uncertain about the current API standards or structural standards due to context compression, you must first call tools to query; writing based on memory (hallucination) is strictly prohibited.
|
|
82
|
+
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
// Project Info
|
|
86
|
+
if (config.project_info) {
|
|
87
|
+
rules += `## Project Information\n`;
|
|
88
|
+
if (config.project_info.projectName) rules += `- **Project Name**: ${config.project_info.projectName}\n`;
|
|
89
|
+
if (config.project_info.developmentLanguage) rules += `- **Development Language**: ${config.project_info.developmentLanguage}\n`;
|
|
90
|
+
if (config.project_info.basicInfo) rules += `- **Basic Info**: ${config.project_info.basicInfo}\n`;
|
|
91
|
+
rules += `\n`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Project Structure
|
|
95
|
+
if (config.project_structure && config.project_structure.length > 0) {
|
|
96
|
+
rules += `## Project Structure\n`;
|
|
97
|
+
config.project_structure.forEach(item => {
|
|
98
|
+
rules += `- \`${item.path}\`: ${item.description}\n`;
|
|
99
|
+
});
|
|
100
|
+
rules += `\n`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// API Standards
|
|
104
|
+
if (config.api_standards) {
|
|
105
|
+
rules += `## API Standards\n`;
|
|
106
|
+
if (config.api_standards.interfaceType) rules += `- **Interface Type**: ${config.api_standards.interfaceType}\n`;
|
|
107
|
+
if (config.api_standards.successStructure) rules += `- **Success Structure**: \`${config.api_standards.successStructure}\`\n`;
|
|
108
|
+
if (config.api_standards.errorStructure) rules += `- **Error Structure**: \`${config.api_standards.errorStructure}\`\n`;
|
|
109
|
+
|
|
110
|
+
if (config.api_standards.requirements && config.api_standards.requirements.length > 0) {
|
|
111
|
+
rules += `### API Requirements\n`;
|
|
112
|
+
config.api_standards.requirements.forEach(req => {
|
|
113
|
+
rules += `- ${req}\n`;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
rules += `\n`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Development Standards
|
|
120
|
+
if (config.development_standards && config.development_standards.length > 0) {
|
|
121
|
+
rules += `## Development Standards\n`;
|
|
122
|
+
config.development_standards.forEach(std => {
|
|
123
|
+
rules += `- ${std}\n`;
|
|
124
|
+
});
|
|
125
|
+
rules += `\n`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Database Standards
|
|
129
|
+
if (config.database_standards && config.database_standards.length > 0) {
|
|
130
|
+
rules += `## Database Standards\n`;
|
|
131
|
+
config.database_standards.forEach(std => {
|
|
132
|
+
rules += `- ${std}\n`;
|
|
133
|
+
});
|
|
134
|
+
rules += `\n`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Determine file name and path
|
|
138
|
+
const toolPrefix = process.env.TOOL_PREFIX || 'project';
|
|
139
|
+
const fileName = global.isCursor ? path.join('.cursor', 'rules', toolPrefix, 'RULE.md') : 'PROJECT_RULES.md';
|
|
140
|
+
const rulesPath = path.resolve(projectPath, fileName);
|
|
141
|
+
|
|
142
|
+
// System Metadata
|
|
143
|
+
rules += `## System Environment\n`;
|
|
144
|
+
rules += `- **Project Path**: \`${projectPath}\`\n`;
|
|
145
|
+
rules += `\n`;
|
|
146
|
+
|
|
147
|
+
if (save) {
|
|
148
|
+
try {
|
|
149
|
+
// Ensure directory exists
|
|
150
|
+
await fs.ensureDir(path.dirname(rulesPath));
|
|
151
|
+
await fs.writeFile(rulesPath, rules, 'utf8');
|
|
152
|
+
return {
|
|
153
|
+
success: true,
|
|
154
|
+
message: `${path.basename(rulesPath)} has been saved to ${rulesPath}`,
|
|
155
|
+
content: rules,
|
|
156
|
+
savePath: rulesPath
|
|
157
|
+
};
|
|
158
|
+
} catch (err) {
|
|
159
|
+
throw new Error(`Failed to save ${path.basename(rulesPath)}: ${err.message}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
success: true,
|
|
165
|
+
content: rules,
|
|
166
|
+
savePath: rulesPath,
|
|
167
|
+
suggestedFileName: fileName,
|
|
168
|
+
message: `Rules generated. Please review the content. You can call this tool with { "save": true } to save it to ${rulesPath}`
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = generate_cursorrules;
|
|
@@ -23,7 +23,7 @@ async function api_standards(params, config, saveConfig) {
|
|
|
23
23
|
|
|
24
24
|
if (action === 'get') {
|
|
25
25
|
try {
|
|
26
|
-
//
|
|
26
|
+
// Get standards from api_standards field in config file, use default if not present
|
|
27
27
|
const apiStandards = config?.api_standards || {
|
|
28
28
|
interfaceType: 'restful',
|
|
29
29
|
successStructure: {
|
|
@@ -74,33 +74,33 @@ async function api_standards(params, config, saveConfig) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
try {
|
|
77
|
-
//
|
|
77
|
+
// Ensure config.api_standards exists
|
|
78
78
|
if (!config.api_standards) {
|
|
79
79
|
config.api_standards = {};
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
//
|
|
82
|
+
// Handle merging logic for array types
|
|
83
83
|
if (Array.isArray(value) && !forceOverwrite) {
|
|
84
|
-
//
|
|
84
|
+
// Merge array instead of overwrite if forceOverwrite is false
|
|
85
85
|
if (!config.api_standards[key]) {
|
|
86
86
|
config.api_standards[key] = [];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
//
|
|
89
|
+
// Ensure existing value is an array
|
|
90
90
|
if (!Array.isArray(config.api_standards[key])) {
|
|
91
91
|
config.api_standards[key] = [];
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
//
|
|
94
|
+
// Merge array and remove duplicates
|
|
95
95
|
const existingArray = config.api_standards[key];
|
|
96
96
|
const newArray = [...new Set([...existingArray, ...value])];
|
|
97
97
|
config.api_standards[key] = newArray;
|
|
98
98
|
} else {
|
|
99
|
-
//
|
|
99
|
+
// Overwrite directly
|
|
100
100
|
config.api_standards[key] = value;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
//
|
|
103
|
+
// Save configuration
|
|
104
104
|
const saved = saveConfig(config);
|
|
105
105
|
if (!saved) {
|
|
106
106
|
throw new Error('Failed to save configuration');
|
|
@@ -131,23 +131,23 @@ async function api_standards(params, config, saveConfig) {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
try {
|
|
134
|
-
//
|
|
134
|
+
// Ensure config.api_standards exists
|
|
135
135
|
if (!config.api_standards) {
|
|
136
136
|
config.api_standards = {};
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
//
|
|
139
|
+
// Delete header
|
|
140
140
|
if (headerName) {
|
|
141
|
-
//
|
|
141
|
+
// Ensure basicHeaders exists
|
|
142
142
|
if (!config.api_standards.basicHeaders) {
|
|
143
143
|
config.api_standards.basicHeaders = {};
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
//
|
|
146
|
+
// Delete specified header
|
|
147
147
|
if (config.api_standards.basicHeaders.hasOwnProperty(headerName)) {
|
|
148
148
|
delete config.api_standards.basicHeaders[headerName];
|
|
149
149
|
|
|
150
|
-
//
|
|
150
|
+
// Save configuration
|
|
151
151
|
const saved = saveConfig(config);
|
|
152
152
|
if (!saved) {
|
|
153
153
|
throw new Error('Failed to save configuration');
|
|
@@ -164,19 +164,19 @@ async function api_standards(params, config, saveConfig) {
|
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
//
|
|
167
|
+
// Delete requirement
|
|
168
168
|
if (requirement) {
|
|
169
|
-
//
|
|
169
|
+
// Ensure requirements exists
|
|
170
170
|
if (!config.api_standards.requirements) {
|
|
171
171
|
config.api_standards.requirements = [];
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
//
|
|
174
|
+
// Find and delete specified requirement
|
|
175
175
|
const requirementIndex = config.api_standards.requirements.indexOf(requirement);
|
|
176
176
|
if (requirementIndex !== -1) {
|
|
177
177
|
config.api_standards.requirements.splice(requirementIndex, 1);
|
|
178
178
|
|
|
179
|
-
//
|
|
179
|
+
// Save configuration
|
|
180
180
|
const saved = saveConfig(config);
|
|
181
181
|
if (!saved) {
|
|
182
182
|
throw new Error('Failed to save configuration');
|
|
@@ -21,7 +21,7 @@ async function development_standards(params, config, saveConfig) {
|
|
|
21
21
|
|
|
22
22
|
if (action === 'get') {
|
|
23
23
|
try {
|
|
24
|
-
//
|
|
24
|
+
// Get standards from development_standards field in config file, use default if not present
|
|
25
25
|
const developmentStandards = config?.development_standards || [
|
|
26
26
|
'Use 2 spaces for indentation',
|
|
27
27
|
'Use single quotes instead of double quotes',
|
|
@@ -61,23 +61,23 @@ async function development_standards(params, config, saveConfig) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
try {
|
|
64
|
-
//
|
|
64
|
+
// Update configuration
|
|
65
65
|
if (!config.development_standards) {
|
|
66
66
|
config.development_standards = [];
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
//
|
|
69
|
+
// Handle merging logic for array types
|
|
70
70
|
if (!forceOverwrite) {
|
|
71
|
-
//
|
|
71
|
+
// Merge array instead of overwrite if forceOverwrite is false
|
|
72
72
|
const existingArray = config.development_standards;
|
|
73
73
|
const newArray = [...new Set([...existingArray, ...standards])];
|
|
74
74
|
config.development_standards = newArray;
|
|
75
75
|
} else {
|
|
76
|
-
//
|
|
76
|
+
// Overwrite directly
|
|
77
77
|
config.development_standards = standards;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
//
|
|
80
|
+
// Save configuration
|
|
81
81
|
const saved = saveConfig(config);
|
|
82
82
|
if (!saved) {
|
|
83
83
|
throw new Error('Failed to save configuration');
|
|
@@ -103,17 +103,17 @@ async function development_standards(params, config, saveConfig) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
try {
|
|
106
|
-
//
|
|
106
|
+
// Ensure config.development_standards exists
|
|
107
107
|
if (!config.development_standards) {
|
|
108
108
|
config.development_standards = [];
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
//
|
|
111
|
+
// Find and delete specified standard
|
|
112
112
|
const standardIndex = config.development_standards.indexOf(standard);
|
|
113
113
|
if (standardIndex !== -1) {
|
|
114
114
|
config.development_standards.splice(standardIndex, 1);
|
|
115
115
|
|
|
116
|
-
//
|
|
116
|
+
// Save configuration
|
|
117
117
|
const saved = saveConfig(config);
|
|
118
118
|
if (!saved) {
|
|
119
119
|
throw new Error('Failed to save configuration');
|
|
@@ -20,20 +20,20 @@ async function project_info(params, config, saveConfig) {
|
|
|
20
20
|
|
|
21
21
|
if (action === 'get') {
|
|
22
22
|
try {
|
|
23
|
-
//
|
|
23
|
+
// Get project info from project_info field in config file, use default if not present
|
|
24
24
|
const projectInfo = config?.project_info || '';
|
|
25
25
|
const projectName = projectInfo.projectName || '';
|
|
26
26
|
const developmentLanguage = projectInfo.developmentLanguage || '';
|
|
27
27
|
const basicInfo = projectInfo.basicInfo || '';
|
|
28
28
|
|
|
29
29
|
return {
|
|
30
|
-
//
|
|
30
|
+
// Project name
|
|
31
31
|
projectName: projectName,
|
|
32
32
|
|
|
33
|
-
//
|
|
33
|
+
// Development language
|
|
34
34
|
developmentLanguage: developmentLanguage,
|
|
35
35
|
|
|
36
|
-
//
|
|
36
|
+
// Basic info
|
|
37
37
|
basicInfo: basicInfo,
|
|
38
38
|
|
|
39
39
|
timestamp: new Date().toISOString()
|
|
@@ -55,15 +55,15 @@ async function project_info(params, config, saveConfig) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
try {
|
|
58
|
-
//
|
|
58
|
+
// Ensure config.project_info exists
|
|
59
59
|
if (!config.project_info) {
|
|
60
60
|
config.project_info = {};
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
//
|
|
63
|
+
// Update specified field
|
|
64
64
|
config.project_info[key] = value;
|
|
65
65
|
|
|
66
|
-
//
|
|
66
|
+
// Save configuration
|
|
67
67
|
const saved = saveConfig(config);
|
|
68
68
|
if (!saved) {
|
|
69
69
|
throw new Error('Failed to save configuration');
|
|
@@ -20,7 +20,7 @@ async function project_structure(params, config, saveConfig) {
|
|
|
20
20
|
|
|
21
21
|
if (action === 'get') {
|
|
22
22
|
try {
|
|
23
|
-
//
|
|
23
|
+
// Get structure from project_structure field in config file, use default if not present
|
|
24
24
|
const projectStructure = config?.project_structure || [];
|
|
25
25
|
|
|
26
26
|
return projectStructure;
|
|
@@ -37,29 +37,29 @@ async function project_structure(params, config, saveConfig) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
try {
|
|
40
|
-
//
|
|
40
|
+
// Get current structure
|
|
41
41
|
let currentStructure = config?.project_structure || [];
|
|
42
42
|
|
|
43
|
-
//
|
|
43
|
+
// Create new structure array
|
|
44
44
|
const newStructure = [];
|
|
45
45
|
|
|
46
|
-
//
|
|
46
|
+
// Loop through each new structure item
|
|
47
47
|
for (const item of structure) {
|
|
48
48
|
if (!item.path || !item.description) {
|
|
49
49
|
throw new Error('Each structure item must have "path" and "description" properties');
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
//
|
|
52
|
+
// Check if path already exists
|
|
53
53
|
const existingIndex = currentStructure.findIndex(existing => existing.path === item.path);
|
|
54
54
|
|
|
55
55
|
if (existingIndex !== -1) {
|
|
56
|
-
//
|
|
56
|
+
// Path exists, replace
|
|
57
57
|
currentStructure[existingIndex] = {
|
|
58
58
|
path: item.path,
|
|
59
59
|
description: item.description
|
|
60
60
|
};
|
|
61
61
|
} else {
|
|
62
|
-
//
|
|
62
|
+
// Path doesn't exist, add to new structure
|
|
63
63
|
newStructure.push({
|
|
64
64
|
path: item.path,
|
|
65
65
|
description: item.description
|
|
@@ -67,16 +67,16 @@ async function project_structure(params, config, saveConfig) {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
//
|
|
70
|
+
// Append new structure to existing structure
|
|
71
71
|
const updatedStructure = [...currentStructure, ...newStructure];
|
|
72
72
|
|
|
73
|
-
//
|
|
73
|
+
// Update configuration
|
|
74
74
|
if (!config.project_structure) {
|
|
75
75
|
config.project_structure = [];
|
|
76
76
|
}
|
|
77
77
|
config.project_structure = updatedStructure;
|
|
78
78
|
|
|
79
|
-
//
|
|
79
|
+
// Save configuration
|
|
80
80
|
const saved = saveConfig(config);
|
|
81
81
|
if (!saved) {
|
|
82
82
|
throw new Error('Failed to save configuration');
|
|
@@ -103,17 +103,17 @@ async function project_structure(params, config, saveConfig) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
try {
|
|
106
|
-
//
|
|
106
|
+
// Ensure config.project_structure exists
|
|
107
107
|
if (!config.project_structure) {
|
|
108
108
|
config.project_structure = [];
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
//
|
|
111
|
+
// Find and delete specified path
|
|
112
112
|
const pathIndex = config.project_structure.findIndex(item => item.path === deletePath);
|
|
113
113
|
if (pathIndex !== -1) {
|
|
114
114
|
const deletedItem = config.project_structure.splice(pathIndex, 1)[0];
|
|
115
115
|
|
|
116
|
-
//
|
|
116
|
+
// Save configuration
|
|
117
117
|
const saved = saveConfig(config);
|
|
118
118
|
if (!saved) {
|
|
119
119
|
throw new Error('Failed to save configuration');
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* List directory structure
|
|
6
|
+
* @param {Object} params - Parameters
|
|
7
|
+
* @param {string} params.path - Subdirectory path to list (relative to PROJECT_PATH)
|
|
8
|
+
* @param {number} params.depth - Max depth to traverse (default: 2)
|
|
9
|
+
* @returns {Object} Directory structure
|
|
10
|
+
*/
|
|
11
|
+
async function list_directory(params) {
|
|
12
|
+
const projectPath = process.env.PROJECT_PATH || (global.isCursor ? '.' : null);
|
|
13
|
+
|
|
14
|
+
if (!projectPath) {
|
|
15
|
+
throw new Error('Project structure tool is disabled: PROJECT_PATH is not set and client is not Cursor.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const subPath = params?.path || '';
|
|
19
|
+
const maxDepth = params?.depth || 2;
|
|
20
|
+
|
|
21
|
+
const targetPath = path.resolve(projectPath, subPath);
|
|
22
|
+
|
|
23
|
+
if (!targetPath.startsWith(path.resolve(projectPath))) {
|
|
24
|
+
throw new Error('Access denied: Path is outside of project directory');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!fs.existsSync(targetPath)) {
|
|
28
|
+
throw new Error(`Path does not exist: ${subPath}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const stats = fs.statSync(targetPath);
|
|
32
|
+
if (!stats.isDirectory()) {
|
|
33
|
+
return {
|
|
34
|
+
name: path.basename(targetPath),
|
|
35
|
+
type: 'file',
|
|
36
|
+
path: subPath
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function getTree(currentPath, currentRelativePath, currentDepth) {
|
|
41
|
+
const name = path.basename(currentPath);
|
|
42
|
+
const result = {
|
|
43
|
+
name: name || '.',
|
|
44
|
+
type: 'directory',
|
|
45
|
+
path: currentRelativePath,
|
|
46
|
+
children: []
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (currentDepth >= maxDepth) {
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const items = await fs.readdir(currentPath);
|
|
55
|
+
for (const item of items) {
|
|
56
|
+
// Skip hidden files/folders (starting with .)
|
|
57
|
+
if (item.startsWith('.') && item !== '.setting') continue;
|
|
58
|
+
if (item === 'node_modules') continue;
|
|
59
|
+
|
|
60
|
+
const itemPath = path.join(currentPath, item);
|
|
61
|
+
const itemRelativePath = path.join(currentRelativePath, item);
|
|
62
|
+
const itemStats = await fs.stat(itemPath);
|
|
63
|
+
|
|
64
|
+
if (itemStats.isDirectory()) {
|
|
65
|
+
result.children.push(await getTree(itemPath, itemRelativePath, currentDepth + 1));
|
|
66
|
+
} else {
|
|
67
|
+
result.children.push({
|
|
68
|
+
name: item,
|
|
69
|
+
type: 'file',
|
|
70
|
+
path: itemRelativePath
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error(`Error reading directory ${currentPath}:`, err.message);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return await getTree(targetPath, subPath, 0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = list_directory;
|
|
85
|
+
|