autosnippet 3.3.2 → 3.3.4

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 (55) hide show
  1. package/README.md +8 -4
  2. package/dist/bin/cli.js +27 -1
  3. package/dist/lib/cli/KnowledgeSyncService.d.ts +26 -0
  4. package/dist/lib/cli/KnowledgeSyncService.js +33 -1
  5. package/dist/lib/external/mcp/handlers/browse.d.ts +1 -0
  6. package/dist/lib/external/mcp/handlers/browse.js +2 -1
  7. package/dist/lib/external/mcp/handlers/consolidated.d.ts +1 -0
  8. package/dist/lib/external/mcp/handlers/panorama.d.ts +11 -11
  9. package/dist/lib/external/mcp/handlers/panorama.js +20 -20
  10. package/dist/lib/external/mcp/handlers/system.d.ts +1 -1
  11. package/dist/lib/external/mcp/handlers/task.js +38 -15
  12. package/dist/lib/external/mcp/tools.d.ts +12 -12
  13. package/dist/lib/external/mcp/tools.js +120 -118
  14. package/dist/lib/http/middleware/validate.js +7 -3
  15. package/dist/lib/infrastructure/database/drizzle/schema.d.ts +100 -0
  16. package/dist/lib/infrastructure/database/drizzle/schema.js +10 -0
  17. package/dist/lib/infrastructure/database/migrations/005_recipe_source_refs.d.ts +9 -0
  18. package/dist/lib/infrastructure/database/migrations/005_recipe_source_refs.js +24 -0
  19. package/dist/lib/infrastructure/vector/HnswVectorAdapter.js +18 -2
  20. package/dist/lib/injection/ServiceContainer.js +2 -0
  21. package/dist/lib/injection/modules/KnowledgeModule.d.ts +5 -0
  22. package/dist/lib/injection/modules/KnowledgeModule.js +80 -0
  23. package/dist/lib/service/bootstrap/UiStartupTasks.d.ts +45 -0
  24. package/dist/lib/service/bootstrap/UiStartupTasks.js +101 -0
  25. package/dist/lib/service/evolution/ConsolidationAdvisor.js +9 -9
  26. package/dist/lib/service/evolution/ContradictionDetector.js +2 -2
  27. package/dist/lib/service/evolution/RedundancyAnalyzer.js +2 -2
  28. package/dist/lib/service/knowledge/SourceRefReconciler.d.ts +68 -0
  29. package/dist/lib/service/knowledge/SourceRefReconciler.js +309 -0
  30. package/dist/lib/service/panorama/PanoramaService.d.ts +18 -1
  31. package/dist/lib/service/panorama/PanoramaService.js +148 -5
  32. package/dist/lib/service/search/BM25Scorer.d.ts +2 -2
  33. package/dist/lib/service/search/CoarseRanker.d.ts +7 -6
  34. package/dist/lib/service/search/CoarseRanker.js +11 -10
  35. package/dist/lib/service/search/FieldWeightedScorer.d.ts +81 -0
  36. package/dist/lib/service/search/FieldWeightedScorer.js +318 -0
  37. package/dist/lib/service/search/MultiSignalRanker.d.ts +2 -2
  38. package/dist/lib/service/search/MultiSignalRanker.js +1 -1
  39. package/dist/lib/service/search/SearchEngine.d.ts +8 -7
  40. package/dist/lib/service/search/SearchEngine.js +59 -10
  41. package/dist/lib/service/search/SearchTypes.d.ts +23 -3
  42. package/dist/lib/service/search/SearchTypes.js +6 -1
  43. package/dist/lib/service/task/IntentExtractor.d.ts +11 -1
  44. package/dist/lib/service/task/IntentExtractor.js +137 -3
  45. package/dist/lib/service/task/PrimeSearchPipeline.js +95 -25
  46. package/dist/lib/service/vector/VectorService.d.ts +3 -0
  47. package/dist/lib/service/vector/VectorService.js +38 -4
  48. package/dist/lib/shared/schemas/mcp-tools.d.ts +1 -0
  49. package/dist/lib/shared/schemas/mcp-tools.js +5 -1
  50. package/package.json +1 -1
  51. package/skills/autosnippet-create/SKILL.md +98 -89
  52. package/skills/autosnippet-devdocs/SKILL.md +55 -60
  53. package/templates/guard-ci.yml +2 -2
  54. package/templates/instructions/conventions.md +4 -2
  55. package/templates/recipes-setup/_template.md +39 -39
