autosnippet 3.1.15 → 3.2.2

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.
Files changed (41) hide show
  1. package/README.md +1 -0
  2. package/bin/cli.js +242 -23
  3. package/dashboard/dist/assets/{icons-CC5R_iwL.js → icons-18VxiaCT.js} +108 -98
  4. package/dashboard/dist/assets/index-BJiuaVPD.css +1 -0
  5. package/dashboard/dist/assets/{index-WmnJCXq4.js → index-CRH5Umim.js} +50 -50
  6. package/dashboard/dist/index.html +3 -3
  7. package/lib/cli/SetupService.js +152 -21
  8. package/lib/domain/task/Task.js +214 -0
  9. package/lib/domain/task/TaskDependency.js +48 -0
  10. package/lib/domain/task/TaskIdGenerator.js +83 -0
  11. package/lib/domain/task/index.js +6 -0
  12. package/lib/external/mcp/McpServer.js +4 -4
  13. package/lib/external/mcp/handlers/task.js +295 -0
  14. package/lib/external/mcp/tools.js +100 -3
  15. package/lib/http/HttpServer.js +8 -0
  16. package/lib/http/routes/guard.js +283 -0
  17. package/lib/http/routes/task.js +282 -0
  18. package/lib/infrastructure/config/Paths.js +18 -8
  19. package/lib/infrastructure/database/migrations/002_add_tasks.js +88 -0
  20. package/lib/injection/ServiceContainer.js +58 -0
  21. package/lib/repository/task/TaskRepository.impl.js +398 -0
  22. package/lib/service/cursor/AgentInstructionsGenerator.js +28 -9
  23. package/lib/service/cursor/CursorDeliveryPipeline.js +42 -20
  24. package/lib/service/cursor/KnowledgeCompressor.js +40 -0
  25. package/lib/service/cursor/TokenBudget.js +2 -2
  26. package/lib/service/guard/GuardFeedbackLoop.js +17 -2
  27. package/lib/service/knowledge/KnowledgeService.js +6 -0
  28. package/lib/service/task/TaskGraphService.js +410 -0
  29. package/lib/service/task/TaskKnowledgeBridge.js +86 -0
  30. package/lib/service/task/TaskReadyEngine.js +127 -0
  31. package/lib/shared/constants.js +3 -3
  32. package/package.json +1 -1
  33. package/skills/autosnippet-intent/SKILL.md +4 -1
  34. package/skills/autosnippet-recipes/SKILL.md +17 -2
  35. package/templates/claude-hooks.yaml +19 -0
  36. package/templates/copilot-instructions.md +33 -1
  37. package/templates/cursor-rules/autosnippet-conventions.mdc +12 -0
  38. package/templates/cursor-rules/autosnippet-workflow.mdc +43 -0
  39. package/templates/guard-ci.yml +1 -0
  40. package/templates/pre-commit-guard.sh +2 -1
  41. package/dashboard/dist/assets/index-6iola4rb.css +0 -1
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Guard 文件检查 API 路由
3
+ *
4
+ * 提供 HTTP 端点供 VS Code Extension 调用,触发 Guard 实时检查。
5
+ * 返回格式面向 IDE DiagnosticCollection 优化。
6
+ *
7
+ * 端点:
8
+ * POST /api/v1/guard/file — 单文件检查(Extension onDidSave 调用)
9
+ * POST /api/v1/guard/batch — 批量文件检查(Extension 工作区扫描)
10
+ */
11
+
12
+ import { readFileSync } from 'node:fs';
13
+ import express from 'express';
14
+ import { getServiceContainer } from '../../injection/ServiceContainer.js';
15
+ import { asyncHandler } from '../middleware/errorHandler.js';
16
+
17
+ const router = express.Router();
18
+
19
+ /**
20
+ * POST /api/v1/guard/file
21
+ *
22
+ * 请求体:
23
+ * { filePath: string, content?: string, language?: string }
24
+ *
25
+ * - filePath: 必须。文件路径(用于语言检测 + 违规追踪)
26
+ * - content: 可选。文件内容,若省略则从 filePath 磁盘读取
27
+ * - language: 可选。语言标识,若省略则从 filePath 扩展名推断
28
+ *
29
+ * 响应:
30
+ * {
31
+ * success: true,
32
+ * data: {
33
+ * filePath, language, violations[], summary,
34
+ * fixedViolations[] // 与上次检查对比已修复的违规
35
+ * }
36
+ * }
37
+ */
38
+ router.post(
39
+ '/file',
40
+ asyncHandler(async (req, res) => {
41
+ const { filePath, content, language } = req.body;
42
+
43
+ if (!filePath) {
44
+ return res.status(400).json({
45
+ success: false,
46
+ message: 'filePath is required',
47
+ });
48
+ }
49
+
50
+ // 获取文件内容
51
+ let code = content;
52
+ if (!code) {
53
+ try {
54
+ code = readFileSync(filePath, 'utf8');
55
+ } catch (err) {
56
+ return res.status(400).json({
57
+ success: false,
58
+ message: `Cannot read file: ${err.message}`,
59
+ });
60
+ }
61
+ }
62
+
63
+ const container = getServiceContainer();
64
+ const { GuardCheckEngine, detectLanguage } = await import(
65
+ '../../service/guard/GuardCheckEngine.js'
66
+ );
67
+
68
+ // 获取 Engine(含 EP 注入)
69
+ const engine = await _getEngine(container, GuardCheckEngine);
70
+
71
+ // 检测语言
72
+ const lang = language || detectLanguage(filePath);
73
+
74
+ // 执行检查
75
+ const violations = engine.checkCode(code, lang, { filePath });
76
+
77
+ // 格式化违规消息面向 Agent
78
+ const formattedViolations = violations.map((v) => ({
79
+ ...v,
80
+ // 面向 Agent 的诊断消息格式
81
+ diagnosticMessage: _buildDiagnosticMessage(v),
82
+ }));
83
+
84
+ const summary = {
85
+ total: violations.length,
86
+ errors: violations.filter((v) => v.severity === 'error').length,
87
+ warnings: violations.filter((v) => v.severity === 'warning').length,
88
+ infos: violations.filter((v) => v.severity === 'info').length,
89
+ };
90
+
91
+ // GuardFeedbackLoop: 检测修复 + confirmUsage
92
+ let fixedViolations = [];
93
+ try {
94
+ const feedbackLoop = container.get('guardFeedbackLoop');
95
+ fixedViolations = feedbackLoop.processFixDetection({ violations }, filePath);
96
+ } catch {
97
+ /* feedbackLoop not available */
98
+ }
99
+
100
+ // 写入 ViolationsStore(供后续对比)
101
+ try {
102
+ const violationsStore = container.get('violationsStore');
103
+ violationsStore.appendRun({
104
+ filePath,
105
+ violations,
106
+ summary: `Guard file check: ${summary.errors}E ${summary.warnings}W ${summary.infos}I`,
107
+ });
108
+ } catch {
109
+ /* violationsStore not available */
110
+ }
111
+
112
+ res.json({
113
+ success: true,
114
+ data: {
115
+ filePath,
116
+ language: lang,
117
+ violations: formattedViolations,
118
+ summary,
119
+ fixedViolations,
120
+ },
121
+ });
122
+ })
123
+ );
124
+
125
+ /**
126
+ * POST /api/v1/guard/batch
127
+ *
128
+ * 请求体:
129
+ * { files: Array<{ filePath: string, content?: string, language?: string }> }
130
+ *
131
+ * 批量检查多个文件(工作区级 Guard 扫描)
132
+ */
133
+ router.post(
134
+ '/batch',
135
+ asyncHandler(async (req, res) => {
136
+ const { files } = req.body;
137
+
138
+ if (!Array.isArray(files) || files.length === 0) {
139
+ return res.status(400).json({
140
+ success: false,
141
+ message: 'files array is required and must not be empty',
142
+ });
143
+ }
144
+
145
+ // 限制单次批量不超过 50 个文件
146
+ if (files.length > 50) {
147
+ return res.status(400).json({
148
+ success: false,
149
+ message: 'Maximum 50 files per batch',
150
+ });
151
+ }
152
+
153
+ const container = getServiceContainer();
154
+ const { GuardCheckEngine, detectLanguage } = await import(
155
+ '../../service/guard/GuardCheckEngine.js'
156
+ );
157
+
158
+ const engine = await _getEngine(container, GuardCheckEngine);
159
+
160
+ const results = [];
161
+ let totalErrors = 0;
162
+ let totalWarnings = 0;
163
+
164
+ for (const file of files) {
165
+ if (!file.filePath) continue;
166
+
167
+ let code = file.content;
168
+ if (!code) {
169
+ try {
170
+ code = readFileSync(file.filePath, 'utf8');
171
+ } catch {
172
+ results.push({
173
+ filePath: file.filePath,
174
+ language: 'unknown',
175
+ violations: [],
176
+ summary: { total: 0, errors: 0, warnings: 0, infos: 0 },
177
+ error: 'Cannot read file',
178
+ });
179
+ continue;
180
+ }
181
+ }
182
+
183
+ const lang = file.language || detectLanguage(file.filePath);
184
+ const violations = engine.checkCode(code, lang, { filePath: file.filePath });
185
+
186
+ const summary = {
187
+ total: violations.length,
188
+ errors: violations.filter((v) => v.severity === 'error').length,
189
+ warnings: violations.filter((v) => v.severity === 'warning').length,
190
+ infos: violations.filter((v) => v.severity === 'info').length,
191
+ };
192
+
193
+ totalErrors += summary.errors;
194
+ totalWarnings += summary.warnings;
195
+
196
+ results.push({
197
+ filePath: file.filePath,
198
+ language: lang,
199
+ violations: violations.map((v) => ({
200
+ ...v,
201
+ diagnosticMessage: _buildDiagnosticMessage(v),
202
+ })),
203
+ summary,
204
+ });
205
+ }
206
+
207
+ res.json({
208
+ success: true,
209
+ data: {
210
+ files: results,
211
+ summary: {
212
+ totalFiles: results.length,
213
+ totalErrors,
214
+ totalWarnings,
215
+ },
216
+ },
217
+ });
218
+ })
219
+ );
220
+
221
+ // ═══ 内部工具 ═══════════════════════════════════════
222
+
223
+ /**
224
+ * 获取或创建 GuardCheckEngine,并注入 Enhancement Pack 规则
225
+ * @param {object} container - ServiceContainer
226
+ * @param {Function} GuardCheckEngine - GuardCheckEngine class
227
+ * @returns {object} engine
228
+ */
229
+ async function _getEngine(container, GuardCheckEngine) {
230
+ let engine;
231
+ try {
232
+ engine = container.get('guardCheckEngine');
233
+ } catch {
234
+ const database = container.get('database');
235
+ engine = new GuardCheckEngine(database);
236
+ }
237
+
238
+ // 注入 Enhancement Pack Guard 规则
239
+ if (!engine.isEpInjected()) {
240
+ try {
241
+ const { getEnhancementRegistry } = await import(
242
+ '../../core/enhancement/index.js'
243
+ );
244
+ const registry = getEnhancementRegistry();
245
+ if (registry) {
246
+ const guardRules = registry.getGuardRules?.() || [];
247
+ engine.injectExternalRules(guardRules);
248
+ engine.markEpInjected();
249
+ }
250
+ } catch {
251
+ /* EP not available */
252
+ }
253
+ }
254
+
255
+ return engine;
256
+ }
257
+
258
+ /**
259
+ * 构建面向 Agent 优化的诊断消息
260
+ *
261
+ * 双重受众设计:
262
+ * - 人类看到: 波浪线 + 违规描述
263
+ * - Agent 看到: ruleId + 明确的 MCP 搜索指令
264
+ *
265
+ * @param {object} violation
266
+ * @returns {string}
267
+ */
268
+ function _buildDiagnosticMessage(violation) {
269
+ const { ruleId, message, fixSuggestion } = violation;
270
+
271
+ let msg = `[AutoSnippet Guard] ${ruleId}: ${message}`;
272
+
273
+ if (fixSuggestion) {
274
+ msg += `\n修复建议: ${fixSuggestion}`;
275
+ }
276
+
277
+ // Agent 指引:嵌入 MCP 搜索建议
278
+ msg += `\n搜 autosnippet_search('${ruleId}') 查找正确写法。`;
279
+
280
+ return msg;
281
+ }
282
+
283
+ export default router;
@@ -0,0 +1,282 @@
1
+ /**
2
+ * TaskGraph HTTP API 路由
3
+ *
4
+ * 为 VS Code Extension `taskTool.ts` 提供 HTTP 转发端点。
5
+ * Extension 通过 lm.registerTool 拦截 tokenBudget 后,
6
+ * 将业务逻辑转发到此端点,由 TaskGraphService 执行。
7
+ *
8
+ * 端点:
9
+ * POST /api/v1/task — 统一入口(operation 路由)
10
+ */
11
+
12
+ import express from 'express';
13
+ import { getServiceContainer } from '../../injection/ServiceContainer.js';
14
+ import { asyncHandler } from '../middleware/errorHandler.js';
15
+
16
+ const router = express.Router();
17
+
18
+ /**
19
+ * POST /api/v1/task
20
+ *
21
+ * 请求体:
22
+ * { operation: string, ...params }
23
+ *
24
+ * 响应:
25
+ * { success: boolean, data?: any, message?: string }
26
+ */
27
+ router.post(
28
+ '/',
29
+ asyncHandler(async (req, res) => {
30
+ const container = getServiceContainer();
31
+ const taskService = container.get('taskGraphService');
32
+
33
+ if (!taskService) {
34
+ return res.status(503).json({
35
+ success: false,
36
+ message: 'TaskGraphService not available',
37
+ });
38
+ }
39
+
40
+ const body = req.body;
41
+ if (!body || typeof body !== 'object') {
42
+ return res.status(400).json({
43
+ success: false,
44
+ message: 'JSON body is required',
45
+ });
46
+ }
47
+
48
+ const { operation, ...params } = body;
49
+
50
+ if (!operation) {
51
+ return res.status(400).json({
52
+ success: false,
53
+ message: 'operation is required',
54
+ });
55
+ }
56
+
57
+ try {
58
+ const result = await _dispatch(taskService, operation, params);
59
+ if (result.success === false) {
60
+ return res.status(400).json(result);
61
+ }
62
+ return res.json(result);
63
+ } catch (err) {
64
+ return res.status(400).json({
65
+ success: false,
66
+ message: err.message,
67
+ operation,
68
+ });
69
+ }
70
+ })
71
+ );
72
+
73
+ /**
74
+ * 操作路由 — 与 MCP handler/task.js 保持一致
75
+ */
76
+ async function _dispatch(svc, operation, params) {
77
+ switch (operation) {
78
+ case 'create':
79
+ return _create(svc, params);
80
+ case 'ready':
81
+ return _ready(svc, params);
82
+ case 'claim':
83
+ return _claim(svc, params);
84
+ case 'close':
85
+ return _close(svc, params);
86
+ case 'fail':
87
+ return _fail(svc, params);
88
+ case 'defer':
89
+ return _defer(svc, params);
90
+ case 'progress':
91
+ return _progress(svc, params);
92
+ case 'prime':
93
+ return _prime(svc);
94
+ case 'decompose':
95
+ return _decompose(svc, params);
96
+ case 'show':
97
+ return _show(svc, params);
98
+ case 'list':
99
+ return _list(svc, params);
100
+ case 'blocked':
101
+ return _blocked(svc);
102
+ case 'dep_add':
103
+ return _depAdd(svc, params);
104
+ case 'dep_tree':
105
+ return _depTree(svc, params);
106
+ case 'stats':
107
+ return _stats(svc);
108
+ default:
109
+ return { success: false, message: `Unknown operation: ${operation}` };
110
+ }
111
+ }
112
+
113
+ // ── create ──
114
+
115
+ async function _create(svc, args) {
116
+ if (!args.title) {
117
+ return { success: false, message: 'title is required' };
118
+ }
119
+ const { task, isDuplicate } = await svc.create({
120
+ title: args.title,
121
+ description: args.description || '',
122
+ design: args.design || '',
123
+ acceptance: args.acceptance || '',
124
+ priority: args.priority ?? 2,
125
+ taskType: args.taskType || 'task',
126
+ parentId: args.parentId || null,
127
+ });
128
+ return {
129
+ success: true,
130
+ data: task.toJSON(),
131
+ message: isDuplicate
132
+ ? `Duplicate detected: ${task.id} already exists`
133
+ : `Created ${task.id}: ${task.title}`,
134
+ };
135
+ }
136
+
137
+ // ── ready ──
138
+
139
+ async function _ready(svc, args) {
140
+ const tasks = await svc.ready({
141
+ limit: args.limit || 5,
142
+ withKnowledge: args.withKnowledge !== false,
143
+ });
144
+ return {
145
+ success: true,
146
+ data: tasks.map((t) => (t.toJSON ? t.toJSON() : t)),
147
+ count: tasks.length,
148
+ };
149
+ }
150
+
151
+ // ── claim ──
152
+
153
+ async function _claim(svc, args) {
154
+ if (!args.id) return { success: false, message: 'id is required' };
155
+ const task = await svc.claim(args.id, args.assignee || 'agent');
156
+ return { success: true, data: task.toJSON() };
157
+ }
158
+
159
+ // ── close ──
160
+
161
+ async function _close(svc, args) {
162
+ if (!args.id) return { success: false, message: 'id is required' };
163
+ const { task, newlyReady } = await svc.close(args.id, args.reason || 'Completed');
164
+ return {
165
+ success: true,
166
+ data: task.toJSON(),
167
+ newlyReady,
168
+ message: `Closed ${task.id}. ${newlyReady.length} tasks newly ready.`,
169
+ };
170
+ }
171
+
172
+ // ── fail ──
173
+
174
+ async function _fail(svc, args) {
175
+ if (!args.id) return { success: false, message: 'id is required' };
176
+ const task = await svc.fail(args.id, args.reason || '');
177
+ return { success: true, data: task.toJSON() };
178
+ }
179
+
180
+ // ── defer ──
181
+
182
+ async function _defer(svc, args) {
183
+ if (!args.id) return { success: false, message: 'id is required' };
184
+ const task = await svc.defer(args.id, args.reason || '');
185
+ return { success: true, data: task.toJSON() };
186
+ }
187
+
188
+ // ── progress ──
189
+
190
+ async function _progress(svc, args) {
191
+ if (!args.id) return { success: false, message: 'id is required' };
192
+ const task = await svc.progress(args.id, args.note || args.description || '');
193
+ return { success: true, data: task.toJSON() };
194
+ }
195
+
196
+ // ── prime ──
197
+
198
+ async function _prime(svc) {
199
+ const result = await svc.prime({ withKnowledge: true });
200
+ return { success: true, data: result };
201
+ }
202
+
203
+ // ── decompose ──
204
+
205
+ async function _decompose(svc, args) {
206
+ const epicId = args.parentId || args.id;
207
+ const subtasks = args.children || args.subtasks;
208
+ if (!epicId) return { success: false, message: 'parentId (or id) is required' };
209
+ if (!subtasks || !Array.isArray(subtasks)) {
210
+ return { success: false, message: 'children (or subtasks) array is required' };
211
+ }
212
+ const tasks = await svc.decompose(epicId, subtasks);
213
+ return {
214
+ success: true,
215
+ data: tasks.map((t) => t.toJSON()),
216
+ count: tasks.length,
217
+ };
218
+ }
219
+
220
+ // ── show ──
221
+
222
+ async function _show(svc, args) {
223
+ if (!args.id) return { success: false, message: 'id is required' };
224
+ const task = await svc.show(args.id);
225
+ if (!task) return { success: false, message: `Task ${args.id} not found` };
226
+ return { success: true, data: task.toJSON() };
227
+ }
228
+
229
+ // ── list ──
230
+
231
+ async function _list(svc, args) {
232
+ const tasks = await svc.list(
233
+ { status: args.status, taskType: args.taskType, parentId: args.parentId },
234
+ { limit: args.limit || 50 }
235
+ );
236
+ return {
237
+ success: true,
238
+ data: tasks.map((t) => t.toJSON()),
239
+ count: tasks.length,
240
+ };
241
+ }
242
+
243
+ // ── blocked ──
244
+
245
+ async function _blocked(svc) {
246
+ const tasks = await svc.blocked();
247
+ return {
248
+ success: true,
249
+ data: tasks.map((t) => (t.toJSON ? t.toJSON() : t)),
250
+ count: tasks.length,
251
+ };
252
+ }
253
+
254
+ // ── dep_add ──
255
+
256
+ async function _depAdd(svc, args) {
257
+ if (!args.taskId || !args.dependsOn) {
258
+ return { success: false, message: 'taskId and dependsOn are required' };
259
+ }
260
+ await svc.addDependency(args.taskId, args.dependsOn, args.depType || 'blocks');
261
+ return {
262
+ success: true,
263
+ message: `Dependency added: ${args.taskId} ${args.depType || 'blocks'} ${args.dependsOn}`,
264
+ };
265
+ }
266
+
267
+ // ── dep_tree ──
268
+
269
+ async function _depTree(svc, args) {
270
+ if (!args.id) return { success: false, message: 'id is required' };
271
+ const tree = await svc.depTree(args.id);
272
+ return { success: true, data: tree };
273
+ }
274
+
275
+ // ── stats ──
276
+
277
+ async function _stats(svc) {
278
+ const stats = await svc.stats();
279
+ return { success: true, data: stats };
280
+ }
281
+
282
+ export default router;
@@ -5,15 +5,19 @@ import pathGuard from '../../shared/PathGuard.js';
5
5
  /**
6
6
  * Paths — 项目路径解析工具
7
7
  * 提供 Snippet 安装目录、缓存目录、知识库目录等路径计算能力。
8
- * 所有路径均自动确保目录存在。
8
+ *
9
+ * 设计原则:路径解析与目录创建分离
10
+ * - 路径 getter 函数仅返回路径字符串,不产生文件系统副作用
11
+ * - 需要创建目录时,调用方应使用 ensureDir() 显式确保目录存在
12
+ * - 全局非项目目录(Xcode snippets、cache)在获取时自动创建
9
13
  */
