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,88 @@
1
+ /**
2
+ * Migration 002: TaskGraph 任务表
3
+ *
4
+ * 新增 tasks 表 + task_dependencies 表 + task_events 表。
5
+ *
6
+ * 设计决策 D1: 独立 task_dependencies 表(不复用 knowledge_edges)
7
+ * - 语义不同(任务依赖 vs 知识关系)
8
+ * - 需要专门的索引优化就绪检测查询
9
+ * - 避免与知识图谱互相污染
10
+ */
11
+ export default function migrate(db) {
12
+ // ── tasks 表 ──
13
+ db.exec(`
14
+ CREATE TABLE IF NOT EXISTS tasks (
15
+ id TEXT PRIMARY KEY,
16
+ parent_id TEXT,
17
+ child_seq INTEGER DEFAULT 0,
18
+
19
+ title TEXT NOT NULL,
20
+ description TEXT DEFAULT '',
21
+ design TEXT DEFAULT '',
22
+ acceptance TEXT DEFAULT '',
23
+ notes TEXT DEFAULT '',
24
+
25
+ status TEXT NOT NULL DEFAULT 'open',
26
+ priority INTEGER NOT NULL DEFAULT 2,
27
+ task_type TEXT NOT NULL DEFAULT 'task',
28
+ close_reason TEXT DEFAULT '',
29
+ content_hash TEXT DEFAULT '',
30
+ fail_count INTEGER DEFAULT 0,
31
+ last_fail_reason TEXT DEFAULT '',
32
+
33
+ assignee TEXT DEFAULT '',
34
+ created_by TEXT DEFAULT 'agent',
35
+
36
+ created_at INTEGER NOT NULL,
37
+ updated_at INTEGER NOT NULL,
38
+ closed_at INTEGER,
39
+
40
+ metadata TEXT DEFAULT '{}'
41
+ );
42
+
43
+ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
44
+ CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority);
45
+ CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_id);
46
+ CREATE INDEX IF NOT EXISTS idx_tasks_type ON tasks(task_type);
47
+ CREATE INDEX IF NOT EXISTS idx_tasks_assignee ON tasks(assignee);
48
+ CREATE INDEX IF NOT EXISTS idx_tasks_created ON tasks(created_at);
49
+ CREATE INDEX IF NOT EXISTS idx_tasks_hash ON tasks(content_hash);
50
+ `);
51
+
52
+ // ── task_dependencies 表 ──
53
+ db.exec(`
54
+ CREATE TABLE IF NOT EXISTS task_dependencies (
55
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
56
+ task_id TEXT NOT NULL,
57
+ depends_on_id TEXT NOT NULL,
58
+ dep_type TEXT NOT NULL DEFAULT 'blocks',
59
+ metadata TEXT DEFAULT '{}',
60
+ created_at INTEGER NOT NULL,
61
+ created_by TEXT DEFAULT 'agent',
62
+
63
+ UNIQUE (task_id, depends_on_id, dep_type)
64
+ );
65
+
66
+ CREATE INDEX IF NOT EXISTS idx_td_task ON task_dependencies(task_id);
67
+ CREATE INDEX IF NOT EXISTS idx_td_depends_on ON task_dependencies(depends_on_id);
68
+ CREATE INDEX IF NOT EXISTS idx_td_type ON task_dependencies(dep_type);
69
+ `);
70
+
71
+ // ── task_events 审计表 ──
72
+ db.exec(`
73
+ CREATE TABLE IF NOT EXISTS task_events (
74
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
75
+ task_id TEXT NOT NULL,
76
+ event_type TEXT NOT NULL,
77
+ actor TEXT DEFAULT 'agent',
78
+ old_value TEXT,
79
+ new_value TEXT,
80
+ comment TEXT,
81
+ created_at INTEGER NOT NULL
82
+ );
83
+
84
+ CREATE INDEX IF NOT EXISTS idx_te_task ON task_events(task_id);
85
+ CREATE INDEX IF NOT EXISTS idx_te_type ON task_events(event_type);
86
+ CREATE INDEX IF NOT EXISTS idx_te_created ON task_events(created_at);
87
+ `);
88
+ }
@@ -64,6 +64,12 @@ import { SnippetFactory } from '../service/snippet/SnippetFactory.js';
64
64
  import { SnippetInstaller } from '../service/snippet/SnippetInstaller.js';
