deeper-cli 1.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 +254 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +12067 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +415 -0
- package/dist/index.js +1599 -0
- package/dist/index.js.map +1 -0
- package/docs/superpowers/plans/2026-05-14-deepercode-implementation.md +24 -0
- package/docs/superpowers/plans/2026-05-14-deepercode-plan.md +1248 -0
- package/docs/superpowers/specs/2026-05-14-deepercode-design.md +560 -0
- package/package.json +60 -0
- package/src/cli/bootstrap.ts +69 -0
- package/src/cli/chat-repl.ts +932 -0
- package/src/cli/commands/chat.ts +39 -0
- package/src/cli/commands/chat.tsx +39 -0
- package/src/cli/commands/config.ts +133 -0
- package/src/cli/commands/mcp.ts +172 -0
- package/src/cli/commands/run.ts +147 -0
- package/src/cli/commands/skill.ts +152 -0
- package/src/cli/index.ts +184 -0
- package/src/core/bugscan.ts +145 -0
- package/src/core/config.ts +285 -0
- package/src/core/constants.ts +49 -0
- package/src/core/eventbus.ts +202 -0
- package/src/core/logger.ts +109 -0
- package/src/core/storage.ts +96 -0
- package/src/index.ts +26 -0
- package/src/mcp/ConfigLoader.ts +74 -0
- package/src/mcp/MCPClient.ts +326 -0
- package/src/mcp/ResourceAdapter.ts +58 -0
- package/src/mcp/SSETransport.ts +133 -0
- package/src/mcp/StdioTransport.ts +116 -0
- package/src/mcp/ToolAdapter.ts +71 -0
- package/src/mcp/types.ts +58 -0
- package/src/memory/xmemory.ts +275 -0
- package/src/model/DeepSeekClient.ts +292 -0
- package/src/model/MessageBuilder.ts +155 -0
- package/src/model/RetryManager.ts +82 -0
- package/src/model/StreamHandler.ts +158 -0
- package/src/model/types.ts +86 -0
- package/src/skills/SkillCreator.ts +153 -0
- package/src/skills/SkillEngine.ts +158 -0
- package/src/skills/SkillExecutor.ts +107 -0
- package/src/skills/SkillLoader.ts +182 -0
- package/src/skills/SkillRegistry.ts +73 -0
- package/src/skills/SkillTrigger.ts +82 -0
- package/src/skills/types.ts +28 -0
- package/src/tools/ToolExecutor.ts +103 -0
- package/src/tools/ToolRegistry.ts +71 -0
- package/src/tools/ToolValidator.ts +103 -0
- package/src/tools/builtin/ai/context_summarize.ts +76 -0
- package/src/tools/builtin/ai/memory_store.ts +86 -0
- package/src/tools/builtin/ai/prompt_template.ts +71 -0
- package/src/tools/builtin/ai/skill_create.ts +53 -0
- package/src/tools/builtin/ai/subagent.ts +39 -0
- package/src/tools/builtin/ai/todo_manager.ts +157 -0
- package/src/tools/builtin/ai/token_count.ts +196 -0
- package/src/tools/builtin/ai/tool_create.ts +52 -0
- package/src/tools/builtin/code/analyze_deps.ts +72 -0
- package/src/tools/builtin/code/bug_scan.ts +80 -0
- package/src/tools/builtin/code/code_metrics.ts +111 -0
- package/src/tools/builtin/code/extract_function.ts +86 -0
- package/src/tools/builtin/code/format_code.ts +57 -0
- package/src/tools/builtin/code/generate_code.ts +75 -0
- package/src/tools/builtin/code/import_organizer.ts +82 -0
- package/src/tools/builtin/code/lint_code.ts +48 -0
- package/src/tools/builtin/code/parse_ast.ts +86 -0
- package/src/tools/builtin/code/refactor_code.ts +63 -0
- package/src/tools/builtin/code/type_check.ts +48 -0
- package/src/tools/builtin/data/chart_generate.ts +62 -0
- package/src/tools/builtin/data/csv_parse.ts +56 -0
- package/src/tools/builtin/data/data_diff.ts +79 -0
- package/src/tools/builtin/data/data_transform.ts +74 -0
- package/src/tools/builtin/data/data_validate.ts +75 -0
- package/src/tools/builtin/data/json_parse.ts +71 -0
- package/src/tools/builtin/data/template_render.ts +58 -0
- package/src/tools/builtin/data/toml_parse.ts +42 -0
- package/src/tools/builtin/data/xml_parse.ts +79 -0
- package/src/tools/builtin/data/yaml_parse.ts +42 -0
- package/src/tools/builtin/database/db_backup.ts +53 -0
- package/src/tools/builtin/database/db_restore.ts +51 -0
- package/src/tools/builtin/database/db_schema.ts +66 -0
- package/src/tools/builtin/database/nosql_query.ts +50 -0
- package/src/tools/builtin/database/orm_generate.ts +66 -0
- package/src/tools/builtin/database/redis_command.ts +46 -0
- package/src/tools/builtin/database/sql_migrate.ts +55 -0
- package/src/tools/builtin/database/sql_query.ts +60 -0
- package/src/tools/builtin/filesystem/batch_read.ts +56 -0
- package/src/tools/builtin/filesystem/batch_write.ts +67 -0
- package/src/tools/builtin/filesystem/copy_file.ts +36 -0
- package/src/tools/builtin/filesystem/create_dir.ts +30 -0
- package/src/tools/builtin/filesystem/delete_file.ts +30 -0
- package/src/tools/builtin/filesystem/diff_files.ts +47 -0
- package/src/tools/builtin/filesystem/edit_file.ts +47 -0
- package/src/tools/builtin/filesystem/file_info.ts +52 -0
- package/src/tools/builtin/filesystem/glob_find.ts +44 -0
- package/src/tools/builtin/filesystem/list_dir.ts +51 -0
- package/src/tools/builtin/filesystem/merge_files.ts +44 -0
- package/src/tools/builtin/filesystem/move_file.ts +37 -0
- package/src/tools/builtin/filesystem/read_file.ts +55 -0
- package/src/tools/builtin/filesystem/watch_file.ts +33 -0
- package/src/tools/builtin/filesystem/write_file.ts +45 -0
- package/src/tools/builtin/index.ts +244 -0
- package/src/tools/builtin/network/api_call.ts +79 -0
- package/src/tools/builtin/network/browser_action.ts +54 -0
- package/src/tools/builtin/network/check_url.ts +59 -0
- package/src/tools/builtin/network/download_file.ts +64 -0
- package/src/tools/builtin/network/graphql_query.ts +46 -0
- package/src/tools/builtin/network/http_request.ts +61 -0
- package/src/tools/builtin/network/parse_html.ts +101 -0
- package/src/tools/builtin/network/proxy_request.ts +53 -0
- package/src/tools/builtin/network/screenshot_page.ts +58 -0
- package/src/tools/builtin/network/web_fetch.ts +70 -0
- package/src/tools/builtin/network/web_search.ts +128 -0
- package/src/tools/builtin/network/websocket_connect.ts +70 -0
- package/src/tools/builtin/project/build_project.ts +68 -0
- package/src/tools/builtin/project/config_manage.ts +99 -0
- package/src/tools/builtin/project/coverage_report.ts +59 -0
- package/src/tools/builtin/project/docker_manage.ts +90 -0
- package/src/tools/builtin/project/env_manage.ts +88 -0
- package/src/tools/builtin/project/npm_manage.ts +71 -0
- package/src/tools/builtin/project/project_init.ts +59 -0
- package/src/tools/builtin/project/run_test.ts +74 -0
- package/src/tools/builtin/search/codebase_search.ts +76 -0
- package/src/tools/builtin/search/find_definition.ts +84 -0
- package/src/tools/builtin/search/find_references.ts +75 -0
- package/src/tools/builtin/search/fuzzy_find.ts +75 -0
- package/src/tools/builtin/search/grep_search.ts +90 -0
- package/src/tools/builtin/search/regex_find.ts +91 -0
- package/src/tools/builtin/search/search_docs.ts +51 -0
- package/src/tools/builtin/search/search_package.ts +50 -0
- package/src/tools/builtin/search/symbol_search.ts +82 -0
- package/src/tools/builtin/search/text_search.ts +63 -0
- package/src/tools/builtin/security/decrypt_file.ts +54 -0
- package/src/tools/builtin/security/encrypt_file.ts +52 -0
- package/src/tools/builtin/security/hash_generate.ts +48 -0
- package/src/tools/builtin/security/jwt_decode.ts +53 -0
- package/src/tools/builtin/security/secret_scan.ts +82 -0
- package/src/tools/builtin/security/vulnerability_check.ts +71 -0
- package/src/tools/builtin/shell/background_terminal.ts +38 -0
- package/src/tools/builtin/shell/check_status.ts +48 -0
- package/src/tools/builtin/shell/interactive_terminal.ts +31 -0
- package/src/tools/builtin/shell/kill_terminal.ts +29 -0
- package/src/tools/builtin/shell/list_terminals.ts +61 -0
- package/src/tools/builtin/shell/pipe_commands.ts +55 -0
- package/src/tools/builtin/shell/process-pool.ts +150 -0
- package/src/tools/builtin/shell/run_async.ts +73 -0
- package/src/tools/builtin/shell/run_command.ts +60 -0
- package/src/tools/builtin/shell/send_ctrl_keys.ts +43 -0
- package/src/tools/builtin/shell/send_keys.ts +36 -0
- package/src/tools/builtin/shell/send_text.ts +35 -0
- package/src/tools/builtin/shell/shell_script.ts +65 -0
- package/src/tools/builtin/shell/stop_command.ts +40 -0
- package/src/tools/builtin/shell/terminal_resize.ts +31 -0
- package/src/tools/builtin/shell/terminal_screenshot.ts +28 -0
- package/src/tools/builtin/system/log_viewer.ts +89 -0
- package/src/tools/builtin/system/notify_user.ts +55 -0
- package/src/tools/builtin/system/process_list.ts +66 -0
- package/src/tools/builtin/system/resource_monitor.ts +66 -0
- package/src/tools/builtin/system/system_info.ts +41 -0
- package/src/tools/tool-types.ts +97 -0
- package/src/ui/AgentTree.tsx +98 -0
- package/src/ui/App.tsx +46 -0
- package/src/ui/ChatView.tsx +278 -0
- package/src/ui/ConfirmDialog.tsx +68 -0
- package/src/ui/DiffView.tsx +64 -0
- package/src/ui/FilePreview.tsx +59 -0
- package/src/ui/InputBox.tsx +267 -0
- package/src/ui/MessageBubble.tsx +30 -0
- package/src/ui/Spinner.tsx +35 -0
- package/src/ui/StatusBar.tsx +41 -0
- package/src/ui/ToolCallCard.tsx +73 -0
- package/src/ui/ansi.ts +50 -0
- package/src/ui/markdown.ts +238 -0
- package/src/ui/themes/dark.ts +4 -0
- package/src/ui/themes/default.ts +25 -0
- package/src/ui/themes/light.ts +14 -0
- package/tests/unit/BuiltinTools.test.ts +129 -0
- package/tests/unit/BuiltinToolsIntegration.test.ts +111 -0
- package/tests/unit/FilesystemTools.test.ts +211 -0
- package/tests/unit/SkillLoader.test.ts +141 -0
- package/tests/unit/SkillRegistry.test.ts +113 -0
- package/tests/unit/ToolExecutor.test.ts +160 -0
- package/tests/unit/ToolRegistry.test.ts +103 -0
- package/tests/unit/ToolValidator.test.ts +137 -0
- package/tsconfig.json +28 -0
- package/tsup.config.ts +17 -0
- package/vitest.config.ts +20 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { Tool } from '../../tool-types.js';
|
|
2
|
+
|
|
3
|
+
export interface TodoItem {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
status: 'pending' | 'in_progress' | 'done' | 'cancelled';
|
|
7
|
+
subtasks?: { title: string; status: 'pending' | 'done' }[];
|
|
8
|
+
plan?: string;
|
|
9
|
+
agent?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const todos: Map<string, TodoItem[]> = new Map();
|
|
13
|
+
const MAX_TOP = 12;
|
|
14
|
+
|
|
15
|
+
function uid() { return `t${Date.now().toString(36)}${Math.random().toString(36).slice(2,5)}`; }
|
|
16
|
+
|
|
17
|
+
function formatList(items: TodoItem[], indent = 0): string {
|
|
18
|
+
const lines: string[] = [];
|
|
19
|
+
for (const t of items) {
|
|
20
|
+
const pf = ' '.repeat(indent);
|
|
21
|
+
const s = t.status === 'done' ? '✓' : t.status === 'in_progress' ? '◉' : t.status === 'cancelled' ? '✗' : '○';
|
|
22
|
+
lines.push(`${pf}${s} ${t.title.slice(0, 72)}`);
|
|
23
|
+
if (t.subtasks) for (const st of t.subtasks) {
|
|
24
|
+
const ss = st.status === 'done' ? '✓' : '○';
|
|
25
|
+
lines.push(`${pf} ${ss} ${st.title.slice(0, 60)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return lines.join('\n');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function formatChange(changes: Array<{ oldStatus: string; newStatus: string; title: string }>): string {
|
|
32
|
+
if (changes.length === 0) return '无变更';
|
|
33
|
+
const sMap: Record<string, string> = { pending: '○', in_progress: '◉', done: '✓', cancelled: '✗' };
|
|
34
|
+
return changes.map(c => `${sMap[c.oldStatus]} → ${sMap[c.newStatus]} ${c.title.slice(0, 60)}`).join('\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function findAndUpdate(items: TodoItem[], idOrTitle: string, status: string): Array<{ oldStatus: string; newStatus: string; title: string }> {
|
|
38
|
+
const changes: Array<{ oldStatus: string; newStatus: string; title: string }> = [];
|
|
39
|
+
for (const t of items) {
|
|
40
|
+
if (t.id === idOrTitle || t.title === idOrTitle) {
|
|
41
|
+
if (t.status !== status) { changes.push({ oldStatus: t.status, newStatus: status, title: t.title }); t.status = status as any; }
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
if (t.subtasks) for (const st of t.subtasks) {
|
|
45
|
+
if (st.title === idOrTitle || st.title.includes(idOrTitle)) {
|
|
46
|
+
if (st.status !== status) { changes.push({ oldStatus: st.status, newStatus: status, title: st.title }); st.status = status as any; }
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return changes;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function countStats(items: TodoItem[]) {
|
|
55
|
+
let total = 0, done = 0;
|
|
56
|
+
for (const t of items) {
|
|
57
|
+
total++; if (t.status === 'done') done++;
|
|
58
|
+
if (t.subtasks) for (const st of t.subtasks) { total++; if (st.status === 'done') done++; }
|
|
59
|
+
}
|
|
60
|
+
return { total, done };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const todo_manager: Tool = {
|
|
64
|
+
name: 'todo_manager',
|
|
65
|
+
description: '管理任务面板。复杂任务前创建 items 列表(≤8项),过程中用 update 改变状态(title 或 task_id 定位)。',
|
|
66
|
+
category: 'ai',
|
|
67
|
+
parameters: {
|
|
68
|
+
type: 'object',
|
|
69
|
+
properties: {
|
|
70
|
+
action: { type: 'string', enum: ['create', 'update', 'list', 'clear'], description: '操作' },
|
|
71
|
+
task_id: { type: 'string', description: 'update 时用 id 定位任务' },
|
|
72
|
+
title: { type: 'string', description: 'update 时用标题定位任务(唯一匹配)' },
|
|
73
|
+
status: { type: 'string', enum: ['pending', 'in_progress', 'done', 'cancelled'], description: '更新目标状态' },
|
|
74
|
+
items: { type: 'array', items: { type: 'object', properties: { title: { type: 'string' }, plan: { type: 'string' }, agent: { type: 'string' }, subtasks: { type: 'array', items: { type: 'object', properties: { title: { type: 'string' } } } } } }, description: 'create 时传入 ≤8 项' },
|
|
75
|
+
},
|
|
76
|
+
required: ['action'],
|
|
77
|
+
},
|
|
78
|
+
dangerous: false,
|
|
79
|
+
requiresApproval: false,
|
|
80
|
+
async execute(params) {
|
|
81
|
+
const action = params.action as string;
|
|
82
|
+
const sessionId = 'default';
|
|
83
|
+
|
|
84
|
+
if (action === 'list') {
|
|
85
|
+
const items = todos.get(sessionId) || [];
|
|
86
|
+
if (items.length === 0) return { success: true, output: '任务列表为空' };
|
|
87
|
+
const { total, done } = countStats(items);
|
|
88
|
+
return { success: true, output: `📋 ${done}/${total}\n${formatList(items)}`, metadata: { total, done } };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (action === 'clear') {
|
|
92
|
+
todos.set(sessionId, []);
|
|
93
|
+
return { success: true, output: '已清空' };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (action === 'create') {
|
|
97
|
+
const items = (params.items as any[]) || [];
|
|
98
|
+
if (items.length === 0 && params.title) {
|
|
99
|
+
todos.set(sessionId, [{ id: uid(), title: params.title as string, status: 'pending', plan: (params as any).plan }]);
|
|
100
|
+
return { success: true, output: `📋 1 项\n${formatList(todos.get(sessionId)!)}`, metadata: { total: 1, done: 0 } };
|
|
101
|
+
}
|
|
102
|
+
const top = items.slice(0, MAX_TOP);
|
|
103
|
+
const mapped: TodoItem[] = top.map((t: any) => ({
|
|
104
|
+
id: uid(), title: t.title, status: 'pending',
|
|
105
|
+
plan: t.plan, agent: t.agent,
|
|
106
|
+
subtasks: t.subtasks ? t.subtasks.slice(0, 6).map((st: any) => ({ title: st.title, status: 'pending' as const })) : undefined,
|
|
107
|
+
}));
|
|
108
|
+
todos.set(sessionId, mapped);
|
|
109
|
+
const { total, done } = countStats(mapped);
|
|
110
|
+
return { success: true, output: `📋 ${done}/${total}\n${formatList(mapped)}`, metadata: { total, done, topLevel: mapped.length } };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (action === 'update') {
|
|
114
|
+
const list = todos.get(sessionId) || [];
|
|
115
|
+
const tid = params.task_id as string;
|
|
116
|
+
const title = params.title as string;
|
|
117
|
+
const status = (params.status as string) || 'in_progress';
|
|
118
|
+
let changes: Array<{ oldStatus: string; newStatus: string; title: string }> = [];
|
|
119
|
+
if (tid) changes = findAndUpdate(list, tid, status);
|
|
120
|
+
else if (title) changes = findAndUpdate(list, title, status);
|
|
121
|
+
if (changes.length === 0 && (tid || title)) {
|
|
122
|
+
return { success: false, error: `未找到: ${tid || title}`, output: '' };
|
|
123
|
+
}
|
|
124
|
+
todos.set(sessionId, list);
|
|
125
|
+
if (changes.length === 0) {
|
|
126
|
+
const { done, total } = countStats(list);
|
|
127
|
+
return { success: true, output: `📋 ${done}/${total}\n${formatList(list)}` };
|
|
128
|
+
}
|
|
129
|
+
const { done, total } = countStats(list);
|
|
130
|
+
return { success: true, output: `${formatChange(changes)}\n(${done}/${total})` };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { success: false, error: '未知 action: ' + action, output: '' };
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export function getTodos(): TodoItem[] {
|
|
138
|
+
return todos.get('default') || [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function todoSummary(maxItems = 8): string {
|
|
142
|
+
const items = getTodos();
|
|
143
|
+
if (items.length === 0) return '';
|
|
144
|
+
const pending = items.filter(t => t.status !== 'done' && t.status !== 'cancelled');
|
|
145
|
+
if (pending.length === 0) return '';
|
|
146
|
+
let s = `[任务 ${pending.length}/${items.length}]\n`;
|
|
147
|
+
for (const t of pending.slice(0, maxItems)) {
|
|
148
|
+
const sym = t.status === 'in_progress' ? '⏳' : '⬚';
|
|
149
|
+
s += `${sym} ${t.title.slice(0, 50)}`;
|
|
150
|
+
if (t.subtasks) {
|
|
151
|
+
const subDone = t.subtasks.filter(st => st.status === 'done').length;
|
|
152
|
+
s += ` (${subDone}/${t.subtasks.length})`;
|
|
153
|
+
}
|
|
154
|
+
s += '\n';
|
|
155
|
+
}
|
|
156
|
+
return s;
|
|
157
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type { Tool } from '../../tool-types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DeepSeek-V3/V4 token estimator.
|
|
5
|
+
*
|
|
6
|
+
* DeepSeek uses a BPE tokenizer. Approximation:
|
|
7
|
+
* - CJK chars (中文/日文/韩文): ~1.6 tokens each
|
|
8
|
+
* - English words (common): ~1.3 tokens each
|
|
9
|
+
* - Code / symbol-heavy: ~1 token per 2.5 chars
|
|
10
|
+
* - Digits: ~1 token per 3 chars
|
|
11
|
+
* - Newlines: ~1 token each
|
|
12
|
+
* - Leading whitespace: ~1 token
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const CJK_RE = /[\u4e00-\u9fff\u3400-\u4dbf\u{20000}-\u{2a6df}\u{2a700}-\u{2b73f}\u{2b740}-\u{2b81f}\u{2b820}-\u{2ceaf}\u{f900}-\u{faff}\u{2f800}-\u{2fa1f}]/gu;
|
|
16
|
+
const HANGUL_RE = /[\uac00-\ud7af]/g;
|
|
17
|
+
const JAPANESE_RE = /[\u3040-\u309f\u30a0-\u30ff]/g;
|
|
18
|
+
const CJK_PUNCT_RE = /[\u3000-\u303f\uff00-\uffef]/g;
|
|
19
|
+
|
|
20
|
+
export function estimateTokens(text: string): number {
|
|
21
|
+
if (!text) return 0;
|
|
22
|
+
|
|
23
|
+
let tokens = 0;
|
|
24
|
+
let remaining = text;
|
|
25
|
+
|
|
26
|
+
// Count CJK characters (Chinese)
|
|
27
|
+
const cjk = remaining.match(CJK_RE);
|
|
28
|
+
if (cjk) tokens += Math.ceil(cjk.length * 1.6);
|
|
29
|
+
|
|
30
|
+
// Count Korean Hangul
|
|
31
|
+
const hangul = remaining.match(HANGUL_RE);
|
|
32
|
+
if (hangul) tokens += Math.ceil(hangul.length * 1.5);
|
|
33
|
+
|
|
34
|
+
// Count Japanese kana
|
|
35
|
+
const jp = remaining.match(JAPANESE_RE);
|
|
36
|
+
if (jp) tokens += Math.ceil(jp.length * 1.4);
|
|
37
|
+
|
|
38
|
+
// Count CJK punctuation
|
|
39
|
+
const cjkp = remaining.match(CJK_PUNCT_RE);
|
|
40
|
+
if (cjkp) tokens += cjkp.length;
|
|
41
|
+
|
|
42
|
+
// Remove all CJK-related chars to process ASCII portion
|
|
43
|
+
let ascii = remaining;
|
|
44
|
+
ascii = ascii.replace(CJK_RE, '');
|
|
45
|
+
ascii = ascii.replace(HANGUL_RE, '');
|
|
46
|
+
ascii = ascii.replace(JAPANESE_RE, '');
|
|
47
|
+
ascii = ascii.replace(CJK_PUNCT_RE, '');
|
|
48
|
+
|
|
49
|
+
// Count newlines
|
|
50
|
+
const nls = ascii.split('\n').length - 1;
|
|
51
|
+
tokens += nls;
|
|
52
|
+
|
|
53
|
+
// Count each line's content separately (better word-boundary handling)
|
|
54
|
+
const lines = ascii.split('\n');
|
|
55
|
+
for (const line of lines) {
|
|
56
|
+
if (!line.trim()) { tokens += 1; continue; }
|
|
57
|
+
|
|
58
|
+
// Split into segments: code/symbols vs words
|
|
59
|
+
// Code-like: contains lots of symbols
|
|
60
|
+
const symbols = (line.match(/[{}()\[\];:'"`,.<>\/\\|&^~`@#$%*+=\-!?]/g) || []).length;
|
|
61
|
+
const digits = (line.match(/\d+/g) || []).join('').length;
|
|
62
|
+
const words = line.match(/[a-zA-Z_]+/g) || [];
|
|
63
|
+
|
|
64
|
+
const wordTokens = words.reduce((s, w) => {
|
|
65
|
+
if (w.length <= 4) return s + 1;
|
|
66
|
+
return s + Math.ceil(w.length / 3.5);
|
|
67
|
+
}, 0);
|
|
68
|
+
|
|
69
|
+
// Whitespace and punctuation within words are usually part of the token
|
|
70
|
+
const digitTokens = Math.ceil(digits / 3);
|
|
71
|
+
const symbolTokens = Math.ceil(symbols / 1.8);
|
|
72
|
+
|
|
73
|
+
tokens += wordTokens + digitTokens + symbolTokens;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Floor + 10% safety margin for BPE overhead
|
|
77
|
+
return Math.max(1, Math.floor(tokens * 1.1));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Estimate tokens for an array of chat messages (DeepSeek API format).
|
|
82
|
+
* Includes counting tool definitions.
|
|
83
|
+
*/
|
|
84
|
+
export function estimateMessageTokens(
|
|
85
|
+
messages: Array<Record<string, unknown>>,
|
|
86
|
+
tools?: Array<{ type: string; function: { name: string; description: string; parameters: Record<string, unknown> } }>,
|
|
87
|
+
): number {
|
|
88
|
+
let total = 0;
|
|
89
|
+
|
|
90
|
+
// System overhead (~8 tokens per DeepSeek's API format)
|
|
91
|
+
total += 8;
|
|
92
|
+
|
|
93
|
+
for (const msg of messages) {
|
|
94
|
+
// Role overhead
|
|
95
|
+
total += 3;
|
|
96
|
+
|
|
97
|
+
const content = msg.content as string | null | undefined;
|
|
98
|
+
if (content) total += estimateTokens(content);
|
|
99
|
+
|
|
100
|
+
const toolCalls = msg.tool_calls as Array<{ function?: { name?: string; arguments?: string } }> | undefined;
|
|
101
|
+
if (toolCalls) {
|
|
102
|
+
for (const tc of toolCalls) {
|
|
103
|
+
total += estimateTokens(tc.function?.name || '');
|
|
104
|
+
total += estimateTokens(tc.function?.arguments || '');
|
|
105
|
+
total += 4; // JSON structure overhead per tool call
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const toolCallId = msg.tool_call_id as string | undefined;
|
|
110
|
+
if (toolCallId) total += estimateTokens(toolCallId) + 2;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Tool definitions
|
|
114
|
+
if (tools) {
|
|
115
|
+
for (const t of tools) {
|
|
116
|
+
total += estimateTokens(t.function.name);
|
|
117
|
+
total += estimateTokens(t.function.description);
|
|
118
|
+
total += estimateTokens(JSON.stringify(t.function.parameters));
|
|
119
|
+
total += 4; // JSON structure overhead per tool def
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return total;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const token_count: Tool = {
|
|
127
|
+
name: 'token_count',
|
|
128
|
+
description: '计算文本/文件/对话的 Token 数量 (DeepSeek-V3/V4 BPE 估算)。支持统计中文、英文、代码的 Token 消耗。',
|
|
129
|
+
category: 'ai',
|
|
130
|
+
parameters: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
properties: {
|
|
133
|
+
text: { type: 'string', description: '要计数的文本' },
|
|
134
|
+
file_path: { type: 'string', description: '文件绝对路径' },
|
|
135
|
+
messages: { type: 'string', description: 'JSON 格式的对话消息列表 (可选)' },
|
|
136
|
+
},
|
|
137
|
+
required: [],
|
|
138
|
+
},
|
|
139
|
+
dangerous: false,
|
|
140
|
+
requiresApproval: false,
|
|
141
|
+
async execute(params) {
|
|
142
|
+
try {
|
|
143
|
+
const text = params.text as string | undefined;
|
|
144
|
+
const filePath = params.file_path as string | undefined;
|
|
145
|
+
const messagesJson = params.messages as string | undefined;
|
|
146
|
+
|
|
147
|
+
let content = '';
|
|
148
|
+
if (filePath) {
|
|
149
|
+
const { readFileSync, existsSync } = await import('node:fs');
|
|
150
|
+
const { resolve } = await import('node:path');
|
|
151
|
+
const abs = resolve(filePath);
|
|
152
|
+
if (!existsSync(abs)) return { success: false, error: `文件不存在: ${abs}`, output: '' };
|
|
153
|
+
content = readFileSync(abs, 'utf-8');
|
|
154
|
+
} else if (text) {
|
|
155
|
+
content = text;
|
|
156
|
+
} else if (messagesJson) {
|
|
157
|
+
content = messagesJson;
|
|
158
|
+
} else {
|
|
159
|
+
return { success: false, error: '请提供 text、file_path 或 messages 参数', output: '' };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const chars = content.length;
|
|
163
|
+
const words = content.split(/\s+/).filter(w => w.length > 0).length;
|
|
164
|
+
const lines = content.split('\n').length;
|
|
165
|
+
const hasCJK = CJK_RE.test(content);
|
|
166
|
+
|
|
167
|
+
let tokens = 0;
|
|
168
|
+
let msgsEstimate = 0;
|
|
169
|
+
|
|
170
|
+
if (messagesJson) {
|
|
171
|
+
try {
|
|
172
|
+
const msgs = JSON.parse(messagesJson) as Array<Record<string, unknown>>;
|
|
173
|
+
msgsEstimate = estimateMessageTokens(msgs);
|
|
174
|
+
tokens = msgsEstimate;
|
|
175
|
+
} catch {
|
|
176
|
+
tokens = estimateTokens(content);
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
tokens = estimateTokens(content);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const output = [
|
|
183
|
+
`Token 估算 (DeepSeek BPE):`,
|
|
184
|
+
`总 Token: ${tokens.toLocaleString()}`,
|
|
185
|
+
`字符数: ${chars.toLocaleString()}`,
|
|
186
|
+
`单词数: ${words.toLocaleString()} ${hasCJK ? '(含中文)' : ''}`,
|
|
187
|
+
`行数: ${lines.toLocaleString()}`,
|
|
188
|
+
`比率: ${(chars / tokens).toFixed(1)} 字符/Token`,
|
|
189
|
+
].join('\n');
|
|
190
|
+
|
|
191
|
+
return { success: true, output, metadata: { tokens, chars, words, lines, messagesTokens: msgsEstimate } };
|
|
192
|
+
} catch (err: unknown) {
|
|
193
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Tool } from '../../tool-types.js';
|
|
2
|
+
|
|
3
|
+
export const tool_create: Tool = {
|
|
4
|
+
name: 'tool_create',
|
|
5
|
+
description: 'AI 动态创建新工具(通过注册中心)',
|
|
6
|
+
category: 'ai',
|
|
7
|
+
parameters: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
name: { type: 'string', description: '工具名称' },
|
|
11
|
+
description: { type: 'string', description: '工具描述' },
|
|
12
|
+
command: { type: 'string', description: '工具执行的命令' },
|
|
13
|
+
category: { type: 'string', description: '工具分类', enum: ['filesystem', 'search', 'shell', 'network', 'code', 'database', 'data', 'security', 'project', 'ai', 'system'] },
|
|
14
|
+
parameters: { type: 'object', description: '参数 JSON Schema' },
|
|
15
|
+
},
|
|
16
|
+
required: ['name', 'command'],
|
|
17
|
+
},
|
|
18
|
+
dangerous: false,
|
|
19
|
+
requiresApproval: true,
|
|
20
|
+
async execute(params) {
|
|
21
|
+
try {
|
|
22
|
+
const name = params.name as string;
|
|
23
|
+
const description = (params.description as string) ?? `动态工具: ${name}`;
|
|
24
|
+
const command = params.command as string;
|
|
25
|
+
const category = (params.category as string) ?? 'ai';
|
|
26
|
+
const parameters = params.parameters as Record<string, unknown> | undefined;
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
success: true,
|
|
30
|
+
output: [
|
|
31
|
+
`动态工具创建请求: ${name}`,
|
|
32
|
+
`分类: ${category}`,
|
|
33
|
+
`命令: ${command}`,
|
|
34
|
+
'',
|
|
35
|
+
'工具可以通过 run_command 或 DynamicTool.createFromCommand 注册。',
|
|
36
|
+
'使用示例:',
|
|
37
|
+
'```js',
|
|
38
|
+
`const dynamicTool = dynamicToolRegistry.createFromCommand(`,
|
|
39
|
+
` '${name}',`,
|
|
40
|
+
` '${description}',`,
|
|
41
|
+
` '${command}',`,
|
|
42
|
+
` '${category}'`,
|
|
43
|
+
');',
|
|
44
|
+
'```',
|
|
45
|
+
].join('\n'),
|
|
46
|
+
metadata: { name, category, command },
|
|
47
|
+
};
|
|
48
|
+
} catch (err: unknown) {
|
|
49
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import type { Tool } from '../../tool-types.js';
|
|
4
|
+
|
|
5
|
+
export const analyze_deps: Tool = {
|
|
6
|
+
name: 'analyze_deps',
|
|
7
|
+
description: '分析文件依赖关系',
|
|
8
|
+
category: 'code',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
file_path: { type: 'string', description: '入口文件路径' },
|
|
13
|
+
dir_path: { type: 'string', description: '分析目录' },
|
|
14
|
+
depth: { type: 'number', description: '分析深度' },
|
|
15
|
+
max_files: { type: 'number', description: '最大分析文件数' },
|
|
16
|
+
},
|
|
17
|
+
required: [],
|
|
18
|
+
},
|
|
19
|
+
dangerous: false,
|
|
20
|
+
requiresApproval: false,
|
|
21
|
+
async execute(params) {
|
|
22
|
+
try {
|
|
23
|
+
const filePath = params.file_path as string | undefined;
|
|
24
|
+
const dirPath = params.dir_path as string | undefined;
|
|
25
|
+
const depth = (params.depth as number) ?? 1;
|
|
26
|
+
const maxFiles = (params.max_files as number) ?? 50;
|
|
27
|
+
|
|
28
|
+
const fg = await import('fast-glob');
|
|
29
|
+
let files: string[];
|
|
30
|
+
if (filePath) {
|
|
31
|
+
files = [resolve(filePath)];
|
|
32
|
+
} else {
|
|
33
|
+
const target = dirPath ? resolve(dirPath) : process.cwd();
|
|
34
|
+
files = await fg.default('**/*.{ts,js,tsx,jsx}', {
|
|
35
|
+
cwd: target,
|
|
36
|
+
absolute: true,
|
|
37
|
+
ignore: ['node_modules/**', '.git/**', 'dist/**'],
|
|
38
|
+
onlyFiles: true,
|
|
39
|
+
});
|
|
40
|
+
files = files.slice(0, maxFiles);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const importRegex = /from\s+['"]([^'"]+)['"]|require\(['"]([^'"]+)['"]\)|import\(['"]([^'"]+)['"]\)/g;
|
|
44
|
+
const results: { file: string; imports: string[] }[] = [];
|
|
45
|
+
|
|
46
|
+
for (const fp of files) {
|
|
47
|
+
try {
|
|
48
|
+
const content = readFileSync(fp, 'utf-8');
|
|
49
|
+
const deps: string[] = [];
|
|
50
|
+
let match;
|
|
51
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
52
|
+
const dep = match[1] || match[2] || match[3];
|
|
53
|
+
deps.push(dep);
|
|
54
|
+
}
|
|
55
|
+
results.push({ file: fp, imports: [...new Set(deps)] });
|
|
56
|
+
} catch { /* skip */ }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const output = results.map(r =>
|
|
60
|
+
`--- ${r.file} ---\n ${r.imports.length > 0 ? r.imports.join('\n ') : '(无外部依赖)'}`
|
|
61
|
+
).join('\n\n');
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
success: true,
|
|
65
|
+
output: output || '未找到依赖关系',
|
|
66
|
+
metadata: { files: results.length },
|
|
67
|
+
};
|
|
68
|
+
} catch (err: unknown) {
|
|
69
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
|
+
import { isAbsolute, join } from 'node:path';
|
|
3
|
+
import fg from 'fast-glob';
|
|
4
|
+
import { analyzeFile, summarizeBugs } from '../../../core/bugscan.js';
|
|
5
|
+
import type { Tool } from '../../tool-types.js';
|
|
6
|
+
import type { BugReport } from '../../../core/bugscan.js';
|
|
7
|
+
|
|
8
|
+
const MAX_FILES = 50;
|
|
9
|
+
const EXTENSIONS = '{ts,js,py,java,go,rs,css,html}';
|
|
10
|
+
const SEVERITY_ORDER: Record<string, number> = { error: 0, warning: 1, info: 2 };
|
|
11
|
+
|
|
12
|
+
export const bug_scan: Tool = {
|
|
13
|
+
name: 'bug_scan',
|
|
14
|
+
description: 'Scan source files for bugs, anti-patterns, and code quality issues. Supports TS/JS/Python/Java/Go/Rust/CSS/HTML.',
|
|
15
|
+
category: 'code',
|
|
16
|
+
parameters: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
path: { type: 'string', description: 'File or directory path to scan' },
|
|
20
|
+
recursive: { type: 'boolean', description: 'Scan directory recursively' },
|
|
21
|
+
severity: { type: 'string', description: 'Minimum severity level to report', enum: ['error', 'warning', 'info'] },
|
|
22
|
+
},
|
|
23
|
+
required: ['path'],
|
|
24
|
+
},
|
|
25
|
+
dangerous: false,
|
|
26
|
+
requiresApproval: false,
|
|
27
|
+
async execute(params) {
|
|
28
|
+
try {
|
|
29
|
+
const target = params.path as string;
|
|
30
|
+
const recursive = (params.recursive as boolean) ?? false;
|
|
31
|
+
const minSeverity = (params.severity as string) ?? 'warning';
|
|
32
|
+
|
|
33
|
+
const absPath = isAbsolute(target) ? target : join(process.cwd(), target);
|
|
34
|
+
|
|
35
|
+
let isFile = false;
|
|
36
|
+
try {
|
|
37
|
+
isFile = statSync(absPath).isFile();
|
|
38
|
+
} catch {
|
|
39
|
+
return { success: false, error: `路径不存在: ${absPath}`, output: '' };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let files: string[];
|
|
43
|
+
if (isFile) {
|
|
44
|
+
files = [absPath];
|
|
45
|
+
} else {
|
|
46
|
+
const pattern = recursive
|
|
47
|
+
? `**/*.${EXTENSIONS}`
|
|
48
|
+
: `*.${EXTENSIONS}`;
|
|
49
|
+
files = await fg(pattern, {
|
|
50
|
+
cwd: absPath,
|
|
51
|
+
absolute: true,
|
|
52
|
+
ignore: ['node_modules/**', '.git/**', 'dist/**'],
|
|
53
|
+
onlyFiles: true,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
files = files.slice(0, MAX_FILES);
|
|
58
|
+
|
|
59
|
+
const allBugs: BugReport[] = [];
|
|
60
|
+
for (const fp of files) {
|
|
61
|
+
try {
|
|
62
|
+
const bugs = analyzeFile(fp);
|
|
63
|
+
allBugs.push(...bugs);
|
|
64
|
+
} catch { /* skip files that fail */ }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const minOrder = SEVERITY_ORDER[minSeverity] ?? 1;
|
|
68
|
+
const filtered = allBugs.filter(b => (SEVERITY_ORDER[b.severity] ?? 2) <= minOrder);
|
|
69
|
+
|
|
70
|
+
if (filtered.length === 0) {
|
|
71
|
+
return { success: true, output: 'No issues found', metadata: { filesScanned: files.length, totalBugs: allBugs.length } };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const output = summarizeBugs(filtered);
|
|
75
|
+
return { success: true, output, metadata: { filesScanned: files.length, totalBugs: allBugs.length, filteredBugs: filtered.length } };
|
|
76
|
+
} catch (err: unknown) {
|
|
77
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { resolve, relative } from 'node:path';
|
|
3
|
+
import type { Tool } from '../../tool-types.js';
|
|
4
|
+
|
|
5
|
+
export const code_metrics: Tool = {
|
|
6
|
+
name: 'code_metrics',
|
|
7
|
+
description: '计算代码度量指标(行数、复杂度等)',
|
|
8
|
+
category: 'code',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
dir_path: { type: 'string', description: '目录路径' },
|
|
13
|
+
file_path: { type: 'string', description: '文件路径' },
|
|
14
|
+
glob: { type: 'string', description: '文件过滤' },
|
|
15
|
+
},
|
|
16
|
+
required: [],
|
|
17
|
+
},
|
|
18
|
+
dangerous: false,
|
|
19
|
+
requiresApproval: false,
|
|
20
|
+
async execute(params) {
|
|
21
|
+
try {
|
|
22
|
+
const dirPath = params.dir_path as string | undefined;
|
|
23
|
+
const filePath = params.file_path as string | undefined;
|
|
24
|
+
const glob = (params.glob as string) ?? '**/*.{ts,js,tsx,jsx,py}';
|
|
25
|
+
|
|
26
|
+
let target: string;
|
|
27
|
+
let files: string[];
|
|
28
|
+
|
|
29
|
+
if (filePath) {
|
|
30
|
+
target = resolve(filePath);
|
|
31
|
+
files = [target];
|
|
32
|
+
} else {
|
|
33
|
+
target = dirPath ? resolve(dirPath) : process.cwd();
|
|
34
|
+
const fg = await import('fast-glob');
|
|
35
|
+
files = await fg.default(glob, {
|
|
36
|
+
cwd: target,
|
|
37
|
+
absolute: true,
|
|
38
|
+
ignore: ['node_modules/**', '.git/**', 'dist/**'],
|
|
39
|
+
onlyFiles: true,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let totalLines = 0;
|
|
44
|
+
let totalCode = 0;
|
|
45
|
+
let totalComments = 0;
|
|
46
|
+
let totalBlank = 0;
|
|
47
|
+
let totalFiles = 0;
|
|
48
|
+
const fileMetrics: Record<string, unknown>[] = [];
|
|
49
|
+
|
|
50
|
+
for (const fp of files.slice(0, 200)) {
|
|
51
|
+
try {
|
|
52
|
+
const stat = statSync(fp);
|
|
53
|
+
if (stat.size > 1048576) continue;
|
|
54
|
+
const content = readFileSync(fp, 'utf-8');
|
|
55
|
+
const lines = content.split('\n');
|
|
56
|
+
let code = 0, comments = 0, blank = 0;
|
|
57
|
+
|
|
58
|
+
let inBlockComment = false;
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
if (line.trim() === '') { blank++; continue; }
|
|
61
|
+
if (inBlockComment) {
|
|
62
|
+
comments++;
|
|
63
|
+
if (line.includes('*/')) inBlockComment = false;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const trimmed = line.trim();
|
|
67
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('#')) { comments++; continue; }
|
|
68
|
+
if (trimmed.startsWith('/*')) {
|
|
69
|
+
comments++;
|
|
70
|
+
if (!trimmed.includes('*/')) inBlockComment = true;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
code++;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
totalLines += lines.length;
|
|
77
|
+
totalCode += code;
|
|
78
|
+
totalComments += comments;
|
|
79
|
+
totalBlank += blank;
|
|
80
|
+
totalFiles++;
|
|
81
|
+
|
|
82
|
+
fileMetrics.push({
|
|
83
|
+
file: relative(target, fp),
|
|
84
|
+
lines: lines.length,
|
|
85
|
+
code,
|
|
86
|
+
comments,
|
|
87
|
+
blank,
|
|
88
|
+
});
|
|
89
|
+
} catch { /* skip */ }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const summary = [
|
|
93
|
+
`代码度量报告 (${totalFiles} 文件)`,
|
|
94
|
+
`总行数: ${totalLines}`,
|
|
95
|
+
`代码行: ${totalCode}`,
|
|
96
|
+
`注释行: ${totalComments}`,
|
|
97
|
+
`空行: ${totalBlank}`,
|
|
98
|
+
`注释率: ${totalLines > 0 ? (totalComments / totalLines * 100).toFixed(1) : 0}%`,
|
|
99
|
+
'',
|
|
100
|
+
'前10文件:',
|
|
101
|
+
...fileMetrics.slice(0, 10).map(
|
|
102
|
+
m => ` ${m.file}: ${m.lines} 行 (代码:${m.code} 注释:${m.comments} 空:${m.blank})`
|
|
103
|
+
),
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
return { success: true, output: summary.join('\n'), metadata: { totalFiles, totalLines, totalCode } };
|
|
107
|
+
} catch (err: unknown) {
|
|
108
|
+
return { success: false, error: (err as Error).message, output: '' };
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
};
|