10
14
 
11
15
  export const SPEC_FILENAME = 'AutoSnippet.boxspec.json';
12
16
 
13
17
  const USER_HOME = process.env.HOME || process.env.USERPROFILE || '';
14
18
 
15
- /** 确保目录存在(静默处理异常) */
16
- function ensureDir(dirPath) {
19
+ /** 确保目录存在(静默处理异常),供写入前调用 */
20
+ export function ensureDir(dirPath) {
17
21
  try {
18
22
  // 双层路径安全检查 — 阻止在项目允许范围外创建文件夹
19
23
  pathGuard.assertProjectWriteSafe(dirPath);
@@ -86,9 +90,10 @@ export function getKnowledgeBaseDirName(projectRoot) {
86
90
 
87
91
  /**
88
92
  * 知识库根目录 = projectRoot/{dirContainingBoxspec}
93
+ * 注意:仅返回路径,不创建目录
89
94
  */
90
95
  export function getProjectKnowledgePath(projectRoot) {
91
- return ensureDir(path.join(projectRoot, getKnowledgeBaseDirName(projectRoot)));
96
+ return path.join(projectRoot, getKnowledgeBaseDirName(projectRoot));
92
97
  }
93
98
 
94
99
  /**
@@ -100,31 +105,35 @@ export function getProjectSpecPath(projectRoot) {
100
105
 
101
106
  /**
102
107
  * 项目内部隐藏数据目录 = knowledgePath/.autosnippet
108
+ * 注意:仅返回路径,不创建目录
103
109
  */
104
110
  export function getProjectInternalDataPath(projectRoot) {
105
- return ensureDir(path.join(getProjectKnowledgePath(projectRoot), '.autosnippet'));
111
+ return path.join(getProjectKnowledgePath(projectRoot), '.autosnippet');
106
112
  }
107
113
 
108
114
  /**
109
115
  * 上下文存储目录 = internalData/context
116
+ * 注意:仅返回路径,不创建目录
110
117
  */
111
118
  export function getContextStoragePath(projectRoot) {
112
- return ensureDir(path.join(getProjectInternalDataPath(projectRoot), 'context'));
119
+ return path.join(getProjectInternalDataPath(projectRoot), 'context');
113
120
  }
114
121
 
115
122
  /**
116
123
  * 上下文索引目录 = contextStorage/index
124
+ * 注意:仅返回路径,不创建目录
117
125
  */
118
126
  export function getContextIndexPath(projectRoot) {
119
- return ensureDir(path.join(getContextStoragePath(projectRoot), 'index'));
127
+ return path.join(getContextStoragePath(projectRoot), 'index');
120
128
  }
121
129
 
122
130
  /**
123
131
  * 项目级 Skills 目录 = knowledgePath/skills
124
132
  * Skills 放在知识库目录下跟随项目走(Git-tracked,用户可见)
133
+ * 注意:仅返回路径,不创建目录
125
134
  */
126
135
  export function getProjectSkillsPath(projectRoot) {
127
- return ensureDir(path.join(getProjectKnowledgePath(projectRoot), 'skills'));
136
+ return path.join(getProjectKnowledgePath(projectRoot), 'skills');
128
137
  }
129
138
 
130
139
  /**
@@ -143,6 +152,7 @@ export function getProjectRecipesPath(projectRoot, rootSpec) {
143
152
 
144
153
  export default {
145
154
  SPEC_FILENAME,
155
+ ensureDir,
146
156
  getSnippetsPath,
147
157
  getVSCodeSnippetsPath,
148
158
  getCachePath,