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.
- package/README.md +1 -0
- package/bin/cli.js +242 -23
- package/dashboard/dist/assets/{icons-CC5R_iwL.js → icons-18VxiaCT.js} +108 -98
- package/dashboard/dist/assets/index-BJiuaVPD.css +1 -0
- package/dashboard/dist/assets/{index-WmnJCXq4.js → index-CRH5Umim.js} +50 -50
- package/dashboard/dist/index.html +3 -3
- package/lib/cli/SetupService.js +152 -21
- package/lib/domain/task/Task.js +214 -0
- package/lib/domain/task/TaskDependency.js +48 -0
- package/lib/domain/task/TaskIdGenerator.js +83 -0
- package/lib/domain/task/index.js +6 -0
- package/lib/external/mcp/McpServer.js +4 -4
- package/lib/external/mcp/handlers/task.js +295 -0
- package/lib/external/mcp/tools.js +100 -3
- package/lib/http/HttpServer.js +8 -0
- package/lib/http/routes/guard.js +283 -0
- package/lib/http/routes/task.js +282 -0
- package/lib/infrastructure/config/Paths.js +18 -8
- package/lib/infrastructure/database/migrations/002_add_tasks.js +88 -0
- package/lib/injection/ServiceContainer.js +58 -0
- package/lib/repository/task/TaskRepository.impl.js +398 -0
- package/lib/service/cursor/AgentInstructionsGenerator.js +28 -9
- package/lib/service/cursor/CursorDeliveryPipeline.js +42 -20
- package/lib/service/cursor/KnowledgeCompressor.js +40 -0
- package/lib/service/cursor/TokenBudget.js +2 -2
- package/lib/service/guard/GuardFeedbackLoop.js +17 -2
- package/lib/service/knowledge/KnowledgeService.js +6 -0
- package/lib/service/task/TaskGraphService.js +410 -0
- package/lib/service/task/TaskKnowledgeBridge.js +86 -0
- package/lib/service/task/TaskReadyEngine.js +127 -0
- package/lib/shared/constants.js +3 -3
- package/package.json +1 -1
- package/skills/autosnippet-intent/SKILL.md +4 -1
- package/skills/autosnippet-recipes/SKILL.md +17 -2
- package/templates/claude-hooks.yaml +19 -0
- package/templates/copilot-instructions.md +33 -1
- package/templates/cursor-rules/autosnippet-conventions.mdc +12 -0
- package/templates/cursor-rules/autosnippet-workflow.mdc +43 -0
- package/templates/guard-ci.yml +1 -0
- package/templates/pre-commit-guard.sh +2 -1
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|