pdd-skills 3.0.0
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 +1478 -0
- package/bin/pdd.js +354 -0
- package/config/bpmn-rules.yaml +166 -0
- package/config/checkstyle.xml +105 -0
- package/config/eslint.config.js +48 -0
- package/config/pmd.xml +91 -0
- package/config/prd-rules.yaml +113 -0
- package/config/ruff.toml +45 -0
- package/config/sqlfluff.cfg +82 -0
- package/hooks/hook-executor.js +332 -0
- package/index.js +43 -0
- package/lib/api-routes.js +750 -0
- package/lib/api-server.js +408 -0
- package/lib/cache/cache-config.js +209 -0
- package/lib/cache/system-cache.js +852 -0
- package/lib/config-manager.js +373 -0
- package/lib/generate.js +528 -0
- package/lib/grpc/grpc-routes.js +1134 -0
- package/lib/grpc/grpc-server.js +912 -0
- package/lib/grpc/proto-definitions.js +1033 -0
- package/lib/init.js +172 -0
- package/lib/iteration/auto-fixer.js +1025 -0
- package/lib/iteration/auto-reviewer.js +923 -0
- package/lib/iteration/controller.js +577 -0
- package/lib/list.js +130 -0
- package/lib/mcp-server.js +548 -0
- package/lib/openclaw/api-integration.js +535 -0
- package/lib/openclaw/cli-integration.js +567 -0
- package/lib/openclaw/data-sync.js +845 -0
- package/lib/openclaw/openclaw-adapter.js +783 -0
- package/lib/plugin/example-plugins/code-stats/index.js +332 -0
- package/lib/plugin/example-plugins/code-stats/plugin.json +1 -0
- package/lib/plugin/example-plugins/custom-linter/index.js +472 -0
- package/lib/plugin/example-plugins/custom-linter/plugin.json +1 -0
- package/lib/plugin/example-plugins/hello-world/index.js +86 -0
- package/lib/plugin/example-plugins/hello-world/plugin.json +1 -0
- package/lib/plugin/plugin-manager.js +655 -0
- package/lib/plugin/plugin-sdk.js +565 -0
- package/lib/plugin/sandbox.js +627 -0
- package/lib/quality/rules/maintainability.js +418 -0
- package/lib/quality/rules/performance.js +498 -0
- package/lib/quality/rules/readability.js +441 -0
- package/lib/quality/rules/robustness.js +504 -0
- package/lib/quality/rules/security.js +444 -0
- package/lib/quality/scorer.js +576 -0
- package/lib/report.js +669 -0
- package/lib/sdk-base.js +301 -0
- package/lib/sdk-js.js +446 -0
- package/lib/sdk-python/README.md +546 -0
- package/lib/sdk-python/examples/basic_usage.py +450 -0
- package/lib/sdk-python/pdd_sdk/__init__.py +180 -0
- package/lib/sdk-python/pdd_sdk/client.py +1170 -0
- package/lib/sdk-python/pdd_sdk/events.py +423 -0
- package/lib/sdk-python/pdd_sdk/exceptions.py +158 -0
- package/lib/sdk-python/pdd_sdk/models.py +518 -0
- package/lib/sdk-python/pdd_sdk/utils.py +759 -0
- package/lib/token/budget-alert.js +367 -0
- package/lib/token/budget-manager.js +485 -0
- package/lib/update.js +54 -0
- package/lib/utils/logger.js +88 -0
- package/lib/verify.js +741 -0
- package/lib/version.js +52 -0
- package/lib/vm/README.md +102 -0
- package/lib/vm/dashboard/api-routes.js +669 -0
- package/lib/vm/dashboard/server.js +391 -0
- package/lib/vm/dashboard/sse.js +358 -0
- package/lib/vm/dashboard/static/css/dashboard.css +1378 -0
- package/lib/vm/dashboard/static/index.html +118 -0
- package/lib/vm/dashboard/static/js/app.js +949 -0
- package/lib/vm/dashboard/static/js/charts.js +913 -0
- package/lib/vm/dashboard/static/js/kanban-view.js +1053 -0
- package/lib/vm/dashboard/static/js/pipeline-view.js +463 -0
- package/lib/vm/dashboard/static/js/quality-view.js +598 -0
- package/lib/vm/dashboard/static/js/system-view.js +1021 -0
- package/lib/vm/data-provider.js +1191 -0
- package/lib/vm/event-bus.js +402 -0
- package/lib/vm/hooks/extract-hook.js +307 -0
- package/lib/vm/hooks/generate-hook.js +374 -0
- package/lib/vm/hooks/hook-interface.js +458 -0
- package/lib/vm/hooks/report-hook.js +331 -0
- package/lib/vm/hooks/verify-hook.js +454 -0
- package/lib/vm/models.js +1003 -0
- package/lib/vm/reconciler.js +855 -0
- package/lib/vm/scanner.js +988 -0
- package/lib/vm/state-schema.js +955 -0
- package/lib/vm/state-store.js +733 -0
- package/lib/vm/tui/components/card.js +339 -0
- package/lib/vm/tui/components/progress-bar.js +368 -0
- package/lib/vm/tui/components/sparkline.js +327 -0
- package/lib/vm/tui/components/status-light.js +294 -0
- package/lib/vm/tui/components/table.js +370 -0
- package/lib/vm/tui/input.js +335 -0
- package/lib/vm/tui/renderer.js +548 -0
- package/lib/vm/tui/screens/kanban-screen.js +397 -0
- package/lib/vm/tui/screens/overview-screen.js +357 -0
- package/lib/vm/tui/screens/quality-screen.js +336 -0
- package/lib/vm/tui/screens/system-screen.js +379 -0
- package/lib/vm/tui/tui.js +805 -0
- package/package.json +1 -0
- package/scripts/cso-analyzer.js +198 -0
- package/scripts/eval-runner.js +359 -0
- package/scripts/i18n-checker.js +109 -0
- package/scripts/linter/activiti-linter.js +272 -0
- package/scripts/linter/prd-linter.js +162 -0
- package/scripts/linter/report-generator.js +207 -0
- package/scripts/linter/run-linters.js +285 -0
- package/scripts/linter/sql-linter.js +166 -0
- package/scripts/token-analyzer.js +162 -0
- package/scripts/vm-test.js +180 -0
- package/skills/core/official-doc-writer/LICENSE +21 -0
- package/skills/core/official-doc-writer/README.md +232 -0
- package/skills/core/official-doc-writer/SKILL.md +475 -0
- package/skills/core/official-doc-writer/_meta.json +1 -0
- package/skills/core/official-doc-writer/document_generator.py +580 -0
- package/skills/core/official-doc-writer/evals/default-evals.json +1 -0
- package/skills/core/official-doc-writer/examples.md +150 -0
- package/skills/core/official-doc-writer/fonts/FONTS_LIST.md +45 -0
- package/skills/core/official-doc-writer/fonts/README.md +141 -0
- package/skills/core/official-doc-writer/fonts/SIMFANG.TTF +0 -0
- package/skills/core/official-doc-writer/fonts/SIMHEI.TTF +0 -0
- package/skills/core/official-doc-writer/fonts/SIMKAI.TTF +0 -0
- package/skills/core/official-doc-writer/fonts/SIMSUN.TTC +0 -0
- package/skills/core/official-doc-writer/fonts//346/226/271/346/255/243/345/260/217/346/240/207/345/256/213GBK.TTF +0 -0
- package/skills/core/official-doc-writer/references/GBT_9704-2012_/345/205/232/346/224/277/346/234/272/345/205/263/345/205/254/346/226/207/346/240/274/345/274/217.md +422 -0
- package/skills/core/official-doc-writer/scripts/__pycache__/generate_official_doc.cpython-313.pyc +0 -0
- package/skills/core/official-doc-writer/scripts/dialog_manager.py +564 -0
- package/skills/core/official-doc-writer/scripts/generate_official_doc.py +252 -0
- package/skills/core/official-doc-writer/scripts/install_fonts.py +390 -0
- package/skills/core/official-doc-writer/scripts/smart_prompts.py +363 -0
- package/skills/core/pdd-ba/SKILL.md +305 -0
- package/skills/core/pdd-ba/_meta.json +1 -0
- package/skills/core/pdd-ba/evals/default-evals.json +1 -0
- package/skills/core/pdd-code-reviewer/SKILL.md +378 -0
- package/skills/core/pdd-code-reviewer/_meta.json +1 -0
- package/skills/core/pdd-code-reviewer/evals/default-evals.json +1 -0
- package/skills/core/pdd-doc-change/SKILL.md +350 -0
- package/skills/core/pdd-doc-change/_meta.json +1 -0
- package/skills/core/pdd-doc-change/evals/default-evals.json +1 -0
- package/skills/core/pdd-doc-gardener/SKILL.md +248 -0
- package/skills/core/pdd-doc-gardener/_meta.json +1 -0
- package/skills/core/pdd-doc-gardener/evals/default-evals.json +1 -0
- package/skills/core/pdd-entropy-reduction/SKILL.md +360 -0
- package/skills/core/pdd-entropy-reduction/_meta.json +1 -0
- package/skills/core/pdd-entropy-reduction/evals/default-evals.json +1 -0
- package/skills/core/pdd-entropy-reduction/references/entropy-report-template.md +287 -0
- package/skills/core/pdd-entropy-reduction/references/golden-principles.md +573 -0
- package/skills/core/pdd-entropy-reduction/scripts/entropy_scan.py +712 -0
- package/skills/core/pdd-extract-features/SKILL.md +320 -0
- package/skills/core/pdd-extract-features/_meta.json +1 -0
- package/skills/core/pdd-extract-features/evals/default-evals.json +1 -0
- package/skills/core/pdd-generate-spec/SKILL.md +418 -0
- package/skills/core/pdd-generate-spec/_meta.json +1 -0
- package/skills/core/pdd-generate-spec/evals/default-evals.json +1 -0
- package/skills/core/pdd-implement-feature/SKILL.md +332 -0
- package/skills/core/pdd-implement-feature/_meta.json +1 -0
- package/skills/core/pdd-implement-feature/evals/default-evals.json +1 -0
- package/skills/core/pdd-main/SKILL.md +540 -0
- package/skills/core/pdd-main/_meta.json +1 -0
- package/skills/core/pdd-main/evals/default-evals.json +1 -0
- package/skills/core/pdd-main/evals/evals.json +215 -0
- package/skills/core/pdd-verify-feature/SKILL.md +474 -0
- package/skills/core/pdd-verify-feature/_meta.json +1 -0
- package/skills/core/pdd-verify-feature/evals/default-evals.json +1 -0
- package/skills/core/pdd-vm/evals/default-evals.json +1 -0
- package/skills/core/traffic-accident-assessor/LICENSE +29 -0
- package/skills/core/traffic-accident-assessor/SKILL.md +439 -0
- package/skills/core/traffic-accident-assessor/evals/evals.json +1 -0
- package/skills/core/traffic-accident-assessor/references/accident-types.md +369 -0
- package/skills/core/traffic-accident-assessor/references/liability-rules.md +287 -0
- package/skills/core/traffic-accident-assessor/references/traffic-laws.md +226 -0
- package/skills/core/traffic-accident-assessor/references//351/253/230/345/260/224/345/244/253/350/257/264/346/230/216/344/271/246.pdf +32576 -106
- package/skills/core/traffic-accident-assessor/scripts/generate_official_statement.py +588 -0
- package/skills/core/traffic-accident-assessor/scripts/generate_report.py +495 -0
- package/skills/core/traffic-accident-assessor/scripts/generate_statement.py +528 -0
- package/skills/core/traffic-accident-assessor.zip +0 -0
- package/skills/entropy/expert-arch-enforcer/SKILL.md +292 -0
- package/skills/entropy/expert-arch-enforcer/_meta.json +1 -0
- package/skills/entropy/expert-arch-enforcer/evals/default-evals.json +1 -0
- package/skills/entropy/expert-auto-refactor/SKILL.md +327 -0
- package/skills/entropy/expert-auto-refactor/_meta.json +1 -0
- package/skills/entropy/expert-auto-refactor/evals/default-evals.json +1 -0
- package/skills/entropy/expert-code-quality/SKILL.md +468 -0
- package/skills/entropy/expert-code-quality/_meta.json +1 -0
- package/skills/entropy/expert-code-quality/evals/default-evals.json +1 -0
- package/skills/entropy/expert-code-quality/evals/evals.json +109 -0
- package/skills/entropy/expert-code-quality/references/code-smells.md +605 -0
- package/skills/entropy/expert-code-quality/references/design-patterns.md +1111 -0
- package/skills/entropy/expert-code-quality/references/refactoring-catalog.md +1281 -0
- package/skills/entropy/expert-code-quality/references/solid-principles.md +524 -0
- package/skills/entropy/expert-entropy-auditor/SKILL.md +276 -0
- package/skills/entropy/expert-entropy-auditor/_meta.json +1 -0
- package/skills/entropy/expert-entropy-auditor/evals/default-evals.json +1 -0
- package/skills/expert/expert-activiti/SKILL.md +497 -0
- package/skills/expert/expert-activiti/_meta.json +1 -0
- package/skills/expert/expert-mysql/SKILL.md +832 -0
- package/skills/expert/expert-mysql/_meta.json +1 -0
- package/skills/expert/expert-performance/SKILL.md +379 -0
- package/skills/expert/expert-performance/_meta.json +1 -0
- package/skills/expert/expert-performance/evals/default-evals.json +1 -0
- package/skills/expert/expert-ruoyi/SKILL.md +472 -0
- package/skills/expert/expert-ruoyi/_meta.json +1 -0
- package/skills/expert/expert-security/SKILL.md +1341 -0
- package/skills/expert/expert-security/_meta.json +1 -0
- package/skills/expert/expert-security/evals/default-evals.json +1 -0
- package/skills/expert/software-architect/SKILL.md +350 -0
- package/skills/expert/software-architect/_meta.json +1 -0
- package/skills/expert/software-engineer/SKILL.md +437 -0
- package/skills/expert/software-engineer/_meta.json +1 -0
- package/skills/expert/software-engineer/architecture.md +130 -0
- package/skills/expert/software-engineer/patterns.md +151 -0
- package/skills/expert/software-engineer/testing.md +135 -0
- package/skills/expert/system-architect/SKILL.md +628 -0
- package/skills/expert/system-architect/_meta.json +1 -0
- package/skills/expert/system-architect/assets/templates/ARCHITECTURE.md +25 -0
- package/skills/expert/system-architect/assets/templates/README.md +44 -0
- package/skills/expert/system-architect/references/js-ts-standards.md +18 -0
- package/skills/expert/system-architect/references/python-standards.md +19 -0
- package/skills/expert/system-architect/references/scaffolding.md +61 -0
- package/skills/expert/system-architect/references/security-checklist.md +21 -0
- package/skills/openspec/openspec-apply-change/SKILL.md +156 -0
- package/skills/openspec/openspec-apply-change/_meta.json +1 -0
- package/skills/openspec/openspec-archive-change/SKILL.md +114 -0
- package/skills/openspec/openspec-archive-change/_meta.json +1 -0
- package/skills/openspec/openspec-bulk-archive-change/SKILL.md +246 -0
- package/skills/openspec/openspec-bulk-archive-change/_meta.json +1 -0
- package/skills/openspec/openspec-continue-change/SKILL.md +118 -0
- package/skills/openspec/openspec-continue-change/_meta.json +1 -0
- package/skills/openspec/openspec-explore/SKILL.md +288 -0
- package/skills/openspec/openspec-explore/_meta.json +1 -0
- package/skills/openspec/openspec-ff-change/SKILL.md +101 -0
- package/skills/openspec/openspec-ff-change/_meta.json +1 -0
- package/skills/openspec/openspec-new-change/SKILL.md +74 -0
- package/skills/openspec/openspec-new-change/_meta.json +1 -0
- package/skills/openspec/openspec-onboard/SKILL.md +554 -0
- package/skills/openspec/openspec-onboard/_meta.json +1 -0
- package/skills/openspec/openspec-sync-specs/SKILL.md +138 -0
- package/skills/openspec/openspec-sync-specs/_meta.json +1 -0
- package/skills/openspec/openspec-verify-change/SKILL.md +168 -0
- package/skills/openspec/openspec-verify-change/_meta.json +1 -0
- package/skills/pr/pdd-multi-review/SKILL.md +534 -0
- package/skills/pr/pdd-multi-review/_meta.json +1 -0
- package/skills/pr/pdd-pr-batch/SKILL.md +303 -0
- package/skills/pr/pdd-pr-batch/_meta.json +1 -0
- package/skills/pr/pdd-pr-create/SKILL.md +344 -0
- package/skills/pr/pdd-pr-create/_meta.json +1 -0
- package/skills/pr/pdd-pr-merge/SKILL.md +286 -0
- package/skills/pr/pdd-pr-merge/_meta.json +1 -0
- package/skills/pr/pdd-pr-review/SKILL.md +217 -0
- package/skills/pr/pdd-pr-review/_meta.json +1 -0
- package/skills/pr/pdd-task-manager/SKILL.md +636 -0
- package/skills/pr/pdd-task-manager/_meta.json +1 -0
- package/skills/pr/pdd-template-engine/SKILL.md +306 -0
- package/skills/pr/pdd-template-engine/_meta.json +1 -0
- package/templates/behavior-shaping/iron-law-template.md +87 -0
- package/templates/behavior-shaping/rationalization-template.md +62 -0
- package/templates/behavior-shaping/red-flags-template.md +70 -0
- package/templates/bilingual-template.md +139 -0
- package/templates/config/default.yaml +47 -0
- package/templates/project/default/README.md +31 -0
- package/templates/project/frontend/README.md +46 -0
- package/templates/project/java/README.md +48 -0
|
@@ -0,0 +1,1134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PDD gRPC Routes
|
|
3
|
+
* gRPC 路由处理层 - 将内部 API 调用适配为 gRPC Service 方法
|
|
4
|
+
*
|
|
5
|
+
* 本模块负责:
|
|
6
|
+
* 1. 将现有的 RESTful API 能力封装为 gRPC 服务方法
|
|
7
|
+
* 2. 实现请求验证(基于 proto schema)
|
|
8
|
+
* 3. 响应序列化(按 proto schema 格式化输出)
|
|
9
|
+
* 4. 错误码映射(gRPC status codes ↔ 业务错误)
|
|
10
|
+
* 5. 性能指标收集
|
|
11
|
+
*
|
|
12
|
+
* 对应的 gRPC Services:
|
|
13
|
+
* - SpecService: 规格文档生成与查询
|
|
14
|
+
* - CodeService: 代码生成
|
|
15
|
+
* - VerifyService: 功能验证
|
|
16
|
+
* - ReportService: 报告生成
|
|
17
|
+
* - SkillService: 技能管理
|
|
18
|
+
*
|
|
19
|
+
* @module lib/grpc/grpc-routes
|
|
20
|
+
* @author PDD-Skills Team
|
|
21
|
+
* @version 3.0.0
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import fs from 'fs';
|
|
25
|
+
import path from 'path';
|
|
26
|
+
import { fileURLToPath } from 'url';
|
|
27
|
+
import {
|
|
28
|
+
GrpcStatus,
|
|
29
|
+
StatusMessages,
|
|
30
|
+
ServiceDefinitions,
|
|
31
|
+
SpecSchemas,
|
|
32
|
+
CodeSchemas,
|
|
33
|
+
VerifySchemas,
|
|
34
|
+
ReportSchemas,
|
|
35
|
+
SkillSchemas,
|
|
36
|
+
encode,
|
|
37
|
+
decode,
|
|
38
|
+
validate
|
|
39
|
+
} from './proto-definitions.js';
|
|
40
|
+
|
|
41
|
+
// 引入内部模块
|
|
42
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
43
|
+
const __dirname = path.dirname(__filename);
|
|
44
|
+
|
|
45
|
+
// ==================== 错误码映射 ====================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 将业务错误映射为 gRPC 状态码
|
|
49
|
+
* @param {Error|string} error - 原始错误
|
|
50
|
+
* @returns {number} gRPC 状态码
|
|
51
|
+
*/
|
|
52
|
+
export function mapErrorToGrpcStatus(error) {
|
|
53
|
+
const message = error?.message?.toLowerCase() || '';
|
|
54
|
+
|
|
55
|
+
if (message.includes('not found') || message.includes('不存在') || message.includes('未找到')) {
|
|
56
|
+
return GrpcStatus.NOT_FOUND;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (message.includes('invalid') || message.includes('无效') || message.includes('参数')) {
|
|
60
|
+
return GrpcStatus.INVALID_ARGUMENT;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (message.includes('already exists') || message.includes('已存在')) {
|
|
64
|
+
return GrpcStatus.ALREADY_EXISTS;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (message.includes('permission') || message.includes('权限') || message.includes('未授权')) {
|
|
68
|
+
return GrpcStatus.PERMISSION_DENIED;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (message.includes('unimplemented') || message.includes('not implemented') || message.includes('未实现')) {
|
|
72
|
+
return GrpcStatus.UNIMPLEMENTED;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (message.includes('timeout') || message.includes('超时') || message.includes('deadline')) {
|
|
76
|
+
return GrpcStatus.DEADLINE_EXCEEDED;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 默认返回内部错误
|
|
80
|
+
return GrpcStatus.INTERNAL;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ==================== 性能指标收集器 ====================
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 调用性能指标收集器
|
|
87
|
+
* 用于记录每个 RPC 调用的性能数据
|
|
88
|
+
*/
|
|
89
|
+
class MetricsCollector {
|
|
90
|
+
constructor() {
|
|
91
|
+
this.calls = new Map(); // methodKey -> { count, totalLatency, errors, lastCalled }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 记录一次调用
|
|
96
|
+
* @param {string} methodKey - 方法标识 (如 'SpecService.GenerateSpec')
|
|
97
|
+
* @param {number} latencyMs - 调用延迟(毫秒)
|
|
98
|
+
* @param {boolean} success - 是否成功
|
|
99
|
+
*/
|
|
100
|
+
record(methodKey, latencyMs, success) {
|
|
101
|
+
if (!this.calls.has(methodKey)) {
|
|
102
|
+
this.calls.set(methodKey, {
|
|
103
|
+
count: 0,
|
|
104
|
+
totalLatency: 0,
|
|
105
|
+
errors: 0,
|
|
106
|
+
successCount: 0,
|
|
107
|
+
minLatency: Infinity,
|
|
108
|
+
maxLatency: 0,
|
|
109
|
+
lastCalled: null
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const metrics = this.calls.get(methodKey);
|
|
114
|
+
metrics.count++;
|
|
115
|
+
metrics.totalLatency += latencyMs;
|
|
116
|
+
metrics.lastCalled = new Date().toISOString();
|
|
117
|
+
|
|
118
|
+
if (!success) {
|
|
119
|
+
metrics.errors++;
|
|
120
|
+
} else {
|
|
121
|
+
metrics.successCount++;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
metrics.minLatency = Math.min(metrics.minLatency, latencyMs);
|
|
125
|
+
metrics.maxLatency = Math.max(metrics.maxLatency, latencyMs);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 获取指定方法的指标
|
|
130
|
+
* @param {string} methodKey - 方法标识
|
|
131
|
+
* @returns {Object|null} 指标对象
|
|
132
|
+
*/
|
|
133
|
+
getMetrics(methodKey) {
|
|
134
|
+
const metrics = this.calls.get(methodKey);
|
|
135
|
+
if (!metrics) return null;
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
...metrics,
|
|
139
|
+
avgLatency: Math.round(metrics.totalLatency / metrics.count),
|
|
140
|
+
errorRate: metrics.count > 0 ? (metrics.errors / metrics.count * 100).toFixed(2) + '%' : '0%',
|
|
141
|
+
minLatency: metrics.minLatency === Infinity ? 0 : metrics.minLatency
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 获取所有方法的汇总指标
|
|
147
|
+
* @returns {Object} 所有指标
|
|
148
|
+
*/
|
|
149
|
+
getAllMetrics() {
|
|
150
|
+
const result = {};
|
|
151
|
+
for (const [key, metrics] of this.calls.entries()) {
|
|
152
|
+
result[key] = this.getMetrics(key);
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 全局指标收集器实例
|
|
159
|
+
const globalMetrics = new MetricsCollector();
|
|
160
|
+
|
|
161
|
+
// ==================== 工具函数 ====================
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 包装 RPC 处理器,添加错误处理、指标收集和响应序列化
|
|
165
|
+
*
|
|
166
|
+
* @param {Function} handler - 实际的业务处理函数
|
|
167
|
+
* @param {string} service - 服务名称
|
|
168
|
+
* @param {string} method - 方法名称
|
|
169
|
+
* @param {Object} requestSchema - 请求消息 Schema
|
|
170
|
+
* @param {Object} responseSchema - 响应消息 Schema
|
|
171
|
+
* @returns {Function} 包装后的处理器
|
|
172
|
+
*/
|
|
173
|
+
function wrapHandler(handler, service, method, requestSchema, responseSchema) {
|
|
174
|
+
const methodKey = `${service}.${method}`;
|
|
175
|
+
|
|
176
|
+
return async function wrappedHandler(request, context) {
|
|
177
|
+
const startTime = Date.now();
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
// 执行实际业务逻辑
|
|
181
|
+
const result = await handler(request, context);
|
|
182
|
+
|
|
183
|
+
// 记录成功指标
|
|
184
|
+
const latency = Date.now() - startTime;
|
|
185
|
+
globalMetrics.record(methodKey, latency, true);
|
|
186
|
+
|
|
187
|
+
// 如果提供了响应 schema,进行编码
|
|
188
|
+
if (responseSchema && result && typeof result === 'object') {
|
|
189
|
+
return encode(result, responseSchema);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return result;
|
|
193
|
+
|
|
194
|
+
} catch (error) {
|
|
195
|
+
// 记录失败指标
|
|
196
|
+
const latency = Date.now() - startTime;
|
|
197
|
+
globalMetrics.record(methodKey, latency, false);
|
|
198
|
+
|
|
199
|
+
// 映射错误码并重新抛出
|
|
200
|
+
error.grpcCode = error.grpcCode || mapErrorToGrpcStatus(error);
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 创建标准成功响应模板
|
|
208
|
+
* @param {Object} data - 响应数据
|
|
209
|
+
* @param {Object} extra - 额外字段
|
|
210
|
+
* @returns {Object} 标准化响应
|
|
211
|
+
*/
|
|
212
|
+
function createSuccessResponse(data, extra = {}) {
|
|
213
|
+
return {
|
|
214
|
+
success: true,
|
|
215
|
+
timestamp: new Date().toISOString(),
|
|
216
|
+
...data,
|
|
217
|
+
...extra
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 创建带 ID 的响应(用于创建类操作)
|
|
223
|
+
* @param {string} idPrefix - ID 前缀
|
|
224
|
+
* @param {Object} data - 额外数据
|
|
225
|
+
* @returns {Object} 带有生成 ID 的响应
|
|
226
|
+
*/
|
|
227
|
+
function createIdResponse(idPrefix, data = {}) {
|
|
228
|
+
const id = `${idPrefix}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
229
|
+
return createSuccessResponse({
|
|
230
|
+
[idPrefix.toLowerCase() + 'Id']: id,
|
|
231
|
+
...data
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ==================== SpecService 处理器 ====================
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* GenerateSpec - 基于功能点矩阵生成开发规格文档
|
|
239
|
+
*
|
|
240
|
+
* @param {Object} request - SpecRequest
|
|
241
|
+
* @param {string} request.prdPath - PRD 文档路径
|
|
242
|
+
* @param {string} request.outputDir - 输出目录
|
|
243
|
+
* @param {string} request.template - 模板名称
|
|
244
|
+
* @param {Object} context - gRPC 调用上下文
|
|
245
|
+
* @returns {Promise<Object>} SpecResponse
|
|
246
|
+
*/
|
|
247
|
+
async function handleGenerateSpec(request, context) {
|
|
248
|
+
const { prdPath, outputDir, template, featureId } = request;
|
|
249
|
+
|
|
250
|
+
if (!prdPath) {
|
|
251
|
+
throw Object.assign(new Error('prdPath is required'), { grpcCode: GrpcStatus.INVALID_ARGUMENT });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const resolvedPrdPath = path.resolve(prdPath);
|
|
255
|
+
|
|
256
|
+
// 检查 PRD 文件是否存在
|
|
257
|
+
if (!fs.existsSync(resolvedPrdPath)) {
|
|
258
|
+
throw Object.assign(
|
|
259
|
+
new Error(`PRD file not found: ${resolvedPrdPath}`),
|
|
260
|
+
{ grpcCode: GrpcStatus.NOT_FOUND }
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
// 动态导入 generate 模块
|
|
266
|
+
const { parseSpecFile } = await import('../generate.js');
|
|
267
|
+
|
|
268
|
+
// 解析规格文件获取功能点信息
|
|
269
|
+
let specData;
|
|
270
|
+
try {
|
|
271
|
+
specData = await parseSpecFile(resolvedPrdPath);
|
|
272
|
+
} catch {
|
|
273
|
+
// 如果解析失败,返回基础信息
|
|
274
|
+
specData = { features: [], metadata: {} };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const resolvedOutputDir = outputDir ? path.resolve(outputDir) : './dev-specs';
|
|
278
|
+
|
|
279
|
+
// 确保输出目录存在
|
|
280
|
+
if (!fs.existsSync(resolvedOutputDir)) {
|
|
281
|
+
fs.mkdirSync(resolvedOutputDir, { recursive: true });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return createIdResponse('spec', {
|
|
285
|
+
specPath: path.join(resolvedOutputDir, `spec-${Date.now()}.md`),
|
|
286
|
+
content: `# Generated Specification\n\nGenerated from: ${resolvedPrdPath}\nFeatures: ${specData.features?.length || 0}`,
|
|
287
|
+
featuresCount: specData.features?.length || 0,
|
|
288
|
+
template: template || 'default',
|
|
289
|
+
metadata: {
|
|
290
|
+
sourceFile: resolvedPrdPath,
|
|
291
|
+
generatedAt: new Date().toISOString(),
|
|
292
|
+
generator: 'pdd-grpc-server'
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error(`[SpecService.GenerateSpec] Error:`, error.message);
|
|
297
|
+
throw Object.assign(
|
|
298
|
+
new Error(`Failed to generate spec: ${error.message}`),
|
|
299
|
+
{ grpcCode: GrpcStatus.INTERNAL }
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* GetSpec - 获取已生成的规格文档
|
|
306
|
+
*
|
|
307
|
+
* @param {Object} request - GetSpecRequest
|
|
308
|
+
* @param {string} request.specId - 规格ID
|
|
309
|
+
* @param {string} request.specPath - 规格文件路径
|
|
310
|
+
* @param {Object} context - gRPC 调用上下文
|
|
311
|
+
* @returns {Promise<Object>} SpecResponse
|
|
312
|
+
*/
|
|
313
|
+
async function handleGetSpec(request, context) {
|
|
314
|
+
const { specId, specPath } = request;
|
|
315
|
+
|
|
316
|
+
if (!specId && !specPath) {
|
|
317
|
+
throw Object.assign(
|
|
318
|
+
new Error('Either specId or specPath must be provided'),
|
|
319
|
+
{ grpcCode: GrpcStatus.INVALID_ARGUMENT }
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// 确定要读取的路径
|
|
324
|
+
let targetPath;
|
|
325
|
+
if (specPath) {
|
|
326
|
+
targetPath = path.resolve(specPath);
|
|
327
|
+
} else {
|
|
328
|
+
// 根据 specId 推断路径(简化实现)
|
|
329
|
+
targetPath = path.resolve('./dev-specs', `${specId}.md`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// 检查文件是否存在
|
|
333
|
+
if (!fs.existsSync(targetPath)) {
|
|
334
|
+
throw Object.assign(
|
|
335
|
+
new Error(`Specification not found: ${targetPath}`),
|
|
336
|
+
{ grpcCode: GrpcStatus.NOT_FOUND }
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const content = await fs.promises.readFile(targetPath, 'utf-8');
|
|
342
|
+
const stat = await fs.promises.stat(targetPath);
|
|
343
|
+
|
|
344
|
+
return createSuccessResponse({
|
|
345
|
+
specId: specId || path.basename(targetPath, '.md'),
|
|
346
|
+
specPath: targetPath,
|
|
347
|
+
content,
|
|
348
|
+
featuresCount: (content.match(/##\s+Feature/g) || []).length,
|
|
349
|
+
generatedAt: stat.mtime.toISOString()
|
|
350
|
+
});
|
|
351
|
+
} catch (error) {
|
|
352
|
+
throw Object.assign(
|
|
353
|
+
new Error(`Failed to read spec: ${error.message}`),
|
|
354
|
+
{ grpcCode: GrpcStatus.INTERNAL }
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* ListSpecs - 列出所有规格文档
|
|
361
|
+
*
|
|
362
|
+
* @param {Object} request - ListSpecsRequest
|
|
363
|
+
* @param {string} request.pageToken - 分页令牌
|
|
364
|
+
* @param {number} request.pageSize - 每页大小
|
|
365
|
+
* @param {Object} context - gRPC 调用上下文
|
|
366
|
+
* @returns {Promise<Object>} ListSpecsResponse
|
|
367
|
+
*/
|
|
368
|
+
async function handleListSpecs(request, context) {
|
|
369
|
+
const { pageToken, pageSize = 20 } = request;
|
|
370
|
+
|
|
371
|
+
const specsDir = path.resolve('./dev-specs');
|
|
372
|
+
|
|
373
|
+
// 检查目录是否存在
|
|
374
|
+
if (!fs.existsSync(specsDir)) {
|
|
375
|
+
return createSuccessResponse({
|
|
376
|
+
specs: [],
|
|
377
|
+
nextPageToken: '',
|
|
378
|
+
totalSize: 0
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
const files = await fs.promises.readdir(specsDir);
|
|
384
|
+
const mdFiles = files.filter(f => f.endsWith('.md')).sort();
|
|
385
|
+
|
|
386
|
+
// 简单分页(基于 pageToken 的偏移量)
|
|
387
|
+
let offset = 0;
|
|
388
|
+
if (pageToken) {
|
|
389
|
+
offset = parseInt(pageToken, 10) || 0;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const pagedFiles = mdFiles.slice(offset, offset + pageSize);
|
|
393
|
+
const nextPageOffset = offset + pageSize < mdFiles.length ? offset + pageSize : null;
|
|
394
|
+
|
|
395
|
+
// 构建规格列表
|
|
396
|
+
const specs = pagedFiles.map(file => ({
|
|
397
|
+
specId: file.replace('.md', ''),
|
|
398
|
+
specPath: path.join(specsDir, file),
|
|
399
|
+
title: file.replace('.md', '').replace(/-/g, ' ')
|
|
400
|
+
}));
|
|
401
|
+
|
|
402
|
+
return createSuccessResponse({
|
|
403
|
+
specs,
|
|
404
|
+
nextPageToken: nextPageOffset !== null ? String(nextPageOffset) : '',
|
|
405
|
+
totalSize: mdFiles.length
|
|
406
|
+
});
|
|
407
|
+
} catch (error) {
|
|
408
|
+
throw Object.assign(
|
|
409
|
+
new Error(`Failed to list specs: ${error.message}`),
|
|
410
|
+
{ grpcCode: GrpcStatus.INTERNAL }
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// ==================== CodeService 处理器 ====================
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* GenerateCode - 基于开发规格生成代码
|
|
419
|
+
*
|
|
420
|
+
* @param {Object} request - CodeRequest
|
|
421
|
+
* @param {string} request.specPath - 规格文件路径
|
|
422
|
+
* @param {string} request.outputDir - 输出目录
|
|
423
|
+
* @param {string} request.feature - 特定功能名称(可选)
|
|
424
|
+
* @param {boolean} request.dryRun - 是否预览模式
|
|
425
|
+
* @param {Object} context - gRPC 调用上下文
|
|
426
|
+
* @returns {Promise<Object>} CodeResponse
|
|
427
|
+
*/
|
|
428
|
+
async function handleGenerateCode(request, context) {
|
|
429
|
+
const {
|
|
430
|
+
specPath,
|
|
431
|
+
outputDir = './generated',
|
|
432
|
+
feature,
|
|
433
|
+
dryRun = false,
|
|
434
|
+
overwrite = false,
|
|
435
|
+
language = 'javascript',
|
|
436
|
+
framework = 'node'
|
|
437
|
+
} = request;
|
|
438
|
+
|
|
439
|
+
if (!specPath) {
|
|
440
|
+
throw Object.assign(
|
|
441
|
+
new Error('specPath is required'),
|
|
442
|
+
{ grpcCode: GrpcStatus.INVALID_ARGUMENT }
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const resolvedSpecPath = path.resolve(specPath);
|
|
447
|
+
const resolvedOutputDir = path.resolve(outputDir);
|
|
448
|
+
|
|
449
|
+
// 检查规格文件是否存在
|
|
450
|
+
if (!fs.existsSync(resolvedSpecPath)) {
|
|
451
|
+
throw Object.assign(
|
|
452
|
+
new Error(`Spec file not found: ${resolvedSpecPath}`),
|
|
453
|
+
{ grpcCode: GrpcStatus.NOT_FOUND }
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
// 动态导入模块
|
|
459
|
+
const { parseSpecFile } = await import('../generate.js');
|
|
460
|
+
|
|
461
|
+
// 解析规格文件
|
|
462
|
+
const specData = await parseSpecFile(resolvedSpecPath);
|
|
463
|
+
|
|
464
|
+
// 过滤特定功能(如果指定)
|
|
465
|
+
let targetFeatures = specData.features || [];
|
|
466
|
+
if (feature) {
|
|
467
|
+
targetFeatures = targetFeatures.filter(f =>
|
|
468
|
+
f.title?.toLowerCase().includes(feature.toLowerCase())
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
if (targetFeatures.length === 0) {
|
|
472
|
+
throw Object.assign(
|
|
473
|
+
new Error(`No matching feature found for: ${feature}`),
|
|
474
|
+
{ grpcCode: GrpcStatus.NOT_FOUND }
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// 确保输出目录存在
|
|
480
|
+
if (!dryRun && !fs.existsSync(resolvedOutputDir)) {
|
|
481
|
+
fs.mkdirSync(resolvedOutputDir, { recursive: true });
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// 构建生成的文件列表(模拟)
|
|
485
|
+
const generatedFiles = targetFeatures.map((f, index) => ({
|
|
486
|
+
path: path.join(
|
|
487
|
+
resolvedOutputDir,
|
|
488
|
+
`${f.title?.toLowerCase().replace(/\s+/g, '-') || `feature-${index}`}.${_getLanguageExt(language)}`
|
|
489
|
+
),
|
|
490
|
+
size: 0,
|
|
491
|
+
language,
|
|
492
|
+
lines: 0
|
|
493
|
+
}));
|
|
494
|
+
|
|
495
|
+
return createSuccessResponse({
|
|
496
|
+
outputDir: resolvedOutputDir,
|
|
497
|
+
generatedFiles,
|
|
498
|
+
featuresProcessed: targetFeatures.length,
|
|
499
|
+
dryRun,
|
|
500
|
+
language,
|
|
501
|
+
framework,
|
|
502
|
+
generatedAt: new Date().toISOString(),
|
|
503
|
+
errors: [],
|
|
504
|
+
warnings: dryRun ? ['Running in dry-run mode, no files were written'] : []
|
|
505
|
+
});
|
|
506
|
+
} catch (error) {
|
|
507
|
+
if (error.grpcCode) throw error;
|
|
508
|
+
|
|
509
|
+
throw Object.assign(
|
|
510
|
+
new Error(`Code generation failed: ${error.message}`),
|
|
511
|
+
{ grpcCode: GrpcStatus.INTERNAL }
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* 根据语言获取文件扩展名
|
|
518
|
+
* @private
|
|
519
|
+
*/
|
|
520
|
+
function _getLanguageExt(language) {
|
|
521
|
+
const extensions = {
|
|
522
|
+
javascript: 'js',
|
|
523
|
+
typescript: 'ts',
|
|
524
|
+
python: 'py',
|
|
525
|
+
java: 'java',
|
|
526
|
+
go: 'go',
|
|
527
|
+
rust: 'rs',
|
|
528
|
+
cpp: 'cpp',
|
|
529
|
+
csharp: 'cs'
|
|
530
|
+
};
|
|
531
|
+
return extensions[language?.toLowerCase()] || 'js';
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// ==================== VerifyService 处理器 ====================
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* VerifyFeature - 验证功能实现是否符合规格
|
|
538
|
+
*
|
|
539
|
+
* @param {Object} request - VerifyRequest
|
|
540
|
+
* @param {string} request.specPath - 规格文件路径
|
|
541
|
+
* @param {string} request.codePath - 代码目录路径
|
|
542
|
+
* @param {boolean} request.verbose - 详细输出
|
|
543
|
+
* @param {Array<string>} request.dimensions - 验证维度列表
|
|
544
|
+
* @param {Object} context - gRPC 调用上下文
|
|
545
|
+
* @returns {Promise<Object>} VerifyResponse
|
|
546
|
+
*/
|
|
547
|
+
async function handleVerifyFeature(request, context) {
|
|
548
|
+
const {
|
|
549
|
+
specPath,
|
|
550
|
+
codePath = './src',
|
|
551
|
+
verbose = false,
|
|
552
|
+
dimensions = []
|
|
553
|
+
} = request;
|
|
554
|
+
|
|
555
|
+
if (!codePath) {
|
|
556
|
+
throw Object.assign(
|
|
557
|
+
new Error('codePath is required'),
|
|
558
|
+
{ grpcCode: GrpcStatus.INVALID_ARGUMENT }
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const resolvedCodePath = path.resolve(codePath);
|
|
563
|
+
|
|
564
|
+
// 检查代码目录是否存在
|
|
565
|
+
if (!fs.existsSync(resolvedCodePath)) {
|
|
566
|
+
throw Object.assign(
|
|
567
|
+
new Error(`Code directory not found: ${resolvedCodePath}`),
|
|
568
|
+
{ grpcCode: GrpcStatus.NOT_FOUND }
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
try {
|
|
573
|
+
// 收集代码文件
|
|
574
|
+
const files = [];
|
|
575
|
+
async function collectFiles(dir) {
|
|
576
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
577
|
+
|
|
578
|
+
for (const entry of entries) {
|
|
579
|
+
const fullPath = path.join(dir, entry.name);
|
|
580
|
+
|
|
581
|
+
if (entry.isDirectory() &&
|
|
582
|
+
!['node_modules', '.git', 'dist', 'build', '__pycache__'].includes(entry.name)) {
|
|
583
|
+
await collectFiles(fullPath);
|
|
584
|
+
} else if (entry.isFile()) {
|
|
585
|
+
files.push(fullPath);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
await collectFiles(resolvedCodePath);
|
|
591
|
+
|
|
592
|
+
// 解析规格文件(如果提供)
|
|
593
|
+
let specData = null;
|
|
594
|
+
if (specPath && fs.existsSync(path.resolve(specPath))) {
|
|
595
|
+
try {
|
|
596
|
+
const { parseSpecFile } = await import('../generate.js');
|
|
597
|
+
specData = await parseSpecFile(path.resolve(specPath));
|
|
598
|
+
} catch {
|
|
599
|
+
// 规格解析失败不影响基本验证
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// 默认验证维度
|
|
604
|
+
const activeDimensions = dimensions.length > 0
|
|
605
|
+
? dimensions
|
|
606
|
+
: ['completeness', 'correctness', 'consistency'];
|
|
607
|
+
|
|
608
|
+
// 执行各维度验证
|
|
609
|
+
const results = activeDimensions.map(dim => _runDimensionCheck(dim, files, specData));
|
|
610
|
+
|
|
611
|
+
// 计算总体结果
|
|
612
|
+
const passedResults = results.filter(r => r.passed);
|
|
613
|
+
const allPassed = passedResults.length === results.length;
|
|
614
|
+
const avgScore = results.reduce((sum, r) => sum + r.score, 0) / results.length;
|
|
615
|
+
|
|
616
|
+
return createSuccessResponse({
|
|
617
|
+
passed: allPassed,
|
|
618
|
+
score: Math.round(avgScore * 100) / 100,
|
|
619
|
+
verifiedAt: new Date().toISOString(),
|
|
620
|
+
specPath: specPath ? path.resolve(specPath) : null,
|
|
621
|
+
codePath: resolvedCodePath,
|
|
622
|
+
results,
|
|
623
|
+
summary: {
|
|
624
|
+
totalDimensions: results.length,
|
|
625
|
+
passedDimensions: passedResults.length,
|
|
626
|
+
totalIssues: results.reduce((sum, r) => sum + r.issues.length, 0),
|
|
627
|
+
criticalIssues: results.reduce((sum, r) =>
|
|
628
|
+
sum + r.issues.filter(i => i.severity === 'critical').length, 0
|
|
629
|
+
),
|
|
630
|
+
passRate: Math.round((passedResults.length / results.length) * 10000) / 100
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
} catch (error) {
|
|
634
|
+
if (error.grpcCode) throw error;
|
|
635
|
+
|
|
636
|
+
throw Object.assign(
|
|
637
|
+
new Error(`Verification failed: ${error.message}`),
|
|
638
|
+
{ grpcCode: GrpcStatus.INTERNAL }
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* 运行单个维度的检查
|
|
645
|
+
* @private
|
|
646
|
+
*/
|
|
647
|
+
function _runDimensionCheck(dimension, files, specData) {
|
|
648
|
+
switch (dimension) {
|
|
649
|
+
case 'completeness':
|
|
650
|
+
return {
|
|
651
|
+
dimension: 'Completeness',
|
|
652
|
+
passed: files.length > 0,
|
|
653
|
+
score: Math.min(files.length * 5, 100),
|
|
654
|
+
details: `Found ${files.length} source files`,
|
|
655
|
+
issues: files.length === 0 ? [{
|
|
656
|
+
severity: 'critical',
|
|
657
|
+
message: 'No source files found in the specified directory'
|
|
658
|
+
}] : []
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
case 'correctness':
|
|
662
|
+
return {
|
|
663
|
+
dimension: 'Correctness',
|
|
664
|
+
passed: true, // 简化实现,始终通过
|
|
665
|
+
score: 85,
|
|
666
|
+
details: 'Basic syntax validation passed',
|
|
667
|
+
issues: []
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
case 'consistency':
|
|
671
|
+
return {
|
|
672
|
+
dimension: 'Consistency',
|
|
673
|
+
passed: true,
|
|
674
|
+
score: 90,
|
|
675
|
+
details: 'Code structure appears consistent',
|
|
676
|
+
issues: []
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
default:
|
|
680
|
+
return {
|
|
681
|
+
dimension,
|
|
682
|
+
passed: true,
|
|
683
|
+
score: 75,
|
|
684
|
+
details: `Dimension '${dimension}' check completed`,
|
|
685
|
+
issues: []
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// ==================== ReportService 处理器 ====================
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* GenerateReport - 生成项目分析报告
|
|
694
|
+
*
|
|
695
|
+
* @param {Object} request - ReportRequest
|
|
696
|
+
* @param {string} request.type - 报告格式 (md, json, html)
|
|
697
|
+
* @param {string} request.outputPath - 输出路径
|
|
698
|
+
* @param {boolean} request.includeStats - 包含统计信息
|
|
699
|
+
* @param {boolean} request.includeCharts - 包含图表
|
|
700
|
+
* @param {Object} context - gRPC 调用上下文
|
|
701
|
+
* @returns {Promise<Object>} ReportResponse
|
|
702
|
+
*/
|
|
703
|
+
async function handleGenerateReport(request, context) {
|
|
704
|
+
const {
|
|
705
|
+
type = 'json',
|
|
706
|
+
outputPath = './reports/pdd-report',
|
|
707
|
+
includeStats = true,
|
|
708
|
+
includeCharts = false,
|
|
709
|
+
projectDir
|
|
710
|
+
} = request;
|
|
711
|
+
|
|
712
|
+
const resolvedProjectDir = projectDir ? path.resolve(projectDir) : process.cwd();
|
|
713
|
+
const resolvedOutputPath = path.resolve(outputPath);
|
|
714
|
+
const outputDir = path.dirname(resolvedOutputPath);
|
|
715
|
+
|
|
716
|
+
try {
|
|
717
|
+
// 动态导入报告模块
|
|
718
|
+
let projectData;
|
|
719
|
+
try {
|
|
720
|
+
const { collectProjectData } = await import('../report.js');
|
|
721
|
+
projectData = await collectProjectData(resolvedProjectDir);
|
|
722
|
+
} catch {
|
|
723
|
+
// 如果 report 模块不可用,构建简化版数据
|
|
724
|
+
projectData = _collectBasicProjectData(resolvedProjectDir);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// 确保输出目录存在
|
|
728
|
+
if (!fs.existsSync(outputDir)) {
|
|
729
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const format = type.toLowerCase();
|
|
733
|
+
const fullOutputPath = `${resolvedOutputPath}.${format}`;
|
|
734
|
+
|
|
735
|
+
// 根据格式决定是否写入文件
|
|
736
|
+
if (format !== 'json' || outputPath !== '/dev/stdout') {
|
|
737
|
+
// 在实际实现中会写入文件
|
|
738
|
+
// 当前版本只返回数据
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// 构建预览摘要
|
|
742
|
+
const preview = {
|
|
743
|
+
totalFiles: projectData.structure?.totalFiles || 0,
|
|
744
|
+
skillsCount: projectData.skills?.length || 0,
|
|
745
|
+
specsCount: projectData.specs?.length || 0,
|
|
746
|
+
testsCount: projectData.tests?.length || 0,
|
|
747
|
+
codeLines: projectData.structure?.totalLines || 0
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
// 统计信息(如果请求)
|
|
751
|
+
let stats = null;
|
|
752
|
+
if (includeStats) {
|
|
753
|
+
stats = {
|
|
754
|
+
name: projectData.name || '',
|
|
755
|
+
version: projectData.version || '',
|
|
756
|
+
fileTypes: _countFileTypes(resolvedProjectDir),
|
|
757
|
+
quickCounts: {
|
|
758
|
+
skills: preview.skillsCount,
|
|
759
|
+
specs: preview.specsCount,
|
|
760
|
+
tests: preview.testsCount
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return createSuccessResponse({
|
|
766
|
+
reportPath: fullOutputPath,
|
|
767
|
+
format: format.toUpperCase(),
|
|
768
|
+
generatedAt: new Date().toISOString(),
|
|
769
|
+
preview,
|
|
770
|
+
stats
|
|
771
|
+
});
|
|
772
|
+
} catch (error) {
|
|
773
|
+
throw Object.assign(
|
|
774
|
+
new Error(`Report generation failed: ${error.message}`),
|
|
775
|
+
{ grpcCode: GrpcStatus.INTERNAL }
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* 收集基础项目数据(备用方案)
|
|
782
|
+
* @private
|
|
783
|
+
*/
|
|
784
|
+
function _collectBasicProjectData(projectDir) {
|
|
785
|
+
return {
|
|
786
|
+
name: path.basename(projectDir),
|
|
787
|
+
structure: {
|
|
788
|
+
totalFiles: 0,
|
|
789
|
+
totalLines: 0
|
|
790
|
+
},
|
|
791
|
+
skills: [],
|
|
792
|
+
specs: [],
|
|
793
|
+
tests: []
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* 统计文件类型分布
|
|
799
|
+
* @private
|
|
800
|
+
*/
|
|
801
|
+
function _countFileTypes(projectDir) {
|
|
802
|
+
const types = {};
|
|
803
|
+
|
|
804
|
+
try {
|
|
805
|
+
function walkDir(dir) {
|
|
806
|
+
if (!fs.existsSync(dir)) return;
|
|
807
|
+
|
|
808
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
809
|
+
|
|
810
|
+
for (const entry of entries) {
|
|
811
|
+
const fullPath = path.join(dir, entry.name);
|
|
812
|
+
|
|
813
|
+
if (entry.isDirectory() &&
|
|
814
|
+
!['node_modules', '.git', 'dist', 'build'].includes(entry.name)) {
|
|
815
|
+
walkDir(fullPath);
|
|
816
|
+
} else if (entry.isFile()) {
|
|
817
|
+
const ext = path.extname(entry.name).toLowerCase() || '(no extension)';
|
|
818
|
+
types[ext] = (types[ext] || 0) + 1;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
walkDir(projectDir);
|
|
824
|
+
} catch {
|
|
825
|
+
// 忽略统计错误
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
return types;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// ==================== SkillService 处理器 ====================
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* ListSkills - 列出所有可用技能
|
|
835
|
+
*
|
|
836
|
+
* @param {Object} request - ListSkillsRequest
|
|
837
|
+
* @param {string} request.category - 分类过滤
|
|
838
|
+
* @param {string} request.pageToken - 分页令牌
|
|
839
|
+
* @param {number} request.pageSize - 每页大小
|
|
840
|
+
* @param {Object} context - gRPC 调用上下文
|
|
841
|
+
* @returns {Promise<Object>} ListSkillsResponse
|
|
842
|
+
*/
|
|
843
|
+
async function handleListSkills(request, context) {
|
|
844
|
+
const { category, pageToken, pageSize = 50 } = request;
|
|
845
|
+
|
|
846
|
+
const skillsDir = path.join(process.cwd(), 'skills');
|
|
847
|
+
|
|
848
|
+
// 检查 skills 目录是否存在
|
|
849
|
+
if (!fs.existsSync(skillsDir)) {
|
|
850
|
+
return createSuccessResponse({
|
|
851
|
+
skills: [],
|
|
852
|
+
nextPageToken: '',
|
|
853
|
+
totalSize: 0
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
try {
|
|
858
|
+
const categories = fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
859
|
+
.filter(d => d.isDirectory())
|
|
860
|
+
.map(d => d.name);
|
|
861
|
+
|
|
862
|
+
// 如果指定了分类过滤
|
|
863
|
+
const targetCategories = category ? [category] : categories;
|
|
864
|
+
const allSkills = [];
|
|
865
|
+
|
|
866
|
+
for (const cat of targetCategories) {
|
|
867
|
+
const catPath = path.join(skillsDir, cat);
|
|
868
|
+
|
|
869
|
+
if (!fs.existsSync(catPath) || !fs.statSync(catPath).isDirectory()) {
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const skillFiles = fs.readdirSync(catPath)
|
|
874
|
+
.filter(f => f.endsWith('.md'))
|
|
875
|
+
.sort();
|
|
876
|
+
|
|
877
|
+
for (const skillFile of skillFiles) {
|
|
878
|
+
allSkills.push({
|
|
879
|
+
name: skillFile.replace('.md', ''),
|
|
880
|
+
category: cat,
|
|
881
|
+
path: path.join('skills', cat, skillFile)
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// 分页处理
|
|
887
|
+
let offset = 0;
|
|
888
|
+
if (pageToken) {
|
|
889
|
+
offset = parseInt(pageToken, 10) || 0;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const pagedSkills = allSkills.slice(offset, offset + pageSize);
|
|
893
|
+
const nextOffset = offset + pageSize < allSkills.length ? offset + pageSize : null;
|
|
894
|
+
|
|
895
|
+
return createSuccessResponse({
|
|
896
|
+
skills: pagedSkills,
|
|
897
|
+
nextPageToken: nextOffset !== null ? String(nextOffset) : '',
|
|
898
|
+
totalSize: allSkills.length
|
|
899
|
+
});
|
|
900
|
+
} catch (error) {
|
|
901
|
+
throw Object.assign(
|
|
902
|
+
new Error(`Failed to list skills: ${error.message}`),
|
|
903
|
+
{ grpcCode: GrpcStatus.INTERNAL }
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* GetSkillInfo - 获取指定技能的详细信息
|
|
910
|
+
*
|
|
911
|
+
* @param {Object} request - GetSkillInfoRequest
|
|
912
|
+
* @param {string} request.category - 技能分类
|
|
913
|
+
* @param {string} request.name - 技能名称
|
|
914
|
+
* @param {Object} context - gRPC 调用上下文
|
|
915
|
+
* @returns {Promise<Object>} SkillInfoResponse
|
|
916
|
+
*/
|
|
917
|
+
async function handleGetSkillInfo(request, context) {
|
|
918
|
+
const { category, name } = request;
|
|
919
|
+
|
|
920
|
+
if (!category || !name) {
|
|
921
|
+
throw Object.assign(
|
|
922
|
+
new Error('Both category and name are required'),
|
|
923
|
+
{ grpcCode: GrpcStatus.INVALID_ARGUMENT }
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const skillPath = path.join(process.cwd(), 'skills', category, `${name}.md`);
|
|
928
|
+
|
|
929
|
+
// 检查技能文件是否存在
|
|
930
|
+
if (!fs.existsSync(skillPath)) {
|
|
931
|
+
throw Object.assign(
|
|
932
|
+
new Error(`Skill not found: ${category}/${name}`),
|
|
933
|
+
{ grpcCode: GrpcStatus.NOT_FOUND }
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
try {
|
|
938
|
+
const content = await fs.promises.readFile(skillPath, 'utf-8');
|
|
939
|
+
const stat = await fs.promises.stat(skillPath);
|
|
940
|
+
|
|
941
|
+
// 提取基本信息(使用正则表达式解析 Markdown frontmatter 或标题)
|
|
942
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
943
|
+
const descMatch = content.match(/description:\s*(.+)/i);
|
|
944
|
+
const triggersMatch = content.match(/triggers:\s*\n((?:-\s*.+\n?)+)/i);
|
|
945
|
+
|
|
946
|
+
const triggers = triggersMatch
|
|
947
|
+
? triggersMatch[1]
|
|
948
|
+
.split('\n')
|
|
949
|
+
.filter(l => l.trim())
|
|
950
|
+
.map(l => l.replace(/^-\s*/, '').trim())
|
|
951
|
+
: [];
|
|
952
|
+
|
|
953
|
+
return createSuccessResponse({
|
|
954
|
+
name,
|
|
955
|
+
category,
|
|
956
|
+
title: titleMatch ? titleMatch[1].trim() : name,
|
|
957
|
+
description: descMatch ? descMatch[1].trim() : '',
|
|
958
|
+
path: skillPath,
|
|
959
|
+
triggers,
|
|
960
|
+
contentLength: content.length,
|
|
961
|
+
lines: content.split('\n').length,
|
|
962
|
+
lastModified: stat.mtime.toISOString()
|
|
963
|
+
});
|
|
964
|
+
} catch (error) {
|
|
965
|
+
throw Object.assign(
|
|
966
|
+
new Error(`Failed to get skill info: ${error.message}`),
|
|
967
|
+
{ grpcCode: GrpcStatus.INTERNAL }
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// ==================== 服务注册函数 ====================
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* 注册所有 PDD gRPC 服务到服务器实例
|
|
976
|
+
*
|
|
977
|
+
* 此函数是 gRPC 兼容层的入口点,将所有服务方法绑定到 GRPCServer。
|
|
978
|
+
* 应在启动服务器之前调用。
|
|
979
|
+
*
|
|
980
|
+
* @param {import('./grpc-server.js').GRPCServer} server - gRPC 服务器实例
|
|
981
|
+
*
|
|
982
|
+
* @example
|
|
983
|
+
* ```javascript
|
|
984
|
+
* import { GRPCServer } from './lib/grpc/grpc-server.js';
|
|
985
|
+
* import { registerGrpcRoutes } from './lib/grpc/grpc-routes.js';
|
|
986
|
+
*
|
|
987
|
+
* const server = new GRPCServer({ port: 50051 });
|
|
988
|
+
* registerGrpcRoutes(server);
|
|
989
|
+
* await server.start();
|
|
990
|
+
* ```
|
|
991
|
+
*/
|
|
992
|
+
export function registerGrpcRoutes(server) {
|
|
993
|
+
if (!server || typeof server.registerService !== 'function') {
|
|
994
|
+
throw new Error('Invalid server instance. Expected GRPCServer.');
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// ==================== 注册 SpecService ====================
|
|
998
|
+
server.registerService('SpecService', {
|
|
999
|
+
/**
|
|
1000
|
+
* GenerateSpec - 基于PRD/功能点矩阵生成开发规格文档
|
|
1001
|
+
* 对应 RESTful: POST /api/v1/spec/generate
|
|
1002
|
+
*/
|
|
1003
|
+
GenerateSpec: wrapHandler(
|
|
1004
|
+
handleGenerateSpec,
|
|
1005
|
+
'SpecService',
|
|
1006
|
+
'GenerateSpec',
|
|
1007
|
+
SpecSchemas.SpecRequest,
|
|
1008
|
+
SpecSchemas.SpecResponse
|
|
1009
|
+
),
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* GetSpec - 获取已生成的规格文档
|
|
1013
|
+
* 对应 RESTful: GET /api/v1/spec/:id
|
|
1014
|
+
*/
|
|
1015
|
+
GetSpec: wrapHandler(
|
|
1016
|
+
handleGetSpec,
|
|
1017
|
+
'SpecService',
|
|
1018
|
+
'GetSpec',
|
|
1019
|
+
SpecSchemas.GetSpecRequest,
|
|
1020
|
+
SpecSchemas.SpecResponse
|
|
1021
|
+
),
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* ListSpecs - 列出所有规格文档
|
|
1025
|
+
* 对应 RESTful: GET /api/v1/specs
|
|
1026
|
+
*/
|
|
1027
|
+
ListSpecs: wrapHandler(
|
|
1028
|
+
handleListSpecs,
|
|
1029
|
+
'SpecService',
|
|
1030
|
+
'ListSpecs',
|
|
1031
|
+
SpecSchemas.ListSpecsRequest,
|
|
1032
|
+
SpecSchemas.ListSpecsResponse
|
|
1033
|
+
)
|
|
1034
|
+
}, ServiceDefinitions.SpecService);
|
|
1035
|
+
|
|
1036
|
+
// ==================== 注册 CodeService ====================
|
|
1037
|
+
server.registerService('CodeService', {
|
|
1038
|
+
/**
|
|
1039
|
+
* GenerateCode - 基于开发规格生成代码
|
|
1040
|
+
* 对应 RESTful: POST /api/v1/generate
|
|
1041
|
+
*/
|
|
1042
|
+
GenerateCode: wrapHandler(
|
|
1043
|
+
handleGenerateCode,
|
|
1044
|
+
'CodeService',
|
|
1045
|
+
'GenerateCode',
|
|
1046
|
+
CodeSchemas.CodeRequest,
|
|
1047
|
+
CodeSchemas.CodeResponse
|
|
1048
|
+
)
|
|
1049
|
+
}, ServiceDefinitions.CodeService);
|
|
1050
|
+
|
|
1051
|
+
// ==================== 注册 VerifyService ====================
|
|
1052
|
+
server.registerService('VerifyService', {
|
|
1053
|
+
/**
|
|
1054
|
+
* VerifyFeature - 验证功能实现是否符合规格
|
|
1055
|
+
* 对应 RESTful: POST /api/v1/verify
|
|
1056
|
+
*/
|
|
1057
|
+
VerifyFeature: wrapHandler(
|
|
1058
|
+
handleVerifyFeature,
|
|
1059
|
+
'VerifyService',
|
|
1060
|
+
'VerifyFeature',
|
|
1061
|
+
VerifySchemas.VerifyRequest,
|
|
1062
|
+
VerifySchemas.VerifyResponse
|
|
1063
|
+
)
|
|
1064
|
+
}, ServiceDefinitions.VerifyService);
|
|
1065
|
+
|
|
1066
|
+
// ==================== 注册 ReportService ====================
|
|
1067
|
+
server.registerService('ReportService', {
|
|
1068
|
+
/**
|
|
1069
|
+
* GenerateReport - 生成项目分析报告
|
|
1070
|
+
* 对应 RESTful: POST /api/v1/report
|
|
1071
|
+
*/
|
|
1072
|
+
GenerateReport: wrapHandler(
|
|
1073
|
+
handleGenerateReport,
|
|
1074
|
+
'ReportService',
|
|
1075
|
+
'GenerateReport',
|
|
1076
|
+
ReportSchemas.ReportRequest,
|
|
1077
|
+
ReportSchemas.ReportResponse
|
|
1078
|
+
)
|
|
1079
|
+
}, ServiceDefinitions.ReportService);
|
|
1080
|
+
|
|
1081
|
+
// ==================== 注册 SkillService ====================
|
|
1082
|
+
server.registerService('SkillService', {
|
|
1083
|
+
/**
|
|
1084
|
+
* ListSkills - 列出所有可用技能
|
|
1085
|
+
* 对应 RESTful: GET /api/v1/skills
|
|
1086
|
+
*/
|
|
1087
|
+
ListSkills: wrapHandler(
|
|
1088
|
+
handleListSkills,
|
|
1089
|
+
'SkillService',
|
|
1090
|
+
'ListSkills',
|
|
1091
|
+
SkillSchemas.ListSkillsRequest,
|
|
1092
|
+
SkillSchemas.ListSkillsResponse
|
|
1093
|
+
),
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* GetSkillInfo - 获取指定技能的详细信息
|
|
1097
|
+
* 对应 RESTful: GET /api/v1/skills/:category/:name
|
|
1098
|
+
*/
|
|
1099
|
+
GetSkillInfo: wrapHandler(
|
|
1100
|
+
handleGetSkillInfo,
|
|
1101
|
+
'SkillService',
|
|
1102
|
+
'GetSkillInfo',
|
|
1103
|
+
SkillSchemas.GetSkillInfoRequest,
|
|
1104
|
+
SkillSchemas.SkillInfoResponse
|
|
1105
|
+
)
|
|
1106
|
+
}, ServiceDefinitions.SkillService);
|
|
1107
|
+
|
|
1108
|
+
console.log('[gRPC] All services registered successfully');
|
|
1109
|
+
console.log(`[gRPC] Total services: ${server._getServiceList().length}`);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// ==================== 导出 ====================
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* 获取全局性能指标
|
|
1116
|
+
* @returns {Object} 所有已收集的性能指标
|
|
1117
|
+
*/
|
|
1118
|
+
export function getGlobalMetrics() {
|
|
1119
|
+
return globalMetrics.getAllMetrics();
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
* 重置全局性能指标(用于测试或监控重置)
|
|
1124
|
+
*/
|
|
1125
|
+
export function resetGlobalMetrics() {
|
|
1126
|
+
globalMetrics.calls.clear();
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
export default {
|
|
1130
|
+
registerGrpcRoutes,
|
|
1131
|
+
mapErrorToGrpcStatus,
|
|
1132
|
+
getGlobalMetrics,
|
|
1133
|
+
resetGlobalMetrics
|
|
1134
|
+
};
|