65
65
  import { DimensionCopy } from '../shared/DimensionCopyRegistry.js';
66
66
  import { LanguageService } from '../shared/LanguageService.js';
67
+ // ─── TaskGraph ────────────────────────────────────────
68
+ import { TaskIdGenerator } from '../domain/task/TaskIdGenerator.js';
69
+ import { TaskRepositoryImpl } from '../repository/task/TaskRepository.impl.js';
70
+ import { TaskReadyEngine } from '../service/task/TaskReadyEngine.js';
71
+ import { TaskKnowledgeBridge } from '../service/task/TaskKnowledgeBridge.js';
72
+ import { TaskGraphService } from '../service/task/TaskGraphService.js';
67
73
 
68
74
  /**
69
75
  * DependencyInjection 容器
@@ -359,6 +365,15 @@ export class ServiceContainer {
359
365
  }
360
366
  return this.singletons.knowledgeSyncService;
361
367
  });
368
+
369
+ // TaskRepository (TaskGraph 持久化)
370
+ this.register('taskRepository', () => {
371
+ if (!this.singletons.taskRepository) {
372
+ const database = this.get('database');
373
+ this.singletons.taskRepository = new TaskRepositoryImpl(database);
374
+ }
375
+ return this.singletons.taskRepository;
376
+ });
362
377
  }
363
378
 
364
379
  /**
@@ -759,6 +774,49 @@ export class ServiceContainer {
759
774
  }
760
775
  return this.singletons.cursorDeliveryPipeline;
761
776
  });
777
+
778
+ // ─── TaskGraph Services ───────────────────────────
779
+ // TaskIdGenerator (短 Hash ID 生成器)
780
+ this.register('taskIdGenerator', () => {
781
+ if (!this.singletons.taskIdGenerator) {
782
+ const database = this.get('database');
783
+ this.singletons.taskIdGenerator = new TaskIdGenerator(database.getDb());
784
+ }
785
+ return this.singletons.taskIdGenerator;
786
+ });
787
+
788
+ // TaskReadyEngine (递归 CTE 就绪检测)
789
+ this.register('taskReadyEngine', () => {
790
+ if (!this.singletons.taskReadyEngine) {
791
+ const database = this.get('database');
792
+ this.singletons.taskReadyEngine = new TaskReadyEngine(database.getDb());
793
+ }
794
+ return this.singletons.taskReadyEngine;
795
+ });
796
+
797
+ // TaskKnowledgeBridge (任务 ↔ 知识桥接)
798
+ this.register('taskKnowledgeBridge', () => {
799
+ if (!this.singletons.taskKnowledgeBridge) {
800
+ const searchEngine = this.get('searchEngine');
801
+ this.singletons.taskKnowledgeBridge = new TaskKnowledgeBridge(searchEngine);
802
+ }
803
+ return this.singletons.taskKnowledgeBridge;
804
+ });
805
+
806
+ // TaskGraphService (任务图核心服务)
807
+ this.register('taskGraphService', () => {
808
+ if (!this.singletons.taskGraphService) {
809
+ const repository = this.get('taskRepository');
810
+ const readyEngine = this.get('taskReadyEngine');
811
+ const knowledgeBridge = this.get('taskKnowledgeBridge');
812
+ const auditLogger = this.get('auditLogger');
813
+ const idGenerator = this.get('taskIdGenerator');
814
+ this.singletons.taskGraphService = new TaskGraphService(
815
+ repository, readyEngine, knowledgeBridge, auditLogger, idGenerator
816
+ );
817
+ }
818
+ return this.singletons.taskGraphService;
819
+ });
762
820
  }
763
821
 
764
822
  /**
@@ -0,0 +1,398 @@
1
+ import { Task } from '../../domain/task/Task.js';
2
+ import Logger from '../../infrastructure/logging/Logger.js';
3
+
4
+ /**
5
+ * TaskRepositoryImpl — 任务实体 SQLite 持久化
6
+ *
7
+ * 直接操作 better-sqlite3 同步 API,方法签名保持 async 以对齐 DDD 接口约定。
8
+ * DB 列名 snake_case,实体属性 camelCase—— Task.fromRow() / _entityToRow() 负责映射。
9
+ */
10
+ export class TaskRepositoryImpl {
11
+ /**
12
+ * @param {import('../../infrastructure/database/DatabaseConnection.js').default} database
13
+ */
14
+ constructor(database) {
15
+ this.db = database.getDb();
16
+ this.logger = Logger.getInstance();
17
+ this._prepareStatements();
18
+ }
19
+
20
+ /** @private 预编译常用语句 */
21
+ _prepareStatements() {
22
+ this._insertStmt = this.db.prepare(`
23
+ INSERT INTO tasks (
24
+ id, parent_id, child_seq,
25
+ title, description, design, acceptance, notes,
26
+ status, priority, task_type, close_reason, content_hash,
27
+ fail_count, last_fail_reason,
28
+ assignee, created_by,
29
+ created_at, updated_at, closed_at,
30
+ metadata
31
+ ) VALUES (
32
+ @id, @parent_id, @child_seq,
33
+ @title, @description, @design, @acceptance, @notes,
34
+ @status, @priority, @task_type, @close_reason, @content_hash,
35
+ @fail_count, @last_fail_reason,
36
+ @assignee, @created_by,
37
+ @created_at, @updated_at, @closed_at,
38
+ @metadata
39
+ )
40
+ `);
41
+
42
+ this._findByIdStmt = this.db.prepare('SELECT * FROM tasks WHERE id = ? LIMIT 1');
43
+
44
+ this._findByHashStmt = this.db.prepare(
45
+ "SELECT * FROM tasks WHERE content_hash = ? AND status != 'closed' LIMIT 1"
46
+ );
47
+
48
+ this._updateFieldsStmt = null; // 动态构建
49
+
50
+ this._deleteStmt = this.db.prepare('DELETE FROM tasks WHERE id = ?');
51
+
52
+ // ── 依赖相关 ──
53
+ this._addDepStmt = this.db.prepare(`
54
+ INSERT OR IGNORE INTO task_dependencies (task_id, depends_on_id, dep_type, created_at, created_by)
55
+ VALUES (?, ?, ?, ?, 'agent')
56
+ `);
57
+
58
+ this._removeDepStmt = this.db.prepare(
59
+ 'DELETE FROM task_dependencies WHERE task_id = ? AND depends_on_id = ? AND dep_type = ?'
60
+ );
61
+
62
+ this._getDepsStmt = this.db.prepare(
63
+ 'SELECT * FROM task_dependencies WHERE task_id = ? ORDER BY created_at'
64
+ );
65
+
66
+ this._getDependentsStmt = this.db.prepare(
67
+ 'SELECT * FROM task_dependencies WHERE depends_on_id = ?'
68
+ );
69
+
70
+ this._getBlockersStmt = this.db.prepare(`
71
+ SELECT t.*
72
+ FROM task_dependencies td
73
+ JOIN tasks t ON td.depends_on_id = t.id
74
+ WHERE td.task_id = ?
75
+ AND td.dep_type IN ('blocks', 'waits-for')
76
+ AND t.status != 'closed'
77
+ `);
78
+
79
+ // ── 环检测 (递归 CTE) ──
80
+ this._reachableStmt = this.db.prepare(`
81
+ WITH RECURSIVE reachable(id, depth) AS (
82
+ SELECT depends_on_id, 1
83
+ FROM task_dependencies
84
+ WHERE task_id = ?
85
+ AND dep_type IN ('blocks', 'waits-for')
86
+
87
+ UNION ALL
88
+
89
+ SELECT td.depends_on_id, r.depth + 1
90
+ FROM task_dependencies td
91
+ JOIN reachable r ON td.task_id = r.id
92
+ WHERE td.dep_type IN ('blocks', 'waits-for')
93
+ AND r.depth < 50
94
+ )
95
+ SELECT 1 FROM reachable WHERE id = ? LIMIT 1
96
+ `);
97
+
98
+ // ── 事件审计 ──
99
+ this._logEventStmt = this.db.prepare(`
100
+ INSERT INTO task_events (task_id, event_type, actor, old_value, new_value, comment, created_at)
101
+ VALUES (?, ?, ?, ?, ?, ?, ?)
102
+ `);
103
+
104
+ // ── 统计 ──
105
+ this._statsStmt = this.db.prepare(`
106
+ SELECT
107
+ COUNT(*) as total,
108
+ SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END) as open,
109
+ SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) as in_progress,
110
+ SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END) as closed,
111
+ SUM(CASE WHEN status = 'deferred' THEN 1 ELSE 0 END) as deferred
112
+ FROM tasks
113
+ `);
114
+ }
115
+
116
+ // ═══ CRUD ═══════════════════════════════════════════
117
+
118
+ /**
119
+ * 创建任务
120
+ * @param {Task} task
121
+ * @returns {Task}
122
+ */
123
+ create(task) {
124
+ const row = this._entityToRow(task);
125
+ this._insertStmt.run(row);
126
+ return this.findById(task.id);
127
+ }
128
+
129
+ /**
130
+ * 按 ID 查询
131
+ * @param {string} id
132
+ * @returns {Task|null}
133
+ */
134
+ findById(id) {
135
+ const row = this._findByIdStmt.get(id);
136
+ return row ? Task.fromRow(row) : null;
137
+ }
138
+
139
+ /**
140
+ * 按内容哈希查询(去重用)
141
+ * @param {string} hash
142
+ * @returns {Task|null}
143
+ */
144
+ findByContentHash(hash) {
145
+ if (!hash) return null;
146
+ const row = this._findByHashStmt.get(hash);
147
+ return row ? Task.fromRow(row) : null;
148
+ }
149
+
150
+ /**
151
+ * 更新任务字段
152
+ * @param {string} id
153
+ * @param {object} fields — 部分字段 (camelCase)
154
+ * @returns {Task}
155
+ */
156
+ update(id, fields) {
157
+ const columnMap = {
158
+ status: 'status',
159
+ priority: 'priority',
160
+ assignee: 'assignee',
161
+ notes: 'notes',
162
+ description: 'description',
163
+ design: 'design',
164
+ acceptance: 'acceptance',
165
+ closeReason: 'close_reason',
166
+ closedAt: 'closed_at',
167
+ updatedAt: 'updated_at',
168
+ failCount: 'fail_count',
169
+ lastFailReason: 'last_fail_reason',
170
+ childSeq: 'child_seq',
171
+ metadata: 'metadata',
172
+ };
173
+
174
+ const setClauses = [];
175
+ const values = [];
176
+
177
+ for (const [key, value] of Object.entries(fields)) {
178
+ const col = columnMap[key];
179
+ if (!col) continue;
180
+ setClauses.push(`${col} = ?`);
181
+ values.push(key === 'metadata' ? JSON.stringify(value) : value);
182
+ }
183
+
184
+ if (setClauses.length === 0) return this.findById(id);
185
+
186
+ // 始终更新 updated_at
187
+ if (!fields.updatedAt) {
188
+ setClauses.push('updated_at = ?');
189
+ values.push(Math.floor(Date.now() / 1000));
190
+ }
191
+
192
+ values.push(id);
193
+ const sql = `UPDATE tasks SET ${setClauses.join(', ')} WHERE id = ?`;
194
+ this.db.prepare(sql).run(...values);
195
+
196
+ return this.findById(id);
197
+ }
198
+
199
+ /**
200
+ * 删除任务
201
+ * @param {string} id
202
+ * @returns {boolean}
203
+ */
204
+ delete(id) {
205
+ const result = this._deleteStmt.run(id);
206
+ return result.changes > 0;
207
+ }
208
+
209
+ /**
210
+ * 列表查询
211
+ * @param {object} filters — { status, taskType, assignee, parentId }
212
+ * @param {object} options — { limit, offset, orderBy }
213
+ * @returns {Task[]}
214
+ */
215
+ findAll(filters = {}, options = {}) {
216
+ const conditions = [];
217
+ const params = [];
218
+
219
+ if (filters.status) {
220
+ conditions.push('status = ?');
221
+ params.push(filters.status);
222
+ }
223
+ if (filters.taskType) {
224
+ conditions.push('task_type = ?');
225
+ params.push(filters.taskType);
226
+ }
227
+ if (filters.assignee) {
228
+ conditions.push('assignee = ?');
229
+ params.push(filters.assignee);
230
+ }
231
+ if (filters.parentId) {
232
+ conditions.push('parent_id = ?');
233
+ params.push(filters.parentId);
234
+ }
235
+
236
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
237
+ const limit = Math.max(1, Math.min(options.limit || 50, 500));
238
+ const offset = Math.max(0, options.offset || 0);
239
+ const orderBy = _sanitizeOrderBy(options.orderBy);
240
+
241
+ const sql = `SELECT * FROM tasks ${where} ORDER BY ${orderBy} LIMIT ? OFFSET ?`;
242
+ params.push(limit, offset);
243
+
244
+ const rows = this.db.prepare(sql).all(...params);
245
+ return rows.map((r) => Task.fromRow(r));
246
+ }
247
+
248
+ // ═══ 依赖管理 ═══════════════════════════════════════
249
+
250
+ /**
251
+ * 添加依赖
252
+ */
253
+ addDependency(taskId, dependsOnId, depType) {
254
+ this._addDepStmt.run(taskId, dependsOnId, depType, Math.floor(Date.now() / 1000));
255
+ }
256
+
257
+ /**
258
+ * 删除依赖
259
+ */
260
+ removeDependency(taskId, dependsOnId, depType) {
261
+ this._removeDepStmt.run(taskId, dependsOnId, depType);
262
+ }
263
+
264
+ /**
265
+ * 获取任务的所有依赖
266
+ */
267
+ getDependencies(taskId) {
268
+ return this._getDepsStmt.all(taskId);
269
+ }
270
+
271
+ /**
272
+ * 获取依赖此任务的所有任务
273
+ */
274
+ getDependents(dependsOnId) {
275
+ return this._getDependentsStmt.all(dependsOnId);
276
+ }
277
+
278
+ /**
279
+ * 获取阻塞某任务的所有任务(含任务详情)
280
+ */
281
+ getBlockers(taskId) {
282
+ return this._getBlockersStmt.all(taskId).map((r) => Task.fromRow(r));
283
+ }
284
+
285
+ /**
286
+ * 环检测:检查 fromId → toId 是否已有可达路径
287
+ * @param {string} fromId
288
+ * @param {string} toId
289
+ * @returns {boolean}
290
+ */
291
+ hasReachablePath(fromId, toId) {
292
+ const row = this._reachableStmt.get(fromId, toId);
293
+ return !!row;
294
+ }
295
+
296
+ // ═══ 事务支持 ═══════════════════════════════════════
297
+
298
+ /**
299
+ * 在事务中执行操作
300
+ * @param {Function} fn
301
+ * @returns {*}
302
+ */
303
+ inTransaction(fn) {
304
+ const txn = this.db.transaction(fn);
305
+ return txn();
306
+ }
307
+
308
+ // ═══ 统计 ═══════════════════════════════════════════
309
+
310
+ /**
311
+ * 获取任务统计
312
+ */
313
+ getStatistics() {
314
+ const row = this._statsStmt.get();
315
+ return {
316
+ total: row.total || 0,
317
+ open: row.open || 0,
318
+ in_progress: row.in_progress || 0,
319
+ closed: row.closed || 0,
320
+ deferred: row.deferred || 0,
321
+ };
322
+ }
323
+
324
+ // ═══ 事件审计 ═══════════════════════════════════════
325
+
326
+ /**
327
+ * 记录任务事件
328
+ */
329
+ logEvent(taskId, eventType, oldValue = null, newValue = null, comment = null, actor = 'agent') {
330
+ this._logEventStmt.run(
331
+ taskId,
332
+ eventType,
333
+ actor,
334
+ oldValue,
335
+ newValue,
336
+ comment,
337
+ Math.floor(Date.now() / 1000)
338
+ );
339
+ }
340
+
341
+ // ═══ 私有方法 ═══════════════════════════════════════
342
+
343
+ /**
344
+ * 实体 → DB 行 (camelCase → snake_case)
345
+ * @param {Task} task
346
+ * @returns {object}
347
+ */
348
+ _entityToRow(task) {
349
+ return {
350
+ id: task.id,
351
+ parent_id: task.parentId || null,
352
+ child_seq: task.childSeq || 0,
353
+ title: task.title,
354
+ description: task.description || '',
355
+ design: task.design || '',
356
+ acceptance: task.acceptance || '',
357
+ notes: task.notes || '',
358
+ status: task.status,
359
+ priority: task.priority ?? 2,
360
+ task_type: task.taskType || 'task',
361
+ close_reason: task.closeReason || '',
362
+ content_hash: task.contentHash || '',
363
+ fail_count: task.failCount || 0,
364
+ last_fail_reason: task.lastFailReason || '',
365
+ assignee: task.assignee || '',
366
+ created_by: task.createdBy || 'agent',
367
+ created_at: task.createdAt,
368
+ updated_at: task.updatedAt,
369
+ closed_at: task.closedAt || null,
370
+ metadata: JSON.stringify(task.metadata || {}),
371
+ };
372
+ }
373
+ }
374
+
375
+ // ═══ 内部工具 ═══════════════════════════════════════
376
+
377
+ const ALLOWED_ORDER_FIELDS = new Set([
378
+ 'priority', 'created_at', 'updated_at', 'status', 'title', 'task_type', 'closed_at',
379
+ ]);
380
+ const ALLOWED_DIRECTIONS = new Set(['ASC', 'DESC']);
381
+
382
+ /**
383
+ * orderBy 白名单校验,防止 SQL 注入
384
+ * @param {string} [orderBy]
385
+ * @returns {string}
386
+ */
387
+ function _sanitizeOrderBy(orderBy) {
388
+ if (!orderBy) return 'priority ASC, created_at ASC';
389
+ return orderBy.split(',').map((clause) => {
390
+ const parts = clause.trim().split(/\s+/);
391
+ const field = parts[0];
392
+ if (!ALLOWED_ORDER_FIELDS.has(field)) return null;
393
+ const dir = ALLOWED_DIRECTIONS.has(parts[1]?.toUpperCase()) ? parts[1].toUpperCase() : 'ASC';
394
+ return `${field} ${dir}`;
395
+ }).filter(Boolean).join(', ') || 'priority ASC, created_at ASC';
396
+ }
397
+
398
+ export default TaskRepositoryImpl;
@@ -55,6 +55,7 @@ const MCP_TOOLS_SUMMARY = [
55
55
  { name: 'autosnippet_skill', desc: 'Skill management (list/load/create/update/delete/suggest)' },
56
56
  { name: 'autosnippet_save_document', desc: 'Save development document (auto-publish)' },
57
57
  { name: 'autosnippet_bootstrap', desc: 'Project cold-start & scan (knowledge/refine/scan)' },
58
+ { name: 'autosnippet_task', desc: 'TaskGraph lifecycle (prime/claim/close/ready/create/fail/defer)' },
58
59
  { name: 'autosnippet_health', desc: 'Service health & KB statistics' },
59
60
  { name: 'autosnippet_capabilities', desc: 'List all available MCP tools (self-discovery)' },
60
61
  ];