@@ -6,6 +6,7 @@
6
6
  *
7
7
  * @module service/task/IntentExtractor
8
8
  */
9
+ import { tokenize } from '#service/search/tokenizer.js';
9
10
  // ── Universal Patterns (language-agnostic) ──────────
10
11
  const UNIVERSAL_PATTERNS = [
11
12
  /\b[A-Z][a-z]+(?:[A-Z][a-z]+)+\b/g, // CamelCase
@@ -29,6 +30,81 @@ const LANG_MAP = {
29
30
  java: 'java',
30
31
  kt: 'kotlin',
31
32
  };
33
+ // ── Cross-Language Synonym Groups ───────────────────
34
+ // Each group contains EN morphological variants + CN equivalents.
35
+ // Used to expand queries so English terms match Chinese recipe fields (and vice versa).
36
+ const SYNONYM_GROUPS = [
37
+ // Design patterns & DI
38
+ ['inject', 'injection', '注入'],
39
+ ['construct', 'constructor', '构造器', '构造函数'],
40
+ ['depend', 'dependency', 'dependencies', '依赖'],
41
+ ['protocol', '协议'],
42
+ ['interface', '接口'],
43
+ ['pattern', '模式'],
44
+ ['factory', '工厂'],
45
+ ['singleton', '单例'],
46
+ ['delegate', '代理', '委托'],
47
+ ['observe', 'observer', '观察者'],
48
+ ['subscribe', 'subscription', '订阅'],
49
+ ['repository', 'repo', '仓库'],
50
+ // Architecture
51
+ ['module', '模块'],
52
+ ['architect', 'architecture', '架构'],
53
+ ['route', 'router', 'routing', '路由'],
54
+ ['middleware', '中间件'],
55
+ ['component', '组件'],
56
+ ['lifecycle', '生命周期'],
57
+ ['layer', '分层', '层'],
58
+ // Language features
59
+ ['generic', 'generics', '泛型'],
60
+ ['closure', '闭包'],
61
+ ['callback', '回调'],
62
+ ['extend', 'extension', '扩展'],
63
+ ['inherit', 'inheritance', '继承'],
64
+ ['abstract', 'abstraction', '抽象'],
65
+ ['encapsulate', 'encapsulation', '封装'],
66
+ ['polymorph', 'polymorphism', '多态'],
67
+ ['implement', 'implementation', '实现'],
68
+ // Concurrency
69
+ ['async', 'asynchronous', '异步'],
70
+ ['sync', 'synchronous', '同步'],
71
+ ['thread', 'threading', '线程'],
72
+ ['concur', 'concurrency', '并发'],
73
+ // Memory management
74
+ ['memory', '内存'],
75
+ ['leak', 'leakage', '泄漏'],
76
+ ['weak', '弱引用'],
77
+ ['retain', '持有', '保留'],
78
+ ['release', '释放'],
79
+ ['reference', '引用'],
80
+ // Common concepts
81
+ ['network', '网络'],
82
+ ['cache', 'caching', '缓存'],
83
+ ['persist', 'persistence', '持久化'],
84
+ ['serialize', 'serialization', '序列化'],
85
+ ['validate', 'validation', '校验', '验证'],
86
+ ['authenticate', 'authentication', '认证'],
87
+ ['authorize', 'authorization', '授权'],
88
+ ['config', 'configuration', '配置'],
89
+ ['navigate', 'navigation', '导航'],
90
+ ['animate', 'animation', '动画'],
91
+ ['layout', '布局'],
92
+ ['render', 'rendering', '渲染'],
93
+ ['responsive', '响应式'],
94
+ ['state', '状态'],
95
+ ['toast', '提示'],
96
+ ['error', '错误'],
97
+ ['handle', 'handler', '处理'],
98
+ ['service', '服务'],
99
+ ['test', 'testing', '测试'],
100
+ ];
101
+ /** Lookup: lowercased term → synonym expansions (excluding the term itself) */
102
+ const SYNONYM_LOOKUP = new Map();
103
+ for (const group of SYNONYM_GROUPS) {
104
+ for (const term of group) {
105
+ SYNONYM_LOOKUP.set(term.toLowerCase(), group.filter((t) => t !== term));
106
+ }
107
+ }
32
108
  // ── Public API ──────────────────────────────────────
33
109
  /**
34
110
  * Extract intent signals from user query and active file.
@@ -36,11 +112,13 @@ const LANG_MAP = {
36
112
  */
37
113
  export function extract(userQuery, activeFile, language, termOpts) {
38
114
  const queries = buildQueries(userQuery, activeFile, termOpts);
115
+ const keywordQueries = buildKeywordQueries(userQuery);
39
116
  const inferredLang = language || (activeFile ? inferLanguage(activeFile) : null);
40
117
  const module = activeFile ? inferFileContext(activeFile) : null;
41
118
  const scenario = classifyScenario(userQuery);
42
119
  return {
43
120
  queries,
121
+ keywordQueries,
44
122
  language: inferredLang,
45
123
  module,
46
124
  scenario,
@@ -49,14 +127,28 @@ export function extract(userQuery, activeFile, language, termOpts) {
49
127
  }
50
128
  /**
51
129
  * Build multi-query set from user query + active file.
52
- * Q1: raw query, Q2: extracted tech terms, Q3: file context.
130
+ * Q1: raw query, Q2: extracted tech terms, Q3: file context, Q4: synonym focus.
131
+ * Q1 is enriched with cross-language synonyms to bridge EN↔CJK matching.
132
+ * Q4 (long queries only): synonym expansion as a separate focused query
133
+ * to prevent BM25 dilution in verbose natural language inputs.
53
134
  */
54
135
  export function buildQueries(userQuery, activeFile, termOpts) {
55
- const queries = [userQuery];
136
+ // Enrich raw query with cross-language synonyms
137
+ const synonyms = expandWithSynonyms(userQuery);
138
+ const enrichedQuery = synonyms ? `${userQuery} ${synonyms}` : userQuery;
139
+ const queries = [enrichedQuery];
56
140
  const terms = extractTechTerms(userQuery, termOpts);
57
141
  if (terms.length > 0) {
58
142
  queries.push(terms.join(' '));
59
143
  }
144
+ // Q4: For long queries (> 50 chars), add cross-language synonyms as a
145
+ // separate focused query. In long sentences, synonym terms appended to Q1
146
+ // get diluted by common words ("ViewController", "ViewModel"), causing
147
+ // BM25 to miss the user's actual intent. A short focused query matches
148
+ // domain-specific terms (e.g. "singleton 单例 inject 注入") directly.
149
+ if (synonyms && userQuery.length > 50) {
150
+ queries.push(synonyms);
151
+ }
60
152
  if (activeFile) {
61
153
  const ctx = inferFileContext(activeFile);
62
154
  if (ctx) {
@@ -65,6 +157,14 @@ export function buildQueries(userQuery, activeFile, termOpts) {
65
157
  }
66
158
  return queries;
67
159
  }
160
+ /**
161
+ * Build keyword-mode queries for cross-language synonym matching.
162
+ * Uses keyword mode to preserve raw FWS scores without CoarseRanker semantic normalization.
163
+ */
164
+ export function buildKeywordQueries(userQuery) {
165
+ const expanded = expandWithSynonyms(userQuery);
166
+ return expanded ? [expanded] : [];
167
+ }
68
168
  /**
69
169
  * Extract tech terms from query using universal patterns + dynamic project prefixes.
70
170
  */
@@ -120,7 +220,7 @@ export function inferLanguage(filePath) {
120
220
  */
121
221
  export function classifyScenario(userQuery) {
122
222
  const q = userQuery.toLowerCase();
123
- if (/帮我[加写做实现创建]|implement|add|create|新[增加建]/.test(q)) {
223
+ if (/帮我[加写做实现创建]|implement|add|create|新[增加建]|添加|修改|删除|实现|开发|编写|创建|初始化/.test(q)) {
124
224
  return 'generate';
125
225
  }
126
226
  if (/检查|review|lint|合规|违规|guard|规[则范]/.test(q)) {
@@ -132,6 +232,40 @@ export function classifyScenario(userQuery) {
132
232
  return 'search';
133
233
  }
134
234
  // ── Internal Helpers ────────────────────────────────
235
+ /**
236
+ * Expand query tokens with cross-language synonyms.
237
+ * Tokenizes query, looks up each token in the synonym table,
238
+ * returns a query string of synonym expansions for cross-language matching.
239
+ *
240
+ * Strategy: per-token cross-script expansion. Each token's script is checked
241
+ * individually, and only synonyms in the OPPOSITE script are added.
242
+ * This correctly handles mixed EN/CJK queries (e.g. "在 module 里用 singleton")
243
+ * where both EN→CJK and CJK→EN expansions are needed.
244
+ */
245
+ function expandWithSynonyms(query) {
246
+ const tokens = tokenize(query);
247
+ const crossScriptTerms = new Set();
248
+ const CJK_RE = /[\u4e00-\u9fff\u3400-\u4dbf]/;
249
+ for (const token of tokens) {
250
+ const synonyms = SYNONYM_LOOKUP.get(token.toLowerCase());
251
+ if (!synonyms) {
252
+ continue;
253
+ }
254
+ // Determine THIS token's script, not the whole query's
255
+ const tokenIsCJK = CJK_RE.test(token);
256
+ for (const syn of synonyms) {
257
+ const synIsCJK = CJK_RE.test(syn);
258
+ // Cross-script: EN token → add CJK synonyms; CJK token → add EN synonyms
259
+ if (tokenIsCJK !== synIsCJK) {
260
+ crossScriptTerms.add(syn);
261
+ }
262
+ }
263
+ }
264
+ if (crossScriptTerms.size === 0) {
265
+ return null;
266
+ }
267
+ return [...crossScriptTerms].slice(0, 16).join(' ');
268
+ }
135
269
  function buildPrefixPattern(prefixes) {
136
270
  if (prefixes.length === 0) {
137
271
  return null;
@@ -8,7 +8,12 @@
8
8
  */
9
9
  import { slimSearchResult } from '#service/search/SearchTypes.js';
10
10
  // ── Constants ───────────────────────────────────────
11
- const RELEVANCE_THRESHOLD = 0.44;
11
+ /** Absolute minimum score — items below this are definitely noise */
12
+ const MIN_SCORE_THRESHOLD = 0.3;
13
+ /** Relative threshold — items scoring below this fraction of the best result are dropped */
14
+ const RELATIVE_SCORE_RATIO = 0.15;
15
+ /** Gap ratio — if score drops by more than this factor from the previous item, truncate */
16
+ const GAP_DROP_RATIO = 0.25;
12
17
  // ── PrimeSearchPipeline ─────────────────────────────
13
18
  export class PrimeSearchPipeline {
14
19
  #search;
@@ -29,10 +34,10 @@ export class PrimeSearchPipeline {
29
34
  intent: intent.scenario,
30
35
  sessionHistory: this.#buildSessionHistory(),
31
36
  };
32
- // Multi-query parallel search
33
- const allResults = await this.#multiQuerySearch(intent.queries, context);
34
- // Threshold filter
35
- const filtered = allResults.filter((r) => (r.score ?? 0) >= RELEVANCE_THRESHOLD);
37
+ // Multi-query parallel search (auto mode + keyword mode for cross-language)
38
+ const allResults = await this.#multiQuerySearch(intent.queries, intent.keywordQueries ?? [], context);
39
+ // Quality filter: absolute threshold + relative-to-best + score gap detection
40
+ const filtered = this.#qualityFilter(allResults);
36
41
  if (filtered.length === 0) {
37
42
  return null;
38
43
  }
@@ -62,32 +67,97 @@ export class PrimeSearchPipeline {
62
67
  }
63
68
  // ── Private ───────────────────────────────────────
64
69
  /**
65
- * Multi-query parallel search + de-dup by ID (keep highest score).
70
+ * Quality filter: absolute threshold + relative-to-best + score gap detection.
71
+ * Expects items sorted by score descending.
66
72
  */
67
- async #multiQuerySearch(queries, context) {
68
- const promises = queries.map((q) => this.#search
69
- .search(q, {
70
- mode: 'auto',
71
- limit: 8,
72
- rank: true,
73
- context,
74
- })
73
+ #qualityFilter(items) {
74
+ if (items.length === 0) {
75
+ return [];
76
+ }
77
+ const maxScore = items[0]?.score ?? 0;
78
+ const effectiveThreshold = Math.max(MIN_SCORE_THRESHOLD, maxScore * RELATIVE_SCORE_RATIO);
79
+ const result = [];
80
+ let prevScore = maxScore;
81
+ for (const item of items) {
82
+ const score = item.score;
83
+ if (score < effectiveThreshold) {
84
+ break;
85
+ }
86
+ // Gap detection: if score drops sharply from previous item, stop
87
+ if (result.length > 0 && score < prevScore * GAP_DROP_RATIO) {
88
+ break;
89
+ }
90
+ result.push(item);
91
+ prevScore = score;
92
+ }
93
+ return result;
94
+ }
95
+ /**
96
+ * Multi-query parallel search with optional Reciprocal Rank Fusion (RRF).
97
+ *
98
+ * Single-query: preserves original search engine scores (BM25/CoarseRanker).
99
+ * Multi-query: uses RRF to fuse results, but weights by original score to
100
+ * retain magnitude information.
101
+ */
102
+ async #multiQuerySearch(autoQueries, keywordQueries, context) {
103
+ // Auto-mode searches (BM25 without CoarseRanker ranking)
104
+ // Using rank: false preserves raw BM25/FWS score magnitude,
105
+ // which the quality filter needs for effective discrimination.
106
+ // CoarseRanker's max-normalization + freshness/popularity signals
107
+ // would cluster scores around 0.35–0.41, defeating the filter.
108
+ const autoPromises = autoQueries.map((q) => this.#search
109
+ .search(q, { mode: 'auto', limit: 8, rank: false, context })
110
+ .catch(() => ({ items: [] })));
111
+ // Keyword-mode searches (raw FWS scores — for cross-language synonym matching)
112
+ const kwPromises = keywordQueries.map((q) => this.#search
113
+ .search(q, { mode: 'keyword', limit: 8, rank: false })
75
114
  .catch(() => ({ items: [] })));
76
- const responses = await Promise.all(promises);
77
- // Merge by ID, keep highest score
78
- const bestById = new Map();
79
- for (const resp of responses) {
80
- const items = resp.items || [];
81
- for (const raw of items) {
115
+ const [autoResponses, kwResponses] = await Promise.all([
116
+ Promise.all(autoPromises),
117
+ Promise.all(kwPromises),
118
+ ]);
119
+ const allResponses = [...autoResponses, ...kwResponses];
120
+ // Single-query shortcut: preserve original scores from search engine.
121
+ // RRF is pointless with one response — it just converts rank to score,
122
+ // discarding the magnitude information from BM25/CoarseRanker.
123
+ if (allResponses.length === 1) {
124
+ const items = (allResponses[0]?.items || []);
125
+ return items.map(slimSearchResult).sort((a, b) => b.score - a.score);
126
+ }
127
+ // Multi-query: Weighted RRF — RRF(d) = Σ origScore / (k + rank)
128
+ // Retains original score magnitude while still boosting cross-query overlap.
129
+ const RRF_K = 60;
130
+ const rrfScores = new Map();
131
+ const itemById = new Map();
132
+ for (const resp of allResponses) {
133
+ const items = (resp.items || []);
134
+ for (let rank = 0; rank < items.length; rank++) {
135
+ const raw = items[rank];
136
+ const origScore = Math.max(raw.score || 0, 0.01);
82
137
  const item = slimSearchResult(raw);
83
- const existing = bestById.get(item.id);
84
- if (!existing || item.score > existing.score) {
85
- bestById.set(item.id, item);
138
+ rrfScores.set(item.id, (rrfScores.get(item.id) ?? 0) + origScore / (RRF_K + rank));
139
+ // Keep the richest metadata version
140
+ if (!itemById.has(item.id)) {
141
+ itemById.set(item.id, item);
86
142
  }
87
143
  }
88
144
  }
89
- // Sort by score descending
90
- return [...bestById.values()].sort((a, b) => b.score - a.score);
145
+ // Assign fused scores and sort
146
+ // Rescale: RRF_K division crushes scores to ~0.003–0.02 range,
147
+ // which falls below qualityFilter's MIN_SCORE_THRESHOLD (0.1).
148
+ // Multiply by RRF_K to restore original score magnitude.
149
+ // Effective formula: Σ origScore / (1 + rank/K), preserving magnitude
150
+ // while still giving a gentle rank-based discount.
151
+ const results = [];
152
+ for (const [id, rrfScore] of rrfScores) {
153
+ const item = itemById.get(id);
154
+ if (!item) {
155
+ continue;
156
+ }
157
+ item.score = Math.round(rrfScore * RRF_K * 1000) / 1000;
158
+ results.push(item);
159
+ }
160
+ return results.sort((a, b) => b.score - a.score);
91
161
  }
92
162
  /**
93
163
  * Build sessionHistory for contextBoost (last 5 queries).
@@ -109,6 +109,9 @@ export declare class VectorService {
109
109
  /**
110
110
  * 混合搜索 (Dense + Sparse RRF 融合)
111
111
  * 通过 HybridRetriever 执行向量 + BM25 关键词并行检索
112
+ *
113
+ * Embed 失败时优雅降级: 跳过 Dense 路, 仅用 Sparse 结果进行 RRF 融合,
114
+ * 避免因网络问题导致整个搜索返回空结果。
112
115
  */
113
116
  hybridSearch(query: string, opts?: {
114
117
  topK?: number;
@@ -26,6 +26,11 @@ export class VectorService {
26
26
  #syncDebounceMs;
27
27
  #logger = Logger.getInstance();
28
28
  #initialized = false;
29
+ // ── Embed circuit breaker ──
30
+ #embedConsecutiveFailures = 0;
31
+ #embedCircuitOpenUntil = 0;
32
+ static #EMBED_CIRCUIT_THRESHOLD = 3;
33
+ static #EMBED_CIRCUIT_COOLDOWN_MS = 60_000;
29
34
  constructor(config) {
30
35
  this.#vectorStore = config.vectorStore;
31
36
  this.#indexingPipeline = config.indexingPipeline;
@@ -211,6 +216,9 @@ export class VectorService {
211
216
  /**
212
217
  * 混合搜索 (Dense + Sparse RRF 融合)
213
218
  * 通过 HybridRetriever 执行向量 + BM25 关键词并行检索
219
+ *
220
+ * Embed 失败时优雅降级: 跳过 Dense 路, 仅用 Sparse 结果进行 RRF 融合,
221
+ * 避免因网络问题导致整个搜索返回空结果。
214
222
  */
215
223
  async hybridSearch(query, opts = {}) {
216
224
  if (!this.#embedProvider) {
@@ -226,11 +234,37 @@ export class VectorService {
226
234
  }));
227
235
  }
228
236
  const { topK = 10, alpha = 0.5, sparseSearchFn = null } = opts;
237
+ // Embed query — circuit breaker skips embed after repeated failures
238
+ let queryVector = null;
239
+ const circuitOpen = Date.now() < this.#embedCircuitOpenUntil;
240
+ if (circuitOpen) {
241
+ this.#logger.debug('[VectorService] embed circuit open, skipping embed');
242
+ }
243
+ else {
244
+ try {
245
+ const embedResult = await this.#embedProvider.embed(query);
246
+ queryVector = Array.isArray(embedResult[0])
247
+ ? embedResult[0]
248
+ : embedResult;
249
+ this.#embedConsecutiveFailures = 0;
250
+ }
251
+ catch (err) {
252
+ this.#embedConsecutiveFailures++;
253
+ if (this.#embedConsecutiveFailures >= VectorService.#EMBED_CIRCUIT_THRESHOLD) {
254
+ this.#embedCircuitOpenUntil = Date.now() + VectorService.#EMBED_CIRCUIT_COOLDOWN_MS;
255
+ this.#logger.warn('[VectorService] embed circuit OPEN — skipping embed for 60s', {
256
+ consecutiveFailures: this.#embedConsecutiveFailures,
257
+ });
258
+ }
259
+ else {
260
+ this.#logger.warn('[VectorService] embed failed, degrading to sparse-only', {
261
+ error: err instanceof Error ? err.message : String(err),
262
+ failCount: this.#embedConsecutiveFailures,
263
+ });
264
+ }
265
+ }
266
+ }
229
267
  try {
230
- const embedResult = await this.#embedProvider.embed(query);
231
- const queryVector = Array.isArray(embedResult[0])
232
- ? embedResult[0]
233
- : embedResult;
234
268
  const fused = await this.#hybridRetriever.search(query, queryVector, {
235
269
  topK,
236
270
  alpha,
@@ -247,6 +247,7 @@ export declare const TaskInput: z.ZodObject<{
247
247
  title: z.ZodOptional<z.ZodString>;
248
248
  description: z.ZodOptional<z.ZodString>;
249
249
  id: z.ZodOptional<z.ZodString>;
250
+ taskId: z.ZodOptional<z.ZodString>;
250
251
  reason: z.ZodOptional<z.ZodString>;
251
252
  rationale: z.ZodOptional<z.ZodString>;
252
253
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -235,7 +235,11 @@ export const TaskInput = z.object({
235
235
  .describe('prime=加载知识上下文 | create=创建任务锚点 | close=完成+Guard | fail=放弃 | record_decision=记录用户偏好'),
236
236
  title: z.string().optional().describe('Task or decision title (create / record_decision)'),
237
237
  description: z.string().optional().describe('Decision description (record_decision)'),
238
- id: z.string().optional().describe('Task ID (close / fail)'),
238
+ id: z
239
+ .string()
240
+ .optional()
241
+ .describe('Task ID (close / fail). Optional if a task was created in the current session.'),
242
+ taskId: z.string().optional().describe('Alias for id (accepted for convenience)'),
239
243
  reason: z.string().optional().describe('Close reason or fail reason'),
240
244
  rationale: z.string().optional().describe('Decision rationale (record_decision)'),
241
245
  tags: z.array(z.string()).optional().describe('Decision tags (record_decision)'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autosnippet",
3
- "version": "3.3.2",
3
+ "version": "3.3.4",
4
4
  "description": "Extract code patterns into a knowledge base for AI coding assistants",
5
5
  "type": "module",
6
6
  "main": "dist/lib/bootstrap.js",