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,285 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { log, findProjectRoot, loadConfig } from '../../lib/utils/logger.js';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const CONFIG_ROOT = path.join(__dirname, '..', '..', 'config');
|
|
10
|
+
|
|
11
|
+
const LINTER_REGISTRY = {
|
|
12
|
+
code: {
|
|
13
|
+
name: 'Code Linter',
|
|
14
|
+
description: '代码质量检查 (Checkstyle/PMD/ESLint/Ruff)',
|
|
15
|
+
runners: ['checkstyle', 'pmd', 'eslint', 'ruff']
|
|
16
|
+
},
|
|
17
|
+
prd: {
|
|
18
|
+
name: 'PRD Linter',
|
|
19
|
+
description: 'PRD文档规范检查',
|
|
20
|
+
runners: ['prd']
|
|
21
|
+
},
|
|
22
|
+
sql: {
|
|
23
|
+
name: 'SQL Linter',
|
|
24
|
+
description: 'SQL脚本规范检查 (SQLFluff)',
|
|
25
|
+
runners: ['sql']
|
|
26
|
+
},
|
|
27
|
+
activiti: {
|
|
28
|
+
name: 'Activiti Linter',
|
|
29
|
+
description: 'BPMN流程文件规范检查',
|
|
30
|
+
runners: ['activiti']
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default async function runLinters(options = {}) {
|
|
35
|
+
const startTime = Date.now();
|
|
36
|
+
const types = options.type || ['code'];
|
|
37
|
+
const incremental = options.incremental || false;
|
|
38
|
+
const outputFormat = options.output || 'text';
|
|
39
|
+
|
|
40
|
+
console.log(chalk.blue.bold('\n🔍 PDD Linter - 代码与文档质量检查\n'));
|
|
41
|
+
|
|
42
|
+
const projectRoot = await findProjectRoot(process.cwd());
|
|
43
|
+
log('info', `项目根目录: ${projectRoot}`);
|
|
44
|
+
const config = await loadConfig(projectRoot);
|
|
45
|
+
|
|
46
|
+
if (!config.linter.enabled) {
|
|
47
|
+
console.log(chalk.yellow('⚠️ Linter未启用,请检查 .pdd/config.yaml'));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const results = [];
|
|
52
|
+
|
|
53
|
+
for (const type of types) {
|
|
54
|
+
const linterDef = LINTER_REGISTRY[type];
|
|
55
|
+
if (!linterDef) {
|
|
56
|
+
log('warn', `未知Linter类型: ${type}, 跳过`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(chalk.cyan(`\n━━━ ${linterDef.name} ━━━`));
|
|
61
|
+
console.log(chalk.gray(` ${linterDef.description}\n`));
|
|
62
|
+
|
|
63
|
+
for (const runner of linterDef.runners) {
|
|
64
|
+
try {
|
|
65
|
+
const result = await executeLinter(runner, projectRoot, config, incremental);
|
|
66
|
+
results.push(result);
|
|
67
|
+
printResult(result, outputFormat);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
results.push({
|
|
70
|
+
runner,
|
|
71
|
+
type,
|
|
72
|
+
success: false,
|
|
73
|
+
error: e.message,
|
|
74
|
+
duration: 0
|
|
75
|
+
});
|
|
76
|
+
log('error', `${runner} 执行失败`, e.message);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await generateSummary(results, startTime, outputFormat);
|
|
82
|
+
|
|
83
|
+
return results;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function executeLinter(runner, projectRoot, config, incremental) {
|
|
87
|
+
const start = Date.now();
|
|
88
|
+
|
|
89
|
+
switch (runner) {
|
|
90
|
+
case 'checkstyle':
|
|
91
|
+
return runCheckstyle(projectRoot, incremental);
|
|
92
|
+
case 'pmd':
|
|
93
|
+
return runPMD(projectRoot, incremental);
|
|
94
|
+
case 'eslint':
|
|
95
|
+
return runESLint(projectRoot, incremental);
|
|
96
|
+
case 'ruff':
|
|
97
|
+
return runRuff(projectRoot, incremental);
|
|
98
|
+
case 'prd':
|
|
99
|
+
return runPRDLinter(projectRoot);
|
|
100
|
+
case 'sql':
|
|
101
|
+
return runSQLLinter(projectRoot);
|
|
102
|
+
case 'activiti':
|
|
103
|
+
return runActivitiLinter(projectRoot);
|
|
104
|
+
default:
|
|
105
|
+
throw new Error(`未知linter runner: ${runner}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function runCheckstyle(projectRoot, incremental) {
|
|
110
|
+
const configPath = path.join(CONFIG_ROOT, 'checkstyle.xml');
|
|
111
|
+
if (!fs.existsSync(configPath)) {
|
|
112
|
+
return { runner: 'checkstyle', success: false, error: '配置文件不存在', duration: 0 };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let cmd = 'java -jar checkstyle-' + getVersion('checkstyle') + '-all.jar -c "' + configPath + '"';
|
|
116
|
+
if (incremental) cmd += ' -r "$(git diff --name-only --diff-filter=ACMR | grep .java$)"';
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const output = execSync(cmd, { cwd: projectRoot, encoding: 'utf-8', timeout: 60000 });
|
|
120
|
+
return parseLinterOutput('checkstyle', output, Date.now());
|
|
121
|
+
} catch (e) {
|
|
122
|
+
return parseLinterOutput('checkstyle', e.stdout || e.stderr || '', Date.now(), e.status !== 0);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function runPMD(projectRoot, incremental) {
|
|
127
|
+
const configPath = path.join(CONFIG_ROOT, 'pmd.xml');
|
|
128
|
+
if (!fs.existsSync(configPath)) {
|
|
129
|
+
return { runner: 'pmd', success: false, error: '配置文件不存在', duration: 0 };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const output = execSync(
|
|
134
|
+
`run.sh pmd -d . -rulesets "${configPath}" -f json -language java`,
|
|
135
|
+
{ cwd: projectRoot, encoding: 'utf-8', timeout: 120000 }
|
|
136
|
+
);
|
|
137
|
+
return parseLinterOutput('pmd', output, Date.now());
|
|
138
|
+
} catch (e) {
|
|
139
|
+
return parseLinterOutput('pmd', e.stdout || '', Date.now(), e.status !== 0);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function runESLint(projectRoot, incremental) {
|
|
144
|
+
const configPath = path.join(CONFIG_ROOT, 'eslint.config.js');
|
|
145
|
+
|
|
146
|
+
let cmd = `npx eslint --config "${configPath}" "**/*.{js,jsx,vue,ts,tsx}"`;
|
|
147
|
+
if (incremental) cmd += ' $(git diff --name-only --diff-filter=ACMR | grep -E "\\.(js|jsx|vue|ts|tsx)$")';
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const output = execSync(cmd, { cwd: projectRoot, encoding: 'utf-8', timeout: 60000 });
|
|
151
|
+
return parseLinterOutput('eslint', output, Date.now());
|
|
152
|
+
} catch (e) {
|
|
153
|
+
return parseLinterOutput('eslint', e.stdout || '', Date.now(), e.status !== 0);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function runRuff(projectRoot, incremental) {
|
|
158
|
+
const configPath = path.join(CONFIG_ROOT, 'ruff.toml');
|
|
159
|
+
|
|
160
|
+
let cmd = `ruff check --config "${configPath}" .`;
|
|
161
|
+
if (incremental) cmd += ' $(git diff --name-only --diff-filter=ACMR | grep "\\.py$")';
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const output = execSync(cmd, { cwd: projectRoot, encoding: 'utf-8', timeout: 60000 });
|
|
165
|
+
return parseLinterOutput('ruff', output, Date.now());
|
|
166
|
+
} catch (e) {
|
|
167
|
+
return parseLinterOutput('ruff', e.stdout || '', Date.now(), e.status !== 0);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function runPRDLinter(projectRoot) {
|
|
172
|
+
const { runPRDChecks } = await import('./prd-linter.js');
|
|
173
|
+
return runPRDChecks(projectRoot);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function runSQLLinter(projectRoot) {
|
|
177
|
+
const { runSQLChecks } = await import('./sql-linter.js');
|
|
178
|
+
return runSQLChecks(projectRoot);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function runActivitiLinter(projectRoot) {
|
|
182
|
+
const { runActivitiChecks } = await import('./activiti-linter.js');
|
|
183
|
+
return runActivitiChecks(projectRoot);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function parseLinterOutput(runner, rawOutput, startTime, hasErrors = false) {
|
|
187
|
+
const issues = [];
|
|
188
|
+
const lines = rawOutput.split('\n').filter(l => l.trim());
|
|
189
|
+
|
|
190
|
+
for (const line of lines) {
|
|
191
|
+
if (line.includes('[ERROR]') || line.includes('[WARN]')) {
|
|
192
|
+
issues.push({
|
|
193
|
+
severity: line.includes('[ERROR]') ? 'error' : 'warning',
|
|
194
|
+
message: line.replace(/^\[.*?\]\s*/, '').trim()
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
runner,
|
|
201
|
+
success: !hasErrors && issues.filter(i => i.severity === 'error').length === 0,
|
|
202
|
+
issueCount: issues.length,
|
|
203
|
+
errorCount: issues.filter(i => i.severity === 'error').length,
|
|
204
|
+
warningCount: issues.filter(i => i.severity === 'warning').length,
|
|
205
|
+
issues: issues.slice(0, 50),
|
|
206
|
+
duration: Date.now() - startTime
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function printResult(result, format) {
|
|
211
|
+
if (format === 'json') return;
|
|
212
|
+
|
|
213
|
+
const icon = result.success ? chalk.green('✓') : chalk.red('✗');
|
|
214
|
+
const timeStr = `${result.duration}ms`;
|
|
215
|
+
|
|
216
|
+
console.log(` ${icon} ${chalk.white(result.runner)} (${timeStr})`);
|
|
217
|
+
|
|
218
|
+
if (result.issueCount > 0) {
|
|
219
|
+
console.log(` ${chalk.yellow(`${result.errorCount} 错误, ${result.warningCount} 警告`)}`);
|
|
220
|
+
|
|
221
|
+
for (const issue of (result.issues || []).slice(0, 10)) {
|
|
222
|
+
const color = issue.severity === 'error' ? chalk.red : chalk.yellow;
|
|
223
|
+
console.log(` ${color('•')} ${issue.message}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (result.issueCount > 10) {
|
|
227
|
+
console.log(` ... 还有 ${result.issueCount - 10} 个问题`);
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
console.log(chalk.gray(' 无问题发现'));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function generateSummary(results, startTime, format) {
|
|
235
|
+
const totalDuration = Date.now() - startTime;
|
|
236
|
+
const totalErrors = results.reduce((s, r) => s + (r.errorCount || 0), 0);
|
|
237
|
+
const totalWarnings = results.reduce((s, r) => s + (r.warningCount || 0), 0);
|
|
238
|
+
const allPassed = results.every(r => r.success);
|
|
239
|
+
|
|
240
|
+
if (format === 'json') {
|
|
241
|
+
console.log(JSON.stringify({ summary: { totalErrors, totalWarnings, allPassed, duration: totalDuration }, results }, null, 2));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
console.log(chalk.bold('\n━━━ 检查摘要 ━━━'));
|
|
246
|
+
console.log(allPassed ? chalk.green(' ✅ 所有检查通过') : chalk.red(` ❌ 发现 ${totalErrors} 个错误`));
|
|
247
|
+
console.log(chalk.gray(` ⏱ 总耗时: ${totalDuration}ms`));
|
|
248
|
+
|
|
249
|
+
const reportDir = path.join(await findProjectRoot(process.cwd()), '.pdd', 'cache', 'reports');
|
|
250
|
+
await fs.ensureDir(reportDir);
|
|
251
|
+
const reportPath = path.join(reportDir, `linter-report-${Date.now()}.md`);
|
|
252
|
+
await generateMarkdownReport(reportPath, results, totalErrors, totalWarnings, totalDuration);
|
|
253
|
+
console.log(chalk.gray(` 📄 报告已保存: ${reportPath}`));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function generateMarkdownReport(reportPath, results, errors, warnings, duration) {
|
|
257
|
+
const lines = [
|
|
258
|
+
'# PDD Linter 报告',
|
|
259
|
+
'',
|
|
260
|
+
`- 生成时间: ${new Date().toISOString()}`,
|
|
261
|
+
`- 总错误数: ${errors}`,
|
|
262
|
+
`- 总警告数: ${warnings}`,
|
|
263
|
+
`- 耗时: ${duration}ms`,
|
|
264
|
+
''
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
for (const r of results) {
|
|
268
|
+
lines.push(`## ${r.runner}`, '');
|
|
269
|
+
if (r.issues && r.issues.length > 0) {
|
|
270
|
+
for (const issue of r.issues) {
|
|
271
|
+
lines.push(`- [${issue.severity.toUpperCase()}] ${issue.message}`);
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
lines.push('- 无问题');
|
|
275
|
+
}
|
|
276
|
+
lines.push('');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
await fs.writeFile(reportPath, lines.join('\n'), 'utf-8');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function getVersion(tool) {
|
|
283
|
+
const versions = { checkstyle: '10.12.0', pmd: '6.55.0' };
|
|
284
|
+
return versions[tool] || 'latest';
|
|
285
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const CONFIG_PATH = path.join(__dirname, '..', '..', 'config', 'sqlfluff.cfg');
|
|
9
|
+
|
|
10
|
+
export async function runSQLChecks(projectRoot) {
|
|
11
|
+
const start = Date.now();
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
14
|
+
return {
|
|
15
|
+
runner: 'sql-linter',
|
|
16
|
+
success: false,
|
|
17
|
+
error: 'SQLFluff配置文件不存在',
|
|
18
|
+
duration: Date.now() - start
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const sqlFiles = findSQLFiles(projectRoot);
|
|
23
|
+
|
|
24
|
+
if (sqlFiles.length === 0) {
|
|
25
|
+
return {
|
|
26
|
+
runner: 'sql-linter',
|
|
27
|
+
success: true,
|
|
28
|
+
issueCount: 0,
|
|
29
|
+
errorCount: 0,
|
|
30
|
+
warningCount: 0,
|
|
31
|
+
issues: [],
|
|
32
|
+
message: '未找到SQL文件',
|
|
33
|
+
duration: Date.now() - start
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const fileList = sqlFiles.map(f => `"${f}"`).join(' ');
|
|
39
|
+
const cmd = `sqlfluff lint --config "${CONFIG_PATH}" ${fileList} --format json`;
|
|
40
|
+
const output = execSync(cmd, { cwd: projectRoot, encoding: 'utf-8', timeout: 120000 });
|
|
41
|
+
|
|
42
|
+
return parseSQLFluffOutput(output, start, sqlFiles.length);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
const stdout = e.stdout || '';
|
|
45
|
+
const stderr = e.stderr || '';
|
|
46
|
+
|
|
47
|
+
if (stdout.startsWith('{') || stdout.startsWith('[')) {
|
|
48
|
+
return parseSQLFluffOutput(stdout, start, sqlFiles.length);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const fallbackOutput = runFallbackChecks(sqlFiles, projectRoot);
|
|
52
|
+
return {
|
|
53
|
+
runner: 'sql-linter',
|
|
54
|
+
success: fallbackOutput.errors === 0,
|
|
55
|
+
issueCount: fallbackOutput.warnings + fallbackOutput.errors,
|
|
56
|
+
errorCount: fallbackOutput.errors,
|
|
57
|
+
warningCount: fallbackOutput.warnings,
|
|
58
|
+
issues: fallbackOutput.issues,
|
|
59
|
+
filesChecked: sqlFiles.length,
|
|
60
|
+
duration: Date.now() - start
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function findSQLFiles(root) {
|
|
66
|
+
const exts = ['.sql', '.hql', '.q'];
|
|
67
|
+
const found = [];
|
|
68
|
+
|
|
69
|
+
for (const ext of exts) {
|
|
70
|
+
try {
|
|
71
|
+
const files = fs.globSync(`**/*${ext}`, {
|
|
72
|
+
cwd: root,
|
|
73
|
+
ignore: ['**/node_modules/**', '**/.git/**', '**/.pdd/**', '**/vendor/**', '**/migrations/__pycache__/**']
|
|
74
|
+
});
|
|
75
|
+
for (const f of files) found.push(path.join(root, f));
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return [...new Set(found)];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseSQLFluffOutput(rawOutput, startTime, fileCount) {
|
|
83
|
+
try {
|
|
84
|
+
const data = JSON.parse(rawOutput);
|
|
85
|
+
const violations = Array.isArray(data) ? data : (data.violations || []);
|
|
86
|
+
const issues = violations.map(v => ({
|
|
87
|
+
ruleId: v.rule || v.code || 'unknown',
|
|
88
|
+
severity: v.level === 'VIOLATION' ? 'error' : 'warn',
|
|
89
|
+
message: v.description || v.message || '',
|
|
90
|
+
file: v.path || v.file_path || '',
|
|
91
|
+
line: v.line_no || v.line || 0,
|
|
92
|
+
col: v.line_pos || v.col || 0
|
|
93
|
+
}));
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
runner: 'sql-linter',
|
|
97
|
+
success: issues.filter(i => i.severity === 'error').length === 0,
|
|
98
|
+
issueCount: issues.length,
|
|
99
|
+
errorCount: issues.filter(i => i.severity === 'error').length,
|
|
100
|
+
warningCount: issues.filter(i => i.severity === 'warn').length,
|
|
101
|
+
issues: issues.slice(0, 100),
|
|
102
|
+
filesChecked: fileCount,
|
|
103
|
+
duration: Date.now() - startTime
|
|
104
|
+
};
|
|
105
|
+
} catch {
|
|
106
|
+
return {
|
|
107
|
+
runner: 'sql-linter',
|
|
108
|
+
success: false,
|
|
109
|
+
error: '无法解析SQLFluff输出',
|
|
110
|
+
duration: Date.now() - startTime
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function runFallbackChecks(sqlFiles, root) {
|
|
116
|
+
const issues = [];
|
|
117
|
+
let errors = 0;
|
|
118
|
+
let warnings = 0;
|
|
119
|
+
|
|
120
|
+
const dangerousPatterns = [
|
|
121
|
+
{ pattern: /\bSELECT\s+\*\s+FROM/i, severity: 'warn', rule: 'no-select-star', message: '避免使用 SELECT *,明确指定所需列' },
|
|
122
|
+
{ pattern: /\bDROP\s+(TABLE|DATABASE)\b/i, severity: 'error', rule: 'dangerous-drop', message: '检测到危险操作 DROP,请确认是否必要' },
|
|
123
|
+
{ pattern: /\bDELETE\s+FROM\b(?!\s+WHERE)/i, severity: 'error', rule: 'delete-without-where', message: 'DELETE语句缺少WHERE条件' },
|
|
124
|
+
{ pattern: /\bUPDATE\b.*\bSET\b(?!(.*WHERE|\s*$))/is, severity: 'error', rule: 'update-without-where', message: 'UPDATE语句缺少WHERE条件' },
|
|
125
|
+
{ pattern: /--\s*TODO|FIXME|HACK|XXX/i, severity: 'info', rule: 'todo-comments', message: 'SQL中存在待办注释' },
|
|
126
|
+
{ pattern: /\bINSERT\s+INTO\b.*VALUES\s*\([^)]*\)\s*;\s*$/im, severity: 'warn', rule: 'explicit-columns', message: 'INSERT语句建议显式指定列名' },
|
|
127
|
+
{ pattern: /\b(?:UNION\s+ALL)\b/i, severity: 'info', rule: 'union-all-check', message: '使用 UNION ALL 时确认是否需要去重' },
|
|
128
|
+
{ pattern: /;\s*$/m, severity: 'info', rule: 'trailing-semicolon', message: '建议每条SQL语句以分号结尾' }
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
for (const filePath of sqlFiles) {
|
|
132
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
133
|
+
const lines = content.split('\n');
|
|
134
|
+
|
|
135
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
136
|
+
const line = lines[idx];
|
|
137
|
+
for (const check of dangerousPatterns) {
|
|
138
|
+
if (check.pattern.test(line)) {
|
|
139
|
+
if (check.severity === 'error') errors++;
|
|
140
|
+
else if (check.severity === 'warn') warnings++;
|
|
141
|
+
|
|
142
|
+
issues.push({
|
|
143
|
+
ruleId: check.rule,
|
|
144
|
+
severity: check.severity,
|
|
145
|
+
message: check.message,
|
|
146
|
+
file: filePath,
|
|
147
|
+
line: idx + 1
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (content.length > 5000) {
|
|
154
|
+
warnings++;
|
|
155
|
+
issues.push({
|
|
156
|
+
ruleId: 'long-file',
|
|
157
|
+
severity: 'warn',
|
|
158
|
+
message: `SQL文件过大(${Math.round(content.length / 1024)}KB),建议拆分`,
|
|
159
|
+
file: filePath,
|
|
160
|
+
line: 0
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { errors, warnings, issues: issues.slice(0, 100) };
|
|
166
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const SKILLS_ROOT = path.join(__dirname, '..', 'skills');
|
|
8
|
+
|
|
9
|
+
export async function analyzeTokenEfficiency(category) {
|
|
10
|
+
const categories = category ? [category] : ['core', 'entropy', 'expert', 'openspec', 'pr'];
|
|
11
|
+
const results = [];
|
|
12
|
+
|
|
13
|
+
for (const cat of categories) {
|
|
14
|
+
const catPath = path.join(SKILLS_ROOT, cat);
|
|
15
|
+
if (!fs.existsSync(catPath)) continue;
|
|
16
|
+
|
|
17
|
+
const entries = fs.readdirSync(catPath).filter(f =>
|
|
18
|
+
fs.statSync(path.join(catPath, f)).isDirectory()
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
const skillPath = path.join(catPath, entry);
|
|
23
|
+
const analysis = analyzeSkillTokens(entry, skillPath);
|
|
24
|
+
results.push({ ...analysis, category: cat });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
printTokenReport(results);
|
|
29
|
+
return results;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function estimateTokenCount(text) {
|
|
33
|
+
if (!text) return 0;
|
|
34
|
+
const chineseChars = (text.match(/[\u4e00-\u9fff]/g) || []).length;
|
|
35
|
+
const englishWords = text
|
|
36
|
+
.replace(/[\u4e00-\u9fff]/g, ' ')
|
|
37
|
+
.split(/\s+/)
|
|
38
|
+
.filter(w => w.length > 0).length;
|
|
39
|
+
return Math.ceil(chineseChars * 1.5 + englishWords * 1.3);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function analyzeSkillTokens(name, skillPath) {
|
|
43
|
+
const skillMd = path.join(skillPath, 'SKILL.md');
|
|
44
|
+
const metaJson = path.join(skillPath, '_meta.json');
|
|
45
|
+
|
|
46
|
+
if (!fs.existsSync(skillMd)) {
|
|
47
|
+
return { name, error: 'SKILL.md not found', tokens: 0, grade: 'F' };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const content = fs.readFileSync(skillMd, 'utf-8');
|
|
51
|
+
const totalTokens = estimateTokenCount(content);
|
|
52
|
+
const fileSizeKB = Buffer.byteLength(content, 'utf-8') / 1024;
|
|
53
|
+
|
|
54
|
+
const lines = content.split('\n');
|
|
55
|
+
const lineCount = lines.length;
|
|
56
|
+
const maxSectionLength = getMaxSectionLength(lines);
|
|
57
|
+
|
|
58
|
+
const sections = countSections(content);
|
|
59
|
+
const codeBlocks = (content.match(/```[\s\S]*?```/g) || []).length;
|
|
60
|
+
const tables = (content.match(/\|.+\|/g) || []).length / 2;
|
|
61
|
+
|
|
62
|
+
const descMatch = content.match(/^description:\s*(.+)$/m);
|
|
63
|
+
const descLen = descMatch ? descMatch[1].trim().length : 0;
|
|
64
|
+
|
|
65
|
+
const hasLayering = /layer/i.test(content) || /##\s*\d+/i.test(content);
|
|
66
|
+
const hasBehaviorShaping = /iron\s*law|red\s*flags|rationalization/i.test(content);
|
|
67
|
+
|
|
68
|
+
let score = 100;
|
|
69
|
+
if (totalTokens > 8000) score -= Math.min(30, (totalTokens - 8000) / 200);
|
|
70
|
+
if (totalTokens > 5000) score -= Math.min(15, (totalTokens - 5000) / 400);
|
|
71
|
+
if (descLen > 200) score -= Math.min(10, (descLen - 200) / 20);
|
|
72
|
+
if (maxSectionLength > 150) score -= Math.min(10, (maxSectionLength - 150) / 10);
|
|
73
|
+
if (!hasLayering) score -= 5;
|
|
74
|
+
if (!hasBehaviorShaping) score -= 10;
|
|
75
|
+
if (lineCount > 300) score -= Math.min(10, (lineCount - 300) / 30);
|
|
76
|
+
|
|
77
|
+
score = Math.max(0, Math.min(100, score));
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
name,
|
|
81
|
+
tokens: totalTokens,
|
|
82
|
+
fileSizeKB: Math.round(fileSizeKB * 10) / 10,
|
|
83
|
+
lineCount,
|
|
84
|
+
sections,
|
|
85
|
+
codeBlocks,
|
|
86
|
+
tables: Math.round(tables),
|
|
87
|
+
descLength: descLen,
|
|
88
|
+
maxSectionLines: maxSectionLength,
|
|
89
|
+
hasLayering,
|
|
90
|
+
hasBehaviorShaping,
|
|
91
|
+
score,
|
|
92
|
+
grade: score >= 80 ? 'A' : score >= 60 ? 'B' : score >= 40 ? 'C' : 'D',
|
|
93
|
+
suggestions: generateSuggestions(totalTokens, descLen, maxSectionLength, hasLayering, hasBehaviorShaping)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getMaxSectionLength(lines) {
|
|
98
|
+
let maxLength = 0;
|
|
99
|
+
let currentLength = 0;
|
|
100
|
+
for (const line of lines) {
|
|
101
|
+
if (/^#{1,3}\s/.test(line)) {
|
|
102
|
+
maxLength = Math.max(maxLength, currentLength);
|
|
103
|
+
currentLength = 0;
|
|
104
|
+
}
|
|
105
|
+
currentLength++;
|
|
106
|
+
}
|
|
107
|
+
return Math.max(maxLength, currentLength);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function countSections(content) {
|
|
111
|
+
return (content.match(/^#{1,3}\s+.+$/gm) || []).length;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function generateSuggestions(tokens, descLen, maxSec, hasLayering, hasBS) {
|
|
115
|
+
const suggestions = [];
|
|
116
|
+
if (tokens > 8000) suggestions.push('⚠️ 文件过大,建议拆分为 Layer 1/2/3 渐进式加载');
|
|
117
|
+
if (tokens > 5000) suggestions.push('📝 文件较大,考虑将参考文档移至 references/ 目录');
|
|
118
|
+
if (descLen > 200) suggestions.push('✂️ description 过长,建议精简至200字以内');
|
|
119
|
+
if (maxSec > 150) suggestions.push('📐 存在超长章节(>' + maxSec + '行),建议拆分');
|
|
120
|
+
if (!hasLayering) suggestions.push('📚 缺少分层结构,建议添加 Layer 1/2/3 标记');
|
|
121
|
+
if (!hasBS) suggestions.push('🛡️ 缺少行为塑造章节(Iron Law/Red Flags)');
|
|
122
|
+
return suggestions;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function printTokenReport(results) {
|
|
126
|
+
const avgTokens = results.reduce((s, r) => s + (r.tokens || 0), 0) / results.length;
|
|
127
|
+
const avgScore = results.reduce((s, r) => s + r.score, 0) / results.length;
|
|
128
|
+
const gradeDist = { A: 0, B: 0, C: 0, D: 0, F: 0 };
|
|
129
|
+
results.forEach(r => gradeDist[r.grade]++);
|
|
130
|
+
|
|
131
|
+
console.log('\n📊 Token 效率分析报告\n');
|
|
132
|
+
console.log(` 总技能数: ${results.length}`);
|
|
133
|
+
console.log(` 平均Token数: ${Math.round(avgTokens)}`);
|
|
134
|
+
console.log(` 平均效率分: ${avgScore.toFixed(1)}/100`);
|
|
135
|
+
console.log(` 等级分布: A:${gradeDist.A} B:${gradeDist.B} C:${gradeDist.C} D:${gradeDist.D} F:${gradeDist.F}\n`);
|
|
136
|
+
|
|
137
|
+
console.log(' ┌────────────────────────┬────────┬───────┬──────┬────────┐');
|
|
138
|
+
console.log(' │ 技能名称 │ Tokens │ 大小 │ 等级 │ 建议 │');
|
|
139
|
+
console.log(' ├────────────────────────┼────────┼───────┼──────┼────────┤');
|
|
140
|
+
|
|
141
|
+
const sorted = [...results].sort((a, b) => a.tokens - b.tokens);
|
|
142
|
+
for (const r of sorted) {
|
|
143
|
+
const icon = r.grade === 'A' ? '🟢' : r.grade === 'B' ? '🟡' : r.grade === 'C' ? '🟠' : r.grade === 'D' ? '🔴' : '⬛';
|
|
144
|
+
const name = r.name.padEnd(24).substring(0, 24);
|
|
145
|
+
const size = (r.fileSizeKB + 'KB').padStart(6);
|
|
146
|
+
const suggestCount = r.suggestions ? r.suggestions.length : 0;
|
|
147
|
+
console.log(` │ ${icon} ${name} │ ${(r.tokens || 0).toString().padStart(6)} │ ${size} │ ${r.grade} │ ${suggestCount.toString().padStart(4)} │`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log(' └────────────────────────┴────────┴───────┴──────┴────────┘');
|
|
151
|
+
|
|
152
|
+
const needOptimization = results.filter(r => r.score < 60);
|
|
153
|
+
if (needOptimization.length > 0) {
|
|
154
|
+
console.log('\n ⚠️ 需要优化的技能:');
|
|
155
|
+
for (const r of needOptimization) {
|
|
156
|
+
console.log(` ${r.name} (${r.tokens} tokens, ${r.grade}级)`);
|
|
157
|
+
for (const s of (r.suggestions || [])) {
|
|
158
|
+
console.log(` ${s}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|