autosnippet 3.2.7 → 3.2.9
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/bin/cli.js +13 -5
- package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
- package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
- package/dashboard/dist/index.html +2 -2
- package/lib/cli/AiScanService.js +26 -29
- package/lib/cli/SetupService.js +1 -1
- package/lib/core/AstAnalyzer.js +27 -5
- package/lib/core/analysis/CallEdgeResolver.js +402 -0
- package/lib/core/analysis/CallGraphAnalyzer.js +367 -0
- package/lib/core/analysis/CallSiteExtractor.js +629 -0
- package/lib/core/analysis/DataFlowInferrer.js +57 -0
- package/lib/core/analysis/ImportPathResolver.js +189 -0
- package/lib/core/analysis/ImportRecord.js +105 -0
- package/lib/core/analysis/SymbolTableBuilder.js +211 -0
- package/lib/core/ast/ProjectGraph.js +8 -0
- package/lib/core/ast/lang-dart.js +352 -5
- package/lib/core/ast/lang-go.js +212 -10
- package/lib/core/ast/lang-java.js +205 -1
- package/lib/core/ast/lang-kotlin.js +330 -1
- package/lib/core/ast/lang-python.js +31 -2
- package/lib/core/ast/lang-rust.js +284 -3
- package/lib/core/ast/lang-swift.js +180 -1
- package/lib/core/ast/lang-typescript.js +290 -1
- package/lib/core/discovery/index.js +2 -2
- package/lib/external/ai/AiProvider.js +66 -172
- package/lib/external/ai/providers/GoogleGeminiProvider.js +23 -1
- package/lib/external/mcp/McpServer.js +1 -0
- package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
- package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +22 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
- package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +311 -162
- package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +102 -7
- package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
- package/lib/external/mcp/handlers/bootstrap-external.js +9 -2
- package/lib/external/mcp/handlers/bootstrap-internal.js +19 -8
- package/lib/external/mcp/handlers/consolidated.js +9 -0
- package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
- package/lib/external/mcp/handlers/guard.js +3 -3
- package/lib/external/mcp/handlers/structure.js +62 -0
- package/lib/external/mcp/handlers/wiki-external.js +66 -3
- package/lib/external/mcp/tools.js +36 -1
- package/lib/http/HttpServer.js +1 -1
- package/lib/http/middleware/requestLogger.js +1 -0
- package/lib/http/routes/ai.js +240 -35
- package/lib/http/routes/candidates.js +2 -3
- package/lib/http/routes/extract.js +13 -11
- package/lib/http/routes/modules.js +2 -2
- package/lib/http/routes/recipes.js +9 -5
- package/lib/http/routes/remote.js +149 -270
- package/lib/http/routes/violations.js +0 -54
- package/lib/http/utils/sse-sessions.js +1 -1
- package/lib/infrastructure/logging/Logger.js +5 -4
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
- package/lib/injection/ServiceContainer.js +70 -28
- package/lib/platform/ScreenCaptureService.js +177 -0
- package/lib/platform/ios/index.js +2 -2
- package/lib/platform/ios/routes/spm.js +2 -2
- package/lib/platform/ios/spm/PackageSwiftParser.js +14 -3
- package/lib/platform/ios/spm/SpmDiscoverer.js +123 -17
- package/lib/platform/ios/spm/{SpmService.js → SpmHelper.js} +43 -675
- package/lib/platform/ios/xcode/XcodeWriteUtils.js +1 -1
- package/lib/service/agent/AgentEventBus.js +207 -0
- package/lib/service/agent/AgentFactory.js +490 -0
- package/lib/service/agent/AgentMessage.js +240 -0
- package/lib/service/agent/AgentRouter.js +228 -0
- package/lib/service/agent/AgentRuntime.js +1016 -0
- package/lib/service/agent/AgentState.js +217 -0
- package/lib/service/agent/IntentClassifier.js +331 -0
- package/lib/service/agent/LarkTransport.js +389 -0
- package/lib/service/agent/capabilities.js +408 -0
- package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
- package/lib/service/{chat → agent/context}/ExplorationTracker.js +77 -22
- package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +14 -2
- package/lib/service/agent/core/LoopContext.js +170 -0
- package/lib/service/agent/core/MessageAdapter.js +223 -0
- package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
- package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +19 -98
- package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
- package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
- package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
- package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +91 -123
- package/lib/service/agent/domain/insight-producer.js +267 -0
- package/lib/service/agent/domain/scan-prompts.js +105 -0
- package/lib/service/agent/forced-summary.js +266 -0
- package/lib/service/agent/index.js +91 -0
- package/lib/service/{chat → agent}/memory/ActiveContext.js +3 -1
- package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
- package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
- package/lib/service/{chat → agent}/memory/SessionStore.js +5 -4
- package/lib/service/{chat → agent}/memory/index.js +1 -1
- package/lib/service/agent/policies.js +442 -0
- package/lib/service/agent/presets.js +303 -0
- package/lib/service/agent/strategies.js +717 -0
- package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
- package/lib/service/agent/tools/ai-analysis.js +75 -0
- package/lib/service/{chat → agent}/tools/ast-graph.js +229 -32
- package/lib/service/{chat → agent}/tools/composite.js +2 -1
- package/lib/service/{chat → agent}/tools/guard.js +1 -121
- package/lib/service/{chat → agent}/tools/index.js +33 -22
- package/lib/service/{chat → agent}/tools/infrastructure.js +6 -1
- package/lib/service/agent/tools/knowledge-graph.js +112 -0
- package/lib/service/agent/tools/scan-recipe.js +189 -0
- package/lib/service/agent/tools/system-interaction.js +476 -0
- package/lib/service/automation/DirectiveDetector.js +0 -1
- package/lib/service/automation/FileWatcher.js +0 -8
- package/lib/service/automation/handlers/CreateHandler.js +7 -3
- package/lib/service/automation/handlers/DraftHandler.js +7 -6
- package/lib/service/cursor/CursorDeliveryPipeline.js +167 -1
- package/lib/service/knowledge/CodeEntityGraph.js +327 -2
- package/lib/service/knowledge/KnowledgeService.js +5 -1
- package/lib/service/module/ModuleService.js +49 -73
- package/lib/service/skills/SignalCollector.js +26 -19
- package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
- package/lib/service/wiki/WikiGenerator.js +1 -1
- package/lib/shared/FieldSpec.js +1 -1
- package/lib/shared/PathGuard.js +1 -1
- package/lib/shared/StyleGuide.js +1 -1
- package/package.json +4 -1
- package/resources/native-ui/screenshot.swift +228 -0
- package/dashboard/dist/assets/index-BaGY7kJI.css +0 -1
- package/dashboard/dist/assets/index-DfHY_3ln.js +0 -128
- package/lib/core/discovery/SpmDiscoverer.js +0 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -749
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
- package/lib/http/routes/spm.js +0 -5
- package/lib/infrastructure/external/XcodeAutomation.js +0 -15
- package/lib/service/chat/ChatAgent.js +0 -1602
- package/lib/service/chat/Memory.js +0 -161
- package/lib/service/chat/ProducerAgent.js +0 -431
- package/lib/service/chat/ReasoningTrace.js +0 -523
- package/lib/service/chat/TaskPipeline.js +0 -357
- package/lib/service/chat/WorkingMemory.js +0 -357
- package/lib/service/chat/memory/PersistentMemory.js +0 -450
- package/lib/service/chat/tools/ai-analysis.js +0 -267
- package/lib/service/chat/tools/knowledge-graph.js +0 -234
- package/lib/service/chat/tools.js +0 -18
- package/lib/service/snippet/PlaceholderConverter.js +0 -5
- package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
- /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
- /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
- /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
- /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
- /package/lib/service/{chat → agent}/tools/query.js +0 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ToolRegistry — 统一工具注册表
|
|
3
3
|
*
|
|
4
|
-
* 管理
|
|
4
|
+
* 管理 Agent 可调用的所有工具定义。
|
|
5
5
|
* 每个工具包含: name, description, parameters(JSON Schema), handler
|
|
6
6
|
*
|
|
7
7
|
* 设计原则:
|
|
8
8
|
* - 所有 AI 能力均封装为 Tool,不再散落在各处
|
|
9
9
|
* - Tool handler 仅做参数整理 + 调用已有 Service,不含业务逻辑
|
|
10
|
-
* - 支持
|
|
10
|
+
* - 支持 Agent ReAct 循环调用(通过 LLM 推理决定工具调用)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import Logger from '
|
|
13
|
+
import Logger from '../../../infrastructure/logging/Logger.js';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* AI 模型常见的参数命名变体 → schema 标准名映射
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ai-analysis.js — AI 分析类工具 (2)
|
|
3
|
+
*
|
|
4
|
+
* 9. enrich_candidate 结构补齐
|
|
5
|
+
* 9b. refine_bootstrap_candidates 内容润色
|
|
6
|
+
*
|
|
7
|
+
* 注意: summarize_code / extract_recipes 已删除。
|
|
8
|
+
* 代码摘要和 Recipe 提取由 Agent LLM 直接推理完成,不再需要专用工具。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ────────────────────────────────────────────────────────────
|
|
12
|
+
// 9. enrich_candidate
|
|
13
|
+
// ────────────────────────────────────────────────────────────
|
|
14
|
+
export const enrichCandidate = {
|
|
15
|
+
name: 'enrich_candidate',
|
|
16
|
+
description:
|
|
17
|
+
'① 结构补齐 — 自动填充缺失的结构性语义字段(rationale/knowledgeType/complexity/scope/steps/constraints)。批量处理,只填空不覆盖。建议在 refine_bootstrap_candidates 之前执行。',
|
|
18
|
+
parameters: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
candidateIds: {
|
|
22
|
+
type: 'array',
|
|
23
|
+
items: { type: 'string' },
|
|
24
|
+
description: '候选 ID 列表 (最多 20 个)',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
required: ['candidateIds'],
|
|
28
|
+
},
|
|
29
|
+
handler: async (params, ctx) => {
|
|
30
|
+
if (!ctx.aiProvider) {
|
|
31
|
+
return { error: 'AI provider not available' };
|
|
32
|
+
}
|
|
33
|
+
// V3: 使用 MCP handler enrichCandidates 的逻辑
|
|
34
|
+
const { enrichCandidates: enrichFn } = await import('../../../external/mcp/handlers/candidate.js');
|
|
35
|
+
const result = await enrichFn(ctx, { candidateIds: params.candidateIds });
|
|
36
|
+
return result?.data || result;
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ────────────────────────────────────────────────────────────
|
|
41
|
+
// 9b. refine_bootstrap_candidates (Phase 6)
|
|
42
|
+
// ────────────────────────────────────────────────────────────
|
|
43
|
+
export const refineBootstrapCandidates = {
|
|
44
|
+
name: 'refine_bootstrap_candidates',
|
|
45
|
+
description:
|
|
46
|
+
'② 内容润色 — 逐条精炼 Bootstrap 候选的内容质量:改善 summary、补充架构 insight、推断 relations 关联、调整 confidence、丰富 tags。建议在 enrich_candidate 之后执行。',
|
|
47
|
+
parameters: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
candidateIds: {
|
|
51
|
+
type: 'array',
|
|
52
|
+
items: { type: 'string' },
|
|
53
|
+
description: '指定候选 ID 列表(可选,默认全部 bootstrap 候选)',
|
|
54
|
+
},
|
|
55
|
+
userPrompt: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
description: '用户自定义润色提示词,指导 AI 润色方向(如"侧重描述线程安全注意事项")',
|
|
58
|
+
},
|
|
59
|
+
dryRun: { type: 'boolean', description: '仅预览 AI 润色结果,不写入数据库' },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
handler: async (params, ctx) => {
|
|
63
|
+
if (!ctx.aiProvider) {
|
|
64
|
+
return { error: 'AI provider not available' };
|
|
65
|
+
}
|
|
66
|
+
// V3: 委托给 bootstrap handler 的 refine 逻辑
|
|
67
|
+
const { bootstrapRefine } = await import('../../../external/mcp/handlers/bootstrap-internal.js');
|
|
68
|
+
const result = await bootstrapRefine(ctx, {
|
|
69
|
+
candidateIds: params.candidateIds,
|
|
70
|
+
userPrompt: params.userPrompt,
|
|
71
|
+
dryRun: params.dryRun,
|
|
72
|
+
});
|
|
73
|
+
return result?.data || result;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ast-graph.js — AST 结构化分析 + Agent Memory 工具 (
|
|
2
|
+
* ast-graph.js — AST 结构化分析 + Agent Memory 工具 (11)
|
|
3
3
|
*
|
|
4
4
|
* 44. get_project_overview 项目 AST 概览
|
|
5
5
|
* 45. get_class_hierarchy 类继承层级
|
|
6
|
-
* 46. get_class_info
|
|
7
|
-
* 47. get_protocol_info
|
|
6
|
+
* 46. get_class_info 类/结构体详细信息 (跨语言)
|
|
7
|
+
* 47. get_protocol_info 协议/接口/trait 详细信息 (跨语言)
|
|
8
8
|
* 48. get_method_overrides 方法覆写查询
|
|
9
|
-
* 49. get_category_map Category 扩展映射
|
|
9
|
+
* 49. get_category_map Category/Extension 扩展映射
|
|
10
10
|
* 50. get_previous_analysis 前序维度分析结果
|
|
11
11
|
* 51. note_finding 记录关键发现
|
|
12
12
|
* 52. get_previous_evidence 检索前序维度证据
|
|
13
13
|
* 53. query_code_graph 查询代码实体图谱
|
|
14
|
+
* 54. query_call_graph 查询方法调用链 (Phase 5)
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
// ════════════════════════════════════════════════════════════
|
|
@@ -36,7 +37,8 @@ function _getProjectGraph(ctx) {
|
|
|
36
37
|
export const getProjectOverview = {
|
|
37
38
|
name: 'get_project_overview',
|
|
38
39
|
description:
|
|
39
|
-
'
|
|
40
|
+
'获取项目的整体结构概览:文件统计、语言分布、模块列表、入口点、类/协议/Category 数量。' +
|
|
41
|
+
'支持所有语言 (Swift/ObjC/Java/Kotlin/Python/TS/JS/Dart/Rust/Go 等)。' +
|
|
40
42
|
'适用场景:了解项目规模和架构布局,规划探索路径。',
|
|
41
43
|
parameters: {
|
|
42
44
|
type: 'object',
|
|
@@ -49,13 +51,30 @@ export const getProjectOverview = {
|
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
const o = graph.getOverview();
|
|
54
|
+
|
|
55
|
+
// §P2: 从文件扩展名统计语言分布
|
|
56
|
+
const langStats = {};
|
|
57
|
+
for (const filePath of graph.getAllFilePaths?.() || []) {
|
|
58
|
+
const ext = filePath.split('.').pop();
|
|
59
|
+
if (ext) langStats[ext] = (langStats[ext] || 0) + 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
52
62
|
const lines = [
|
|
53
63
|
`📊 项目 AST 概览 (构建耗时 ${o.buildTimeMs}ms)`,
|
|
54
64
|
``,
|
|
55
|
-
`文件: ${o.totalFiles} | 类: ${o.totalClasses} |
|
|
56
|
-
``,
|
|
57
|
-
`── 模块 ──`,
|
|
65
|
+
`文件: ${o.totalFiles} | 类: ${o.totalClasses} | 协议/接口: ${o.totalProtocols} | Category/Extension: ${o.totalCategories} | 方法: ${o.totalMethods}`,
|
|
58
66
|
];
|
|
67
|
+
|
|
68
|
+
// 语言分布
|
|
69
|
+
const langEntries = Object.entries(langStats).sort((a, b) => b[1] - a[1]);
|
|
70
|
+
if (langEntries.length > 0) {
|
|
71
|
+
lines.push(``, `── 语言分布 ──`);
|
|
72
|
+
for (const [ext, count] of langEntries) {
|
|
73
|
+
lines.push(` .${ext}: ${count} 个文件`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
lines.push(``, `── 模块 ──`);
|
|
59
78
|
for (const mod of o.topLevelModules) {
|
|
60
79
|
const count = o.classesPerModule[mod] || 0;
|
|
61
80
|
lines.push(` ${mod}/ — ${count} 个类`);
|
|
@@ -76,7 +95,8 @@ export const getProjectOverview = {
|
|
|
76
95
|
export const getClassHierarchy = {
|
|
77
96
|
name: 'get_class_hierarchy',
|
|
78
97
|
description:
|
|
79
|
-
'
|
|
98
|
+
'查看指定类/结构体的继承链(向上到根类)和直接子类列表。' +
|
|
99
|
+
'支持所有语言的类继承体系 (Swift class/struct, Java/Kotlin class, Python class, TS class, Dart class, Rust struct, Go struct 等)。' +
|
|
80
100
|
'传入 className 查看指定类,不传则返回项目中所有根类及其子树。',
|
|
81
101
|
parameters: {
|
|
82
102
|
type: 'object',
|
|
@@ -138,7 +158,9 @@ export const getClassHierarchy = {
|
|
|
138
158
|
// ────────────────────────────────────────────────────────────
|
|
139
159
|
export const getClassInfo = {
|
|
140
160
|
name: 'get_class_info',
|
|
141
|
-
description:
|
|
161
|
+
description:
|
|
162
|
+
'获取指定类/结构体的详细信息: 属性、方法签名、导入、继承关系、扩展。' +
|
|
163
|
+
'跨语言通用:ObjC @interface, Swift class/struct, Java/Kotlin class, Python class, TS/JS class, Dart class, Rust struct impl, Go struct 等。',
|
|
142
164
|
parameters: {
|
|
143
165
|
type: 'object',
|
|
144
166
|
properties: {
|
|
@@ -169,7 +191,7 @@ export const getClassInfo = {
|
|
|
169
191
|
];
|
|
170
192
|
|
|
171
193
|
if (info.protocols.length > 0) {
|
|
172
|
-
lines.push(
|
|
194
|
+
lines.push(`实现: ${info.protocols.join(', ')}`);
|
|
173
195
|
}
|
|
174
196
|
|
|
175
197
|
if (info.properties.length > 0) {
|
|
@@ -195,7 +217,7 @@ export const getClassInfo = {
|
|
|
195
217
|
}
|
|
196
218
|
|
|
197
219
|
if (cats.length > 0) {
|
|
198
|
-
lines.push(``, `──
|
|
220
|
+
lines.push(``, `── 扩展 (${cats.length}) ──`);
|
|
199
221
|
for (const cat of cats) {
|
|
200
222
|
const methodNames = cat.methods.map((m) => m.selector).join(', ');
|
|
201
223
|
lines.push(` ${info.name}(${cat.categoryName}) — ${cat.filePath} — [${methodNames}]`);
|
|
@@ -218,7 +240,9 @@ export const getClassInfo = {
|
|
|
218
240
|
// ────────────────────────────────────────────────────────────
|
|
219
241
|
export const getProtocolInfo = {
|
|
220
242
|
name: 'get_protocol_info',
|
|
221
|
-
description:
|
|
243
|
+
description:
|
|
244
|
+
'获取指定协议/接口/trait 的定义(必选/可选方法)及所有实现者列表。' +
|
|
245
|
+
'跨语言通用:ObjC @protocol, Swift protocol, Java/Kotlin interface, Python ABC, TS interface, Dart abstract class, Rust trait, Go interface 等。',
|
|
222
246
|
parameters: {
|
|
223
247
|
type: 'object',
|
|
224
248
|
properties: {
|
|
@@ -235,36 +259,36 @@ export const getProtocolInfo = {
|
|
|
235
259
|
const protocolName = params.protocolName || params.protocol_name;
|
|
236
260
|
const info = graph.getProtocolInfo(protocolName);
|
|
237
261
|
if (!info) {
|
|
238
|
-
return
|
|
262
|
+
return `未找到协议/接口 "${protocolName}"。可以使用 get_project_overview 查看项目中的所有协议/接口。`;
|
|
239
263
|
}
|
|
240
264
|
|
|
241
|
-
const lines = [`📋
|
|
265
|
+
const lines = [`📋 ${info.name}`, `文件: ${info.filePath}:${info.line}`];
|
|
242
266
|
|
|
243
267
|
if (info.inherits.length > 0) {
|
|
244
|
-
lines.push(`继承:
|
|
268
|
+
lines.push(`继承: ${info.inherits.join(', ')}`);
|
|
245
269
|
}
|
|
246
270
|
|
|
247
271
|
if (info.requiredMethods.length > 0) {
|
|
248
|
-
lines.push(``, `──
|
|
272
|
+
lines.push(``, `── 必须实现 (${info.requiredMethods.length}) ──`);
|
|
249
273
|
for (const m of info.requiredMethods) {
|
|
250
274
|
lines.push(` ${m.isClassMethod ? '+' : '-'} ${m.selector} → ${m.returnType}`);
|
|
251
275
|
}
|
|
252
276
|
}
|
|
253
277
|
|
|
254
278
|
if (info.optionalMethods.length > 0) {
|
|
255
|
-
lines.push(``, `──
|
|
279
|
+
lines.push(``, `── 可选实现 (${info.optionalMethods.length}) ──`);
|
|
256
280
|
for (const m of info.optionalMethods) {
|
|
257
281
|
lines.push(` ${m.isClassMethod ? '+' : '-'} ${m.selector} → ${m.returnType}`);
|
|
258
282
|
}
|
|
259
283
|
}
|
|
260
284
|
|
|
261
285
|
if (info.conformers.length > 0) {
|
|
262
|
-
lines.push(``, `──
|
|
286
|
+
lines.push(``, `── 实现者 (${info.conformers.length}) ──`);
|
|
263
287
|
for (const c of info.conformers) {
|
|
264
288
|
lines.push(` ${c}`);
|
|
265
289
|
}
|
|
266
290
|
} else {
|
|
267
|
-
lines.push(``, `⚠️
|
|
291
|
+
lines.push(``, `⚠️ 暂未发现实现此协议/接口的类`);
|
|
268
292
|
}
|
|
269
293
|
|
|
270
294
|
return lines.join('\n');
|
|
@@ -276,7 +300,9 @@ export const getProtocolInfo = {
|
|
|
276
300
|
// ────────────────────────────────────────────────────────────
|
|
277
301
|
export const getMethodOverrides = {
|
|
278
302
|
name: 'get_method_overrides',
|
|
279
|
-
description:
|
|
303
|
+
description:
|
|
304
|
+
'查找覆写了指定方法的所有子类。适用于理解方法在继承树中的多态行为。' +
|
|
305
|
+
'跨语言通用:支持 ObjC/Swift/Java/Kotlin/Python/TS/Dart 等语言的方法覆写。',
|
|
280
306
|
parameters: {
|
|
281
307
|
type: 'object',
|
|
282
308
|
properties: {
|
|
@@ -314,7 +340,9 @@ export const getMethodOverrides = {
|
|
|
314
340
|
export const getCategoryMap = {
|
|
315
341
|
name: 'get_category_map',
|
|
316
342
|
description:
|
|
317
|
-
'
|
|
343
|
+
'获取指定类的 Category/Extension 扩展映射。' +
|
|
344
|
+
'ObjC Category、Swift Extension 等语言的类扩展机制。了解它有助于发现功能划分。' +
|
|
345
|
+
'不传 className 则返回整个项目中有扩展的类列表。',
|
|
318
346
|
parameters: {
|
|
319
347
|
type: 'object',
|
|
320
348
|
properties: {
|
|
@@ -455,7 +483,9 @@ export const noteFinding = {
|
|
|
455
483
|
const coordinator = ctx._memoryCoordinator;
|
|
456
484
|
if (coordinator) {
|
|
457
485
|
const finding = params.finding || '';
|
|
458
|
-
|
|
486
|
+
// P0 Fix: AI 可能传入 array/object,强制转为 string
|
|
487
|
+
const rawEvidence = params.evidence;
|
|
488
|
+
const evidence = typeof rawEvidence === 'string' ? rawEvidence : Array.isArray(rawEvidence) ? rawEvidence.join(', ') : rawEvidence ? String(rawEvidence) : '';
|
|
459
489
|
const importance = params.importance || 5;
|
|
460
490
|
const round = ctx._currentRound || 0;
|
|
461
491
|
const scopeId = ctx._dimensionScopeId || undefined;
|
|
@@ -547,8 +577,8 @@ export const queryCodeGraph = {
|
|
|
547
577
|
},
|
|
548
578
|
entity_type: {
|
|
549
579
|
type: 'string',
|
|
550
|
-
enum: ['class', 'protocol', 'category', 'module', 'pattern'],
|
|
551
|
-
description: '实体类型过滤 (
|
|
580
|
+
enum: ['class', 'protocol', 'category', 'module', 'pattern', 'method'],
|
|
581
|
+
description: '实体类型过滤 (可选,含 Phase 5 method 类型)',
|
|
552
582
|
},
|
|
553
583
|
max_depth: {
|
|
554
584
|
type: 'number',
|
|
@@ -559,14 +589,17 @@ export const queryCodeGraph = {
|
|
|
559
589
|
},
|
|
560
590
|
handler: async (params, ctx) => {
|
|
561
591
|
try {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
if (!
|
|
565
|
-
|
|
592
|
+
// 优先从 container 获取单例 CEG,避免每次创建新实例
|
|
593
|
+
let ceg = ctx?.container?.get?.('codeEntityGraph');
|
|
594
|
+
if (!ceg) {
|
|
595
|
+
const { CodeEntityGraph } = await import('../../knowledge/CodeEntityGraph.js');
|
|
596
|
+
const db = ctx?.container?.get?.('database');
|
|
597
|
+
if (!db) {
|
|
598
|
+
return '代码实体图谱不可用: 数据库未初始化';
|
|
599
|
+
}
|
|
600
|
+
const projectRoot = ctx?.projectRoot || process.env.ASD_PROJECT_DIR || '';
|
|
601
|
+
ceg = new CodeEntityGraph(db, { projectRoot });
|
|
566
602
|
}
|
|
567
|
-
|
|
568
|
-
const projectRoot = ctx?.projectRoot || process.env.ASD_PROJECT_DIR || '';
|
|
569
|
-
const ceg = new CodeEntityGraph(db, { projectRoot });
|
|
570
603
|
const maxDepth = params.max_depth || 3;
|
|
571
604
|
|
|
572
605
|
switch (params.action) {
|
|
@@ -680,3 +713,167 @@ export const queryCodeGraph = {
|
|
|
680
713
|
}
|
|
681
714
|
},
|
|
682
715
|
};
|
|
716
|
+
|
|
717
|
+
// ────────────────────────────────────────────────────────────
|
|
718
|
+
// 54. query_call_graph — 查询方法调用链 (Phase 5)
|
|
719
|
+
// ────────────────────────────────────────────────────────────
|
|
720
|
+
export const queryCallGraph = {
|
|
721
|
+
name: 'query_call_graph',
|
|
722
|
+
description:
|
|
723
|
+
'查询方法的调用链上下文 (Call Graph)。支持 10+ 种编程语言 (Swift/ObjC/Java/Kotlin/Python/TS/JS/Dart/Rust/Go)。\n' +
|
|
724
|
+
'• callers: 谁调用了这个方法?(向上追踪调用者链)\n' +
|
|
725
|
+
'• callees: 这个方法调用了谁?(向下追踪依赖链)\n' +
|
|
726
|
+
'• both: 同时获取调用者和被调用者\n' +
|
|
727
|
+
'• impact: 修改此方法的影响半径分析 (受影响文件数)\n' +
|
|
728
|
+
'• search: 按名称搜索方法实体\n' +
|
|
729
|
+
'⚠️ 需要先完成 bootstrap 才有数据。方法名格式: "ClassName.methodName" 或简写 "methodName"。',
|
|
730
|
+
parameters: {
|
|
731
|
+
type: 'object',
|
|
732
|
+
properties: {
|
|
733
|
+
methodName: {
|
|
734
|
+
type: 'string',
|
|
735
|
+
description:
|
|
736
|
+
'方法名 (e.g. "UserService.getUser", "viewDidLoad", "handleClick")。' +
|
|
737
|
+
'search 模式下为搜索关键词。',
|
|
738
|
+
},
|
|
739
|
+
direction: {
|
|
740
|
+
type: 'string',
|
|
741
|
+
enum: ['callers', 'callees', 'both', 'impact', 'search'],
|
|
742
|
+
description:
|
|
743
|
+
'callers=调用者 | callees=被调用者 | both=双向 | impact=影响分析 | search=搜索方法实体',
|
|
744
|
+
},
|
|
745
|
+
maxDepth: {
|
|
746
|
+
type: 'number',
|
|
747
|
+
description: '最大遍历深度 (1-5, 默认 2)',
|
|
748
|
+
},
|
|
749
|
+
},
|
|
750
|
+
required: ['methodName'],
|
|
751
|
+
},
|
|
752
|
+
handler: async (params, ctx) => {
|
|
753
|
+
try {
|
|
754
|
+
// 前置: 必填参数校验 (fail-fast)
|
|
755
|
+
const methodName = params.methodName || params.method_name;
|
|
756
|
+
if (!methodName) {
|
|
757
|
+
return '请提供 methodName 参数。';
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// 优先从 container 获取单例 CEG,避免每次创建新实例
|
|
761
|
+
let ceg = ctx?.container?.get?.('codeEntityGraph');
|
|
762
|
+
if (!ceg) {
|
|
763
|
+
// fallback: 手动构建
|
|
764
|
+
const { CodeEntityGraph } = await import('../../knowledge/CodeEntityGraph.js');
|
|
765
|
+
const db = ctx?.container?.get?.('database');
|
|
766
|
+
if (!db) {
|
|
767
|
+
return '调用图查询不可用: 数据库未初始化。';
|
|
768
|
+
}
|
|
769
|
+
const projectRoot = ctx?.projectRoot || process.env.ASD_PROJECT_DIR || '';
|
|
770
|
+
ceg = new CodeEntityGraph(db, { projectRoot });
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const direction = params.direction || 'both';
|
|
774
|
+
const maxDepth = Math.min(Math.max(Number(params.maxDepth || params.max_depth) || 2, 1), 5);
|
|
775
|
+
|
|
776
|
+
// search 模式: 按名称搜索方法实体
|
|
777
|
+
if (direction === 'search') {
|
|
778
|
+
const results = ceg.searchEntities(methodName, { type: 'method', limit: 15 });
|
|
779
|
+
if (results.length === 0) {
|
|
780
|
+
// 降级: 不限类型搜索
|
|
781
|
+
const allResults = ceg.searchEntities(methodName, { limit: 15 });
|
|
782
|
+
if (allResults.length === 0) {
|
|
783
|
+
return `未找到匹配 "${methodName}" 的实体。请确认 bootstrap 已完成。`;
|
|
784
|
+
}
|
|
785
|
+
const lines = [`🔍 搜索 "${methodName}" (${allResults.length} 条, 无 method 类型匹配):`];
|
|
786
|
+
for (const e of allResults) {
|
|
787
|
+
lines.push(` • [${e.entityType}] ${e.name}${e.filePath ? ` (${e.filePath})` : ''}`);
|
|
788
|
+
}
|
|
789
|
+
lines.push(``, `💡 提示: 调用图查询需使用 method 实体的 entityId。`);
|
|
790
|
+
return lines.join('\n');
|
|
791
|
+
}
|
|
792
|
+
const lines = [`🔍 方法搜索 "${methodName}" (${results.length} 条):`];
|
|
793
|
+
for (const e of results) {
|
|
794
|
+
lines.push(` • ${e.name}${e.filePath ? ` (${e.filePath})` : ''}`);
|
|
795
|
+
}
|
|
796
|
+
lines.push(``, `💡 复制上面的完整方法名,用 callers/callees/both/impact 查询其调用链。`);
|
|
797
|
+
return lines.join('\n');
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// impact 模式
|
|
801
|
+
if (direction === 'impact') {
|
|
802
|
+
const impact = ceg.getCallImpactRadius(methodName);
|
|
803
|
+
const lines = [
|
|
804
|
+
`⚡ 修改 "${methodName}" 的影响半径:`,
|
|
805
|
+
` 直接调用者: ${impact.directCallers}`,
|
|
806
|
+
` 传递性调用者 (depth≤3): ${impact.transitiveCallers}`,
|
|
807
|
+
];
|
|
808
|
+
if (impact.affectedFiles.length > 0) {
|
|
809
|
+
lines.push(` 受影响文件 (${impact.affectedFiles.length}):`);
|
|
810
|
+
for (const f of impact.affectedFiles.slice(0, 15)) {
|
|
811
|
+
lines.push(` 📄 ${f}`);
|
|
812
|
+
}
|
|
813
|
+
if (impact.affectedFiles.length > 15) {
|
|
814
|
+
lines.push(` ... 还有 ${impact.affectedFiles.length - 15} 个文件`);
|
|
815
|
+
}
|
|
816
|
+
} else {
|
|
817
|
+
lines.push(` 未发现受影响文件 (可能是叶子方法或 bootstrap 无调用图数据)。`);
|
|
818
|
+
}
|
|
819
|
+
return lines.join('\n');
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// callers / callees / both 模式
|
|
823
|
+
const result = {};
|
|
824
|
+
if (direction === 'callers' || direction === 'both') {
|
|
825
|
+
result.callers = ceg.getCallers(methodName, maxDepth);
|
|
826
|
+
}
|
|
827
|
+
if (direction === 'callees' || direction === 'both') {
|
|
828
|
+
result.callees = ceg.getCallees(methodName, maxDepth);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const totalCallers = result.callers?.length || 0;
|
|
832
|
+
const totalCallees = result.callees?.length || 0;
|
|
833
|
+
|
|
834
|
+
if (totalCallers === 0 && totalCallees === 0) {
|
|
835
|
+
return (
|
|
836
|
+
`"${methodName}" 在调用图中没有找到调用关系。\n\n` +
|
|
837
|
+
`可能原因:\n` +
|
|
838
|
+
` 1. 方法名拼写不匹配 — 尝试 search 模式: query_call_graph({methodName:"关键词", direction:"search"})\n` +
|
|
839
|
+
` 2. 该方法是入口点/叶子节点,无上下游\n` +
|
|
840
|
+
` 3. bootstrap 尚未执行或不包含此文件`
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
const lines = [`📞 "${methodName}" 的调用链 (depth≤${maxDepth}):`];
|
|
845
|
+
|
|
846
|
+
if (result.callers?.length > 0) {
|
|
847
|
+
lines.push(``, `── 调用者 (谁调用了它, ${result.callers.length} 条) ──`);
|
|
848
|
+
for (const c of result.callers.slice(0, 20)) {
|
|
849
|
+
const indent = ' '.repeat(c.depth);
|
|
850
|
+
const typeTag = c.callType !== 'unknown' ? ` [${c.callType}]` : '';
|
|
851
|
+
lines.push(`${indent}⬆ ${c.caller}${typeTag}`);
|
|
852
|
+
}
|
|
853
|
+
if (result.callers.length > 20) {
|
|
854
|
+
lines.push(` ... 还有 ${result.callers.length - 20} 个调用者`);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (result.callees?.length > 0) {
|
|
859
|
+
lines.push(``, `── 被调用者 (它调用了谁, ${result.callees.length} 条) ──`);
|
|
860
|
+
for (const c of result.callees.slice(0, 20)) {
|
|
861
|
+
const indent = ' '.repeat(c.depth);
|
|
862
|
+
const typeTag = c.callType !== 'unknown' ? ` [${c.callType}]` : '';
|
|
863
|
+
lines.push(`${indent}⬇ ${c.callee}${typeTag}`);
|
|
864
|
+
}
|
|
865
|
+
if (result.callees.length > 20) {
|
|
866
|
+
lines.push(` ... 还有 ${result.callees.length - 20} 个被调用者`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
return lines.join('\n');
|
|
871
|
+
} catch (err) {
|
|
872
|
+
// 表不存在 → 未 bootstrap
|
|
873
|
+
if (err.message?.includes('no such table')) {
|
|
874
|
+
return '调用图数据不可用 — knowledge_edges 表不存在,请先运行 bootstrap。';
|
|
875
|
+
}
|
|
876
|
+
return `调用图查询失败: ${err.message}`;
|
|
877
|
+
}
|
|
878
|
+
},
|
|
879
|
+
};
|
|
@@ -160,7 +160,7 @@ export const submitWithCheck = {
|
|
|
160
160
|
content: {
|
|
161
161
|
type: 'object',
|
|
162
162
|
description:
|
|
163
|
-
'{ markdown: "项目特写 Markdown", pattern: "核心代码 3-8
|
|
163
|
+
'{ markdown: "项目特写 Markdown (≥200字符,含代码块)", rationale: "设计原理说明 (必填)", pattern: "核心代码 3-8 行,语法完整" }',
|
|
164
164
|
},
|
|
165
165
|
title: { type: 'string', description: '候选标题' },
|
|
166
166
|
description: { type: 'string', description: '中文简述 ≤80 字' },
|
|
@@ -234,6 +234,7 @@ export const submitWithCheck = {
|
|
|
234
234
|
ctx.logger?.info(`[submit_with_check] ✗ validator rejected: ${validResult.errors.join('; ')}`);
|
|
235
235
|
return {
|
|
236
236
|
submitted: false,
|
|
237
|
+
status: 'rejected',
|
|
237
238
|
reason: 'validation_failed',
|
|
238
239
|
errors: validResult.errors,
|
|
239
240
|
warnings: validResult.warnings,
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* guard.js — Guard 安全类工具 (
|
|
2
|
+
* guard.js — Guard 安全类工具 (4)
|
|
3
3
|
*
|
|
4
4
|
* 7b. list_guard_rules 列出 Guard 规则
|
|
5
5
|
* 8b. get_recommendations 获取推荐 Recipe
|
|
6
|
-
* 12. ai_translate AI 翻译
|
|
7
6
|
* 13. guard_check_code Guard 检查代码
|
|
8
7
|
* 14. query_violations 查询违规历史
|
|
9
|
-
* 15. generate_guard_rule AI 生成 Guard 规则
|
|
10
8
|
*/
|
|
11
9
|
|
|
12
10
|
// ────────────────────────────────────────────────────────────
|
|
@@ -75,46 +73,6 @@ export const getRecommendations = {
|
|
|
75
73
|
},
|
|
76
74
|
};
|
|
77
75
|
|
|
78
|
-
// ────────────────────────────────────────────────────────────
|
|
79
|
-
// 12. ai_translate
|
|
80
|
-
// ────────────────────────────────────────────────────────────
|
|
81
|
-
export const aiTranslate = {
|
|
82
|
-
name: 'ai_translate',
|
|
83
|
-
description: 'AI 翻译 — 将中文 summary/usageGuide 翻译为英文。',
|
|
84
|
-
parameters: {
|
|
85
|
-
type: 'object',
|
|
86
|
-
properties: {
|
|
87
|
-
summary: { type: 'string', description: '中文摘要' },
|
|
88
|
-
usageGuide: { type: 'string', description: '中文使用指南' },
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
handler: async (params, ctx) => {
|
|
92
|
-
if (!ctx.aiProvider) {
|
|
93
|
-
return { error: 'AI provider not available' };
|
|
94
|
-
}
|
|
95
|
-
const { summary, usageGuide } = params;
|
|
96
|
-
if (!summary && !usageGuide) {
|
|
97
|
-
return { summaryEn: '', usageGuideEn: '' };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const systemPrompt =
|
|
101
|
-
'You are a technical translator. Translate from Chinese to English. Keep technical terms unchanged. Return ONLY valid JSON: { "summaryEn": "...", "usageGuideEn": "..." }.';
|
|
102
|
-
const parts = [];
|
|
103
|
-
if (summary) {
|
|
104
|
-
parts.push(`summary: ${summary}`);
|
|
105
|
-
}
|
|
106
|
-
if (usageGuide) {
|
|
107
|
-
parts.push(`usageGuide: ${usageGuide}`);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const parsed = await ctx.aiProvider.chatWithStructuredOutput(parts.join('\n'), {
|
|
111
|
-
systemPrompt,
|
|
112
|
-
temperature: 0.2,
|
|
113
|
-
});
|
|
114
|
-
return parsed || { summaryEn: summary || '', usageGuideEn: usageGuide || '' };
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
|
|
118
76
|
// ────────────────────────────────────────────────────────────
|
|
119
77
|
// 13. guard_check_code
|
|
120
78
|
// ────────────────────────────────────────────────────────────
|
|
@@ -184,82 +142,4 @@ export const queryViolations = {
|
|
|
184
142
|
},
|
|
185
143
|
};
|
|
186
144
|
|
|
187
|
-
// ────────────────────────────────────────────────────────────
|
|
188
|
-
// 15. generate_guard_rule
|
|
189
|
-
// ────────────────────────────────────────────────────────────
|
|
190
|
-
export const generateGuardRule = {
|
|
191
|
-
name: 'generate_guard_rule',
|
|
192
|
-
description: 'AI 生成 Guard 规则 — 描述你想阻止的代码模式,AI 自动生成正则表达式和规则定义。',
|
|
193
|
-
parameters: {
|
|
194
|
-
type: 'object',
|
|
195
|
-
properties: {
|
|
196
|
-
description: {
|
|
197
|
-
type: 'string',
|
|
198
|
-
description: '规则描述(例如 "禁止在主线程使用同步网络请求")',
|
|
199
|
-
},
|
|
200
|
-
language: { type: 'string', description: '目标语言 (swift/objc 等)' },
|
|
201
|
-
severity: { type: 'string', description: '严重程度 (error/warning/info),默认 warning' },
|
|
202
|
-
autoCreate: { type: 'boolean', description: '是否自动创建到数据库,默认 false' },
|
|
203
|
-
},
|
|
204
|
-
required: ['description'],
|
|
205
|
-
},
|
|
206
|
-
handler: async (params, ctx) => {
|
|
207
|
-
if (!ctx.aiProvider) {
|
|
208
|
-
return { error: 'AI provider not available' };
|
|
209
|
-
}
|
|
210
|
-
const { description, language = 'swift', severity = 'warning', autoCreate = false } = params;
|
|
211
|
-
|
|
212
|
-
const prompt = `Generate a Guard rule for this requirement:
|
|
213
|
-
Description: ${description}
|
|
214
|
-
Language: ${language}
|
|
215
|
-
Severity: ${severity}
|
|
216
|
-
|
|
217
|
-
Return ONLY valid JSON:
|
|
218
|
-
{
|
|
219
|
-
"name": "rule-name-kebab-case",
|
|
220
|
-
"description": "One-line description in English",
|
|
221
|
-
"description_cn": "一行中文描述",
|
|
222
|
-
"pattern": "regex pattern for matching the problematic code",
|
|
223
|
-
"languages": ["${language}"],
|
|
224
|
-
"severity": "${severity}",
|
|
225
|
-
"testCases": {
|
|
226
|
-
"shouldMatch": ["code example that should trigger"],
|
|
227
|
-
"shouldNotMatch": ["code example that should NOT trigger"]
|
|
228
|
-
}
|
|
229
|
-
}`;
|
|
230
|
-
|
|
231
|
-
const rule = await ctx.aiProvider.chatWithStructuredOutput(prompt, { temperature: 0.2 });
|
|
232
|
-
if (!rule) {
|
|
233
|
-
return { error: 'Failed to parse AI response' };
|
|
234
|
-
}
|
|
235
145
|
|
|
236
|
-
// 验证正则表达式
|
|
237
|
-
try {
|
|
238
|
-
new RegExp(rule.pattern);
|
|
239
|
-
} catch (e) {
|
|
240
|
-
return { error: `Invalid regex pattern: ${e.message}`, rule };
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// 自动创建
|
|
244
|
-
if (autoCreate && rule.name && rule.pattern) {
|
|
245
|
-
try {
|
|
246
|
-
const guardService = ctx.container.get('guardService');
|
|
247
|
-
const created = await guardService.createRule(
|
|
248
|
-
{
|
|
249
|
-
name: rule.name,
|
|
250
|
-
description: rule.description || description,
|
|
251
|
-
pattern: rule.pattern,
|
|
252
|
-
languages: rule.languages || [language],
|
|
253
|
-
severity: rule.severity || severity,
|
|
254
|
-
},
|
|
255
|
-
{ userId: 'agent' }
|
|
256
|
-
);
|
|
257
|
-
return { rule, created: true, recipeId: created.id };
|
|
258
|
-
} catch (err) {
|
|
259
|
-
return { rule, created: false, error: err.message };
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return { rule, created: false };
|
|
264
|
-
},
|
|
265
|
-
};
|