@@ -278,16 +279,19 @@ export class AgentInstructionsGenerator {
278
279
  // Key tools highlight for Claude
279
280
  lines.push('### Recommended Workflow', '');
280
281
  lines.push(
281
- '1. **Before writing code**: `autosnippet_search({ query: "<topic>" })` to find relevant patterns'
282
+ '1. **Session start**: `autosnippet_task({ operation: "prime" })` Restore task context'
282
283
  );
283
284
  lines.push(
284
- '2. **Check compliance**: `autosnippet_guard({ code: "<your code>" })` before committing'
285
+ '2. **Before writing code**: `autosnippet_search({ query: "<topic>" })` to find relevant patterns'
285
286
  );
286
287
  lines.push(
287
- '3. **Record knowledge**: `autosnippet_submit_knowledge({ ... })` when discovering reusable patterns'
288
+ '3. **Check compliance**: `autosnippet_guard({ code: "<your code>" })` before committing'
288
289
  );
289
290
  lines.push(
290
- '4. **Confirm adoption**: `autosnippet_knowledge({ operation: "confirm_usage", id: "<id>" })`'
291
+ '4. **Complete task**: `autosnippet_task({ operation: "close", id: "<id>" })`'
292
+ );
293
+ lines.push(
294
+ '5. **Guard diagnostics**: Read ruleId → `autosnippet_search({ query: "<ruleId>" })` → fix'
291
295
  );
292
296
  lines.push('');
293
297
 
@@ -400,11 +404,26 @@ export class AgentInstructionsGenerator {
400
404
  return [
401
405
  '## Recommended Workflow',
402
406
  '',
403
- '1. **Search first**: `autosnippet_search({ query: "<topic>" })` before writing code',
404
- '2. **Check patterns**: Look for existing `@trigger` patterns before implementing',
405
- '3. **Guard check**: `autosnippet_guard({ code: "<code>" })` to validate compliance',
406
- '4. **Submit discoveries**: `autosnippet_submit_knowledge({ ... })` for reusable patterns',
407
- '5. **Confirm adoption**: `autosnippet_knowledge({ operation: "confirm_usage" })`',
407
+ '### Session Start (ALWAYS)',
408
+ '1. `autosnippet_task({ operation: "prime" })` Restore task context',
409
+ '',
410
+ '### Task Lifecycle',
411
+ '2. `autosnippet_task({ operation: "claim", id: "asd-xxx" })` — Start working',
412
+ '3. `autosnippet_search({ query: "<topic>" })` — Search knowledge before coding',
413
+ '4. **CODE** — Write the implementation',
414
+ '5. `autosnippet_guard({ code: "<code>" })` — Check compliance',
415
+ '6. `autosnippet_task({ operation: "close", id: "asd-xxx" })` — Complete',
416
+ '',
417
+ '### Guard Diagnostics Response',
418
+ 'When editor shows diagnostics from "AutoSnippet Guard":',
419
+ '1. Read the `ruleId` from the diagnostic message',
420
+ '2. `autosnippet_search({ query: "<ruleId>" })` to find the Recipe',
421
+ '3. Apply the Recipe\'s `doClause` + `coreCode` to fix',
422
+ '4. Save and verify the diagnostic disappears',
423
+ '',
424
+ '### Context Pressure',
425
+ '- `_contextHint: CONTEXT_PRESSURE:WARNING` → Summarize completed work, then continue',
426
+ '- `_contextHint: CONTEXT_PRESSURE:CRITICAL` → Call `autosnippet_task({ operation: "prime" })` immediately',
408
427
  '',
409
428
  ];
410
429
  }