skill-any-code 1.0.0 → 1.0.1
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/dist/adapters/command.schemas.js +18 -0
- package/dist/application/analysis.app.service.js +264 -0
- package/dist/application/bootstrap.js +21 -0
- package/dist/application/services/llm.analysis.service.js +170 -0
- package/dist/common/config.js +213 -0
- package/dist/common/constants.js +11 -0
- package/dist/common/errors.js +37 -0
- package/dist/common/logger.js +77 -0
- package/dist/common/types.js +2 -0
- package/dist/common/ui.js +201 -0
- package/dist/common/utils.js +117 -0
- package/dist/domain/index.js +17 -0
- package/dist/domain/interfaces.js +2 -0
- package/dist/domain/services/analysis.service.js +696 -0
- package/dist/domain/services/incremental.service.js +81 -0
- package/dist/infrastructure/blacklist.service.js +71 -0
- package/dist/infrastructure/cache/file.hash.cache.js +140 -0
- package/dist/infrastructure/git/git.service.js +159 -0
- package/dist/infrastructure/git.service.js +157 -0
- package/dist/infrastructure/index.service.js +108 -0
- package/dist/infrastructure/llm/llm.usage.tracker.js +58 -0
- package/dist/infrastructure/llm/openai.client.js +141 -0
- package/{src/infrastructure/llm/prompt.template.ts → dist/infrastructure/llm/prompt.template.js} +31 -36
- package/dist/infrastructure/llm.service.js +61 -0
- package/dist/infrastructure/skill/skill.generator.js +83 -0
- package/{src/infrastructure/skill/templates/resolve.script.ts → dist/infrastructure/skill/templates/resolve.script.js} +18 -15
- package/dist/infrastructure/skill/templates/skill.md.template.js +47 -0
- package/dist/infrastructure/splitter/code.splitter.js +137 -0
- package/dist/infrastructure/storage.service.js +409 -0
- package/dist/infrastructure/worker-pool/parse.worker.impl.js +137 -0
- package/dist/infrastructure/worker-pool/parse.worker.js +43 -0
- package/dist/infrastructure/worker-pool/worker-pool.service.js +171 -0
- package/package.json +5 -1
- package/jest.config.js +0 -27
- package/src/adapters/command.schemas.ts +0 -21
- package/src/application/analysis.app.service.ts +0 -272
- package/src/application/bootstrap.ts +0 -35
- package/src/application/services/llm.analysis.service.ts +0 -237
- package/src/cli.ts +0 -297
- package/src/common/config.ts +0 -209
- package/src/common/constants.ts +0 -8
- package/src/common/errors.ts +0 -34
- package/src/common/logger.ts +0 -82
- package/src/common/types.ts +0 -385
- package/src/common/ui.ts +0 -228
- package/src/common/utils.ts +0 -81
- package/src/domain/index.ts +0 -1
- package/src/domain/interfaces.ts +0 -188
- package/src/domain/services/analysis.service.ts +0 -735
- package/src/domain/services/incremental.service.ts +0 -50
- package/src/index.ts +0 -6
- package/src/infrastructure/blacklist.service.ts +0 -37
- package/src/infrastructure/cache/file.hash.cache.ts +0 -119
- package/src/infrastructure/git/git.service.ts +0 -120
- package/src/infrastructure/git.service.ts +0 -121
- package/src/infrastructure/index.service.ts +0 -94
- package/src/infrastructure/llm/llm.usage.tracker.ts +0 -65
- package/src/infrastructure/llm/openai.client.ts +0 -162
- package/src/infrastructure/llm.service.ts +0 -70
- package/src/infrastructure/skill/skill.generator.ts +0 -53
- package/src/infrastructure/skill/templates/skill.md.template.ts +0 -45
- package/src/infrastructure/splitter/code.splitter.ts +0 -176
- package/src/infrastructure/storage.service.ts +0 -413
- package/src/infrastructure/worker-pool/parse.worker.impl.ts +0 -135
- package/src/infrastructure/worker-pool/parse.worker.ts +0 -9
- package/src/infrastructure/worker-pool/worker-pool.service.ts +0 -173
- package/tsconfig.json +0 -24
- package/tsconfig.test.json +0 -5
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.configManager = void 0;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const zod_1 = require("zod");
|
|
12
|
+
const constants_1 = require("./constants");
|
|
13
|
+
const logger_1 = require("./logger");
|
|
14
|
+
const errors_1 = require("./errors");
|
|
15
|
+
// V2.5:全局黑名单默认值(.gitignore 语法)
|
|
16
|
+
const DEFAULT_BLACKLIST = [
|
|
17
|
+
'*.yml', '*.yaml', '*.ini', '*.cfg', '*.json', '*.toml',
|
|
18
|
+
'*.md', '*.txt', '*.rst',
|
|
19
|
+
'README*', 'LICENSE*', 'CHANGELOG*',
|
|
20
|
+
'*.lock', 'package-lock.json', 'yarn.lock',
|
|
21
|
+
'*.env*', 'credentials.*', '*.pem', '*.key',
|
|
22
|
+
// 图片与图标等常见二进制资源(需求文档 13.3 / 测试文档 16.6)
|
|
23
|
+
'*.png', '*.jpg', '*.jpeg', '*.gif',
|
|
24
|
+
'*.bmp', '*.svg', '*.webp', '*.ico',
|
|
25
|
+
'docs/', 'dist/', 'build/', 'coverage/',
|
|
26
|
+
'node_modules/', '.git/', '.skill-any-code-result/',
|
|
27
|
+
'.agents/', '.claude/', '.gitignore'
|
|
28
|
+
];
|
|
29
|
+
const DEFAULT_PROVIDERS = ['opencode', 'cursor', 'claude', 'codex'];
|
|
30
|
+
const ConfigSchema = zod_1.z.object({
|
|
31
|
+
global: zod_1.z.object({
|
|
32
|
+
log_level: zod_1.z.enum(['debug', 'info', 'warn', 'error']).default('info'),
|
|
33
|
+
output_format: zod_1.z.enum(['text', 'json', 'markdown']).default('text'),
|
|
34
|
+
auto_confirm: zod_1.z.boolean().default(false),
|
|
35
|
+
output_dir: zod_1.z.string().default(constants_1.DEFAULT_OUTPUT_DIR),
|
|
36
|
+
}).default({}),
|
|
37
|
+
analyze: zod_1.z.object({
|
|
38
|
+
default_mode: zod_1.z.enum(['full', 'incremental', 'auto']).default('auto'),
|
|
39
|
+
/**
|
|
40
|
+
* 最大并发数上限:
|
|
41
|
+
* - 实际默认并发 = CPU核心数 * 2;
|
|
42
|
+
* - 若 CPU*2 > max_concurrency,则使用 max_concurrency 作为上限。
|
|
43
|
+
*/
|
|
44
|
+
max_concurrency: zod_1.z.number().default(constants_1.DEFAULT_CONCURRENCY),
|
|
45
|
+
default_depth: zod_1.z.number().default(-1),
|
|
46
|
+
blacklist: zod_1.z.array(zod_1.z.string()).default(DEFAULT_BLACKLIST),
|
|
47
|
+
}).default({}),
|
|
48
|
+
skills: zod_1.z.object({
|
|
49
|
+
default_providers: zod_1.z.array(zod_1.z.string()).default(DEFAULT_PROVIDERS),
|
|
50
|
+
}).default({}),
|
|
51
|
+
llm: zod_1.z.object({
|
|
52
|
+
// V2.5:默认配置中不再内置具体远程服务与模型,避免误上传代码(需求文档 13.4.1)
|
|
53
|
+
base_url: zod_1.z.string().default(''),
|
|
54
|
+
api_key: zod_1.z.string().default(''),
|
|
55
|
+
model: zod_1.z.string().default(''),
|
|
56
|
+
temperature: zod_1.z.number().min(0).max(2).default(0.1),
|
|
57
|
+
max_tokens: zod_1.z.number().int().min(100).default(4000),
|
|
58
|
+
timeout: zod_1.z.number().int().min(1000).default(60000),
|
|
59
|
+
max_retries: zod_1.z.number().int().min(0).default(3),
|
|
60
|
+
retry_delay: zod_1.z.number().int().min(100).default(1000),
|
|
61
|
+
context_window_size: zod_1.z.number().int().min(1000).default(128000),
|
|
62
|
+
cache_enabled: zod_1.z.boolean().default(true),
|
|
63
|
+
cache_dir: zod_1.z.string().default('~/.cache/skill-any-code/llm'),
|
|
64
|
+
// V2.5:新增缓存容量上限(MB),0 表示禁用磁盘缓存(需求文档 13.5.2)
|
|
65
|
+
cache_max_size_mb: zod_1.z.number().int().min(0).default(500),
|
|
66
|
+
}).default({}),
|
|
67
|
+
});
|
|
68
|
+
class ConfigManager {
|
|
69
|
+
config = null;
|
|
70
|
+
configPath = '';
|
|
71
|
+
getDefaultConfigPath() {
|
|
72
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
73
|
+
return path_1.default.join(home, '.config', 'skill-any-code', 'config.yaml');
|
|
74
|
+
}
|
|
75
|
+
expandTilde(pathStr) {
|
|
76
|
+
if (pathStr.startsWith('~') && (pathStr.length === 1 || pathStr[1] === '/' || pathStr[1] === '\\')) {
|
|
77
|
+
const home = process.env.HOME || process.env.USERPROFILE || os_1.default.homedir();
|
|
78
|
+
return path_1.default.join(home, pathStr.slice(1));
|
|
79
|
+
}
|
|
80
|
+
return pathStr;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 加载配置文件。
|
|
84
|
+
*
|
|
85
|
+
* V2.5 起,当目标配置文件不存在时:
|
|
86
|
+
* - 不再自动创建磁盘文件(由 init 子命令负责);
|
|
87
|
+
* - 抛出带有 ErrorCode.CONFIG_NOT_INITIALIZED 的 AppError,表示「配置未初始化」,
|
|
88
|
+
* 由 CLI 层捕获并提示用户先执行 init(测试文档 16.3 / 16.1.1)。
|
|
89
|
+
*/
|
|
90
|
+
async load(customPath) {
|
|
91
|
+
this.configPath = customPath ? this.expandTilde(customPath) : this.getDefaultConfigPath();
|
|
92
|
+
// 1. 加载默认配置(作为解析/校验基线)
|
|
93
|
+
let config = ConfigSchema.parse({});
|
|
94
|
+
// 2. 加载配置文件;不存在时交由上层处理「未初始化」状态
|
|
95
|
+
if (await fs_extra_1.default.pathExists(this.configPath)) {
|
|
96
|
+
try {
|
|
97
|
+
const fileContent = await fs_extra_1.default.readFile(this.configPath, 'utf-8');
|
|
98
|
+
const fileConfig = js_yaml_1.default.load(fileContent);
|
|
99
|
+
config = ConfigSchema.parse(fileConfig);
|
|
100
|
+
logger_1.logger.debug(`Loaded config file: ${this.configPath}`);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
logger_1.logger.warn(`Failed to parse config file. Using defaults: ${error.message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// 配置文件不存在:不再隐式创建,由 CLI 的 init 子命令负责初始化
|
|
108
|
+
const defaultConfig = ConfigSchema.parse({});
|
|
109
|
+
this.config = defaultConfig;
|
|
110
|
+
throw new errors_1.AppError(errors_1.ErrorCode.CONFIG_NOT_INITIALIZED, `Config is not initialized: ${this.configPath}`);
|
|
111
|
+
}
|
|
112
|
+
// 3. 加载环境变量
|
|
113
|
+
const envConfig = {};
|
|
114
|
+
Object.entries(process.env).forEach(([key, value]) => {
|
|
115
|
+
if (!key.startsWith('SKILL_ANY_CODE_'))
|
|
116
|
+
return;
|
|
117
|
+
const configKey = key.replace('SKILL_ANY_CODE_', '').toLowerCase();
|
|
118
|
+
if (!envConfig.global)
|
|
119
|
+
envConfig.global = {};
|
|
120
|
+
if (configKey === 'log_level' && value)
|
|
121
|
+
envConfig.global.log_level = value;
|
|
122
|
+
if (configKey === 'output_format' && value)
|
|
123
|
+
envConfig.global.output_format = value;
|
|
124
|
+
if (configKey === 'auto_confirm' && value)
|
|
125
|
+
envConfig.global.auto_confirm = value === 'true';
|
|
126
|
+
if (configKey === 'output_dir' && value)
|
|
127
|
+
envConfig.global.output_dir = value;
|
|
128
|
+
if (!envConfig.analyze)
|
|
129
|
+
envConfig.analyze = {};
|
|
130
|
+
if (configKey === 'analyze_default_mode' && value)
|
|
131
|
+
envConfig.analyze.default_mode = value;
|
|
132
|
+
if (configKey === 'analyze_max_concurrency' && value)
|
|
133
|
+
envConfig.analyze.max_concurrency = Number(value);
|
|
134
|
+
if (configKey === 'analyze_default_depth' && value)
|
|
135
|
+
envConfig.analyze.default_depth = Number(value);
|
|
136
|
+
if (!envConfig.skills)
|
|
137
|
+
envConfig.skills = {};
|
|
138
|
+
if (configKey === 'skills_default_providers' && value) {
|
|
139
|
+
envConfig.skills.default_providers = value.split(',').map((s) => s.trim());
|
|
140
|
+
}
|
|
141
|
+
if (!envConfig.llm)
|
|
142
|
+
envConfig.llm = {};
|
|
143
|
+
if (configKey === 'llm_base_url' && value)
|
|
144
|
+
envConfig.llm.base_url = value;
|
|
145
|
+
if (configKey === 'llm_api_key' && value)
|
|
146
|
+
envConfig.llm.api_key = value;
|
|
147
|
+
if (configKey === 'llm_model' && value)
|
|
148
|
+
envConfig.llm.model = value;
|
|
149
|
+
if (configKey === 'llm_temperature' && value)
|
|
150
|
+
envConfig.llm.temperature = Number(value);
|
|
151
|
+
if (configKey === 'llm_max_tokens' && value)
|
|
152
|
+
envConfig.llm.max_tokens = Number(value);
|
|
153
|
+
if (configKey === 'llm_timeout' && value)
|
|
154
|
+
envConfig.llm.timeout = Number(value);
|
|
155
|
+
if (configKey === 'llm_max_retries' && value)
|
|
156
|
+
envConfig.llm.max_retries = Number(value);
|
|
157
|
+
if (configKey === 'llm_retry_delay' && value)
|
|
158
|
+
envConfig.llm.retry_delay = Number(value);
|
|
159
|
+
if (configKey === 'llm_context_window_size' && value)
|
|
160
|
+
envConfig.llm.context_window_size = Number(value);
|
|
161
|
+
if (configKey === 'llm_cache_enabled' && value)
|
|
162
|
+
envConfig.llm.cache_enabled = value === 'true';
|
|
163
|
+
if (configKey === 'llm_cache_dir' && value)
|
|
164
|
+
envConfig.llm.cache_dir = value;
|
|
165
|
+
if (configKey === 'llm_cache_max_size_mb' && value)
|
|
166
|
+
envConfig.llm.cache_max_size_mb = Number(value);
|
|
167
|
+
});
|
|
168
|
+
// 注意:配置为嵌套对象,环境变量覆盖需要做“深合并”,否则只设置某个 llm 子字段
|
|
169
|
+
// 会导致整个 llm 段被浅覆盖成不完整对象(从而触发 LLM_INVALID_CONFIG)。
|
|
170
|
+
config = ConfigSchema.parse({
|
|
171
|
+
...config,
|
|
172
|
+
global: { ...config.global, ...(envConfig.global ?? {}) },
|
|
173
|
+
analyze: { ...config.analyze, ...(envConfig.analyze ?? {}) },
|
|
174
|
+
skills: { ...config.skills, ...(envConfig.skills ?? {}) },
|
|
175
|
+
llm: { ...config.llm, ...(envConfig.llm ?? {}) },
|
|
176
|
+
});
|
|
177
|
+
this.config = config;
|
|
178
|
+
return config;
|
|
179
|
+
}
|
|
180
|
+
getConfig() {
|
|
181
|
+
if (!this.config) {
|
|
182
|
+
throw new Error('Config not loaded. Call load() first.');
|
|
183
|
+
}
|
|
184
|
+
return this.config;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* 显式初始化配置文件内容。
|
|
188
|
+
*
|
|
189
|
+
* - 当目标文件不存在时:创建目录并写入默认配置;
|
|
190
|
+
* - 当目标文件已存在时:直接覆盖为默认配置(是否覆盖由上层 CLI 交互确认)。
|
|
191
|
+
*/
|
|
192
|
+
async init(customPath) {
|
|
193
|
+
this.configPath = customPath ? this.expandTilde(customPath) : this.getDefaultConfigPath();
|
|
194
|
+
const defaultConfig = ConfigSchema.parse({});
|
|
195
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(this.configPath));
|
|
196
|
+
await fs_extra_1.default.writeFile(this.configPath, js_yaml_1.default.dump(defaultConfig), 'utf-8');
|
|
197
|
+
this.config = defaultConfig;
|
|
198
|
+
logger_1.logger.debug(`Initialized config file: ${this.configPath}`);
|
|
199
|
+
}
|
|
200
|
+
async save(config) {
|
|
201
|
+
const currentConfig = this.config || await this.load();
|
|
202
|
+
const mergedConfig = { ...currentConfig, ...config };
|
|
203
|
+
const validatedConfig = ConfigSchema.parse(mergedConfig);
|
|
204
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(this.configPath));
|
|
205
|
+
await fs_extra_1.default.writeFile(this.configPath, js_yaml_1.default.dump(validatedConfig), 'utf-8');
|
|
206
|
+
this.config = validatedConfig;
|
|
207
|
+
}
|
|
208
|
+
async reset() {
|
|
209
|
+
const defaultConfig = ConfigSchema.parse({});
|
|
210
|
+
await this.save(defaultConfig);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
exports.configManager = new ConfigManager();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_OUTPUT_DIR = exports.SUPPORTED_EXTENSIONS = exports.DEFAULT_CONCURRENCY = exports.MAX_GIT_COMMITS_HISTORY = exports.SCHEMA_VERSION = exports.ANALYSIS_VERSION = void 0;
|
|
4
|
+
exports.ANALYSIS_VERSION = '1.0.0';
|
|
5
|
+
exports.SCHEMA_VERSION = '1.0';
|
|
6
|
+
exports.MAX_GIT_COMMITS_HISTORY = 50;
|
|
7
|
+
exports.DEFAULT_CONCURRENCY = require('os').cpus().length * 2;
|
|
8
|
+
// 所有文本代码文件都支持,无后缀限制,由LLM自动识别语言
|
|
9
|
+
exports.SUPPORTED_EXTENSIONS = ['*'];
|
|
10
|
+
// V2.3:默认黑名单已迁移至 config.ts 的 DEFAULT_BLACKLIST
|
|
11
|
+
exports.DEFAULT_OUTPUT_DIR = './.skill-any-code-result';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AppError = exports.ErrorCode = void 0;
|
|
4
|
+
var ErrorCode;
|
|
5
|
+
(function (ErrorCode) {
|
|
6
|
+
ErrorCode[ErrorCode["SUCCESS"] = 200] = "SUCCESS";
|
|
7
|
+
ErrorCode[ErrorCode["PARAM_VALIDATION_FAILED"] = 4000] = "PARAM_VALIDATION_FAILED";
|
|
8
|
+
ErrorCode[ErrorCode["PROJECT_PATH_NOT_EXIST"] = 4001] = "PROJECT_PATH_NOT_EXIST";
|
|
9
|
+
ErrorCode[ErrorCode["NO_PARSER_AVAILABLE"] = 4002] = "NO_PARSER_AVAILABLE";
|
|
10
|
+
ErrorCode[ErrorCode["INCREMENTAL_NOT_AVAILABLE"] = 4003] = "INCREMENTAL_NOT_AVAILABLE";
|
|
11
|
+
ErrorCode[ErrorCode["ANALYSIS_NOT_EXIST"] = 4004] = "ANALYSIS_NOT_EXIST";
|
|
12
|
+
ErrorCode[ErrorCode["CONFIG_NOT_INITIALIZED"] = 4005] = "CONFIG_NOT_INITIALIZED";
|
|
13
|
+
ErrorCode[ErrorCode["ANALYSIS_EXCEPTION"] = 5000] = "ANALYSIS_EXCEPTION";
|
|
14
|
+
ErrorCode[ErrorCode["GIT_OPERATION_FAILED"] = 5001] = "GIT_OPERATION_FAILED";
|
|
15
|
+
ErrorCode[ErrorCode["STORAGE_WRITE_FAILED"] = 5002] = "STORAGE_WRITE_FAILED";
|
|
16
|
+
ErrorCode[ErrorCode["WORKER_SCHEDULE_FAILED"] = 5003] = "WORKER_SCHEDULE_FAILED";
|
|
17
|
+
ErrorCode[ErrorCode["LLM_CALL_FAILED"] = 5010] = "LLM_CALL_FAILED";
|
|
18
|
+
ErrorCode[ErrorCode["LLM_RESPONSE_PARSE_FAILED"] = 5011] = "LLM_RESPONSE_PARSE_FAILED";
|
|
19
|
+
ErrorCode[ErrorCode["LLM_RATE_LIMITED"] = 5012] = "LLM_RATE_LIMITED";
|
|
20
|
+
ErrorCode[ErrorCode["LLM_TIMEOUT"] = 5013] = "LLM_TIMEOUT";
|
|
21
|
+
ErrorCode[ErrorCode["LLM_INVALID_CONFIG"] = 5014] = "LLM_INVALID_CONFIG";
|
|
22
|
+
ErrorCode[ErrorCode["FILE_TOO_LARGE"] = 5020] = "FILE_TOO_LARGE";
|
|
23
|
+
ErrorCode[ErrorCode["FILE_SPLIT_FAILED"] = 5021] = "FILE_SPLIT_FAILED";
|
|
24
|
+
ErrorCode[ErrorCode["CHUNK_MERGE_FAILED"] = 5022] = "CHUNK_MERGE_FAILED";
|
|
25
|
+
})(ErrorCode || (exports.ErrorCode = ErrorCode = {}));
|
|
26
|
+
class AppError extends Error {
|
|
27
|
+
code;
|
|
28
|
+
details;
|
|
29
|
+
constructor(code, message, details) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = 'AppError';
|
|
32
|
+
this.code = code;
|
|
33
|
+
this.details = details;
|
|
34
|
+
Object.setPrototypeOf(this, AppError.prototype);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.AppError = AppError;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.logger = void 0;
|
|
7
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
8
|
+
const LOG_LEVELS = {
|
|
9
|
+
debug: 0,
|
|
10
|
+
info: 1,
|
|
11
|
+
warn: 2,
|
|
12
|
+
error: 3,
|
|
13
|
+
};
|
|
14
|
+
class Logger {
|
|
15
|
+
level = 'info';
|
|
16
|
+
// 可选的外部日志接收器(例如 CLI 多行渲染器),用于实现“日志固定在进度块下方”。
|
|
17
|
+
sink;
|
|
18
|
+
setLevel(level) {
|
|
19
|
+
this.level = level;
|
|
20
|
+
}
|
|
21
|
+
setSink(sink) {
|
|
22
|
+
this.sink = sink;
|
|
23
|
+
}
|
|
24
|
+
shouldLog(level) {
|
|
25
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
|
|
26
|
+
}
|
|
27
|
+
write(line, isError) {
|
|
28
|
+
if (this.sink) {
|
|
29
|
+
this.sink(line, isError);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (isError) {
|
|
33
|
+
// stderr 不做颜色降级,交给调用方控制;这里保留 ANSI 颜色。
|
|
34
|
+
// eslint-disable-next-line no-console
|
|
35
|
+
console.error(line);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
// eslint-disable-next-line no-console
|
|
39
|
+
console.log(line);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
debug(message, ...args) {
|
|
43
|
+
if (!this.shouldLog('debug'))
|
|
44
|
+
return;
|
|
45
|
+
const line = picocolors_1.default.gray(`[DEBUG] ${message}${args.length ? ' ' + args.join(' ') : ''}`);
|
|
46
|
+
this.write(line, false);
|
|
47
|
+
}
|
|
48
|
+
info(message, ...args) {
|
|
49
|
+
if (!this.shouldLog('info'))
|
|
50
|
+
return;
|
|
51
|
+
const line = picocolors_1.default.blue(`[INFO] ${message}${args.length ? ' ' + args.join(' ') : ''}`);
|
|
52
|
+
this.write(line, false);
|
|
53
|
+
}
|
|
54
|
+
success(message, ...args) {
|
|
55
|
+
if (!this.shouldLog('info'))
|
|
56
|
+
return;
|
|
57
|
+
const line = picocolors_1.default.green(`[SUCCESS] ${message}${args.length ? ' ' + args.join(' ') : ''}`);
|
|
58
|
+
this.write(line, false);
|
|
59
|
+
}
|
|
60
|
+
warn(message, ...args) {
|
|
61
|
+
if (!this.shouldLog('warn'))
|
|
62
|
+
return;
|
|
63
|
+
const line = picocolors_1.default.yellow(`[WARN] ${message}${args.length ? ' ' + args.join(' ') : ''}`);
|
|
64
|
+
this.write(line, false);
|
|
65
|
+
}
|
|
66
|
+
error(message, error, ...args) {
|
|
67
|
+
if (!this.shouldLog('error'))
|
|
68
|
+
return;
|
|
69
|
+
const base = picocolors_1.default.red(`[ERROR] ${message}${args.length ? ' ' + args.join(' ') : ''}`);
|
|
70
|
+
this.write(base, true);
|
|
71
|
+
if (error && this.level === 'debug') {
|
|
72
|
+
const stackLine = picocolors_1.default.red(error.stack || String(error));
|
|
73
|
+
this.write(stackLine, true);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.logger = new Logger();
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.cliRenderer = exports.CliMultiSectionRenderer = void 0;
|
|
7
|
+
exports.confirm = confirm;
|
|
8
|
+
exports.select = select;
|
|
9
|
+
exports.input = input;
|
|
10
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
11
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
12
|
+
/**
|
|
13
|
+
* CLI 多行区域渲染器:统一负责
|
|
14
|
+
* - 进度行:「解析进度 |████...| 37% | 已处理: x/y 对象」
|
|
15
|
+
* - 当前对象块:
|
|
16
|
+
* 当前对象:
|
|
17
|
+
* [ foo ]
|
|
18
|
+
* [ bar ]
|
|
19
|
+
* - Tokens 行:「Tokens: in=... out=... total=...」
|
|
20
|
+
*
|
|
21
|
+
* 通过 ANSI 光标控制码在同一块区域内重绘,避免产生多份重复的块。
|
|
22
|
+
*/
|
|
23
|
+
class CliMultiSectionRenderer {
|
|
24
|
+
state = {
|
|
25
|
+
total: 0,
|
|
26
|
+
done: 0,
|
|
27
|
+
currentObjects: [],
|
|
28
|
+
totalKnown: false,
|
|
29
|
+
};
|
|
30
|
+
// 最近一次渲染占用了多少行,用于回退光标和清空
|
|
31
|
+
renderedLines = 0;
|
|
32
|
+
isInteractive;
|
|
33
|
+
constructor() {
|
|
34
|
+
this.isInteractive = !!process.stdout.isTTY && process.env.TERM !== 'dumb';
|
|
35
|
+
}
|
|
36
|
+
setTotal(total) {
|
|
37
|
+
this.state.total = total;
|
|
38
|
+
this.state.totalKnown = true;
|
|
39
|
+
if (this.state.done > total) {
|
|
40
|
+
this.state.done = total;
|
|
41
|
+
}
|
|
42
|
+
this.render();
|
|
43
|
+
}
|
|
44
|
+
updateProgress(done, total, currentPathsText, maxLines) {
|
|
45
|
+
this.state.done = done;
|
|
46
|
+
// 若 total 尚未通过 onTotalKnown 确认,则不接受外部传入的 total,避免占位魔法数字污染首帧。
|
|
47
|
+
if (this.state.totalKnown) {
|
|
48
|
+
this.state.total = total;
|
|
49
|
+
}
|
|
50
|
+
if (currentPathsText !== undefined) {
|
|
51
|
+
let lines = currentPathsText
|
|
52
|
+
.split('\n')
|
|
53
|
+
.map((l) => l.trim())
|
|
54
|
+
.filter((l) => l.length > 0);
|
|
55
|
+
if (typeof maxLines === 'number' && maxLines > 0 && lines.length > maxLines) {
|
|
56
|
+
lines = lines.slice(0, maxLines);
|
|
57
|
+
}
|
|
58
|
+
this.state.currentObjects = lines;
|
|
59
|
+
}
|
|
60
|
+
this.render();
|
|
61
|
+
}
|
|
62
|
+
updateTokens(tokens) {
|
|
63
|
+
this.state.tokens = { ...tokens };
|
|
64
|
+
this.render();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 扫描阶段:实时更新“已扫描将被解析的文件数”。
|
|
68
|
+
* 该信息以单独一行展示,并在同一位置覆盖刷新。
|
|
69
|
+
*/
|
|
70
|
+
updateScanProgress(scannedFiles) {
|
|
71
|
+
this.state.scannedFiles = scannedFiles;
|
|
72
|
+
this.render();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 在进度区域下方追加一条日志。
|
|
76
|
+
* 实现方式:先清空进度区域,将日志打印到 stdout,然后重新渲染进度区域。
|
|
77
|
+
*/
|
|
78
|
+
logBelow(message) {
|
|
79
|
+
if (this.isInteractive) {
|
|
80
|
+
this.clearRenderedArea();
|
|
81
|
+
process.stdout.write(message + '\n');
|
|
82
|
+
this.render();
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// 非交互终端下不做覆盖,仅顺序输出日志与最新快照,避免依赖 ANSI 光标控制。
|
|
86
|
+
process.stdout.write(message + '\n');
|
|
87
|
+
const lines = this.buildLines();
|
|
88
|
+
for (const line of lines) {
|
|
89
|
+
process.stdout.write(line + '\n');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
buildLines() {
|
|
94
|
+
const { total, done, currentObjects, tokens, totalKnown, scannedFiles } = this.state;
|
|
95
|
+
const safeTotal = totalKnown && total > 0 ? total : 1;
|
|
96
|
+
const ratio = totalKnown ? Math.max(0, Math.min(1, done / safeTotal)) : 0;
|
|
97
|
+
const percentage = totalKnown ? Math.floor(ratio * 100) : 0;
|
|
98
|
+
const barWidth = 40;
|
|
99
|
+
const completeCount = Math.round(barWidth * ratio);
|
|
100
|
+
const incompleteCount = barWidth - completeCount;
|
|
101
|
+
const bar = '█'.repeat(completeCount) +
|
|
102
|
+
'░'.repeat(Math.max(0, incompleteCount));
|
|
103
|
+
const lines = [];
|
|
104
|
+
// 扫描阶段行:仅当有值时展示一行,实时覆盖刷新(对象=文件+目录)
|
|
105
|
+
if (typeof scannedFiles === 'number') {
|
|
106
|
+
lines.push(`Scanned objects to analyze: ${scannedFiles}`);
|
|
107
|
+
}
|
|
108
|
+
// 进度行(V2.4:将「已处理: x/y」拆为独立行,避免被“当前对象路径提取”误判为路径)
|
|
109
|
+
const totalLabel = totalKnown ? String(total) : '?';
|
|
110
|
+
lines.push(`${picocolors_1.default.blue('Progress')} |${picocolors_1.default.cyan(bar)}| ${percentage}%`);
|
|
111
|
+
lines.push(`Processed: ${done}/${totalLabel}`);
|
|
112
|
+
// 当前对象块
|
|
113
|
+
// 需求:仅当存在 worker 正在处理的对象时才展示该块;无对象时整段不输出(不占用任何行)。
|
|
114
|
+
if (currentObjects.length > 0) {
|
|
115
|
+
lines.push('Current:');
|
|
116
|
+
for (const obj of currentObjects) {
|
|
117
|
+
lines.push(` [ ${obj} ]`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Tokens 行(即使 tokens 尚未产生也显示 0,避免首帧缺行导致 UI 跳变)
|
|
121
|
+
const t = tokens ?? { totalPromptTokens: 0, totalCompletionTokens: 0, totalTokens: 0, totalCalls: 0 };
|
|
122
|
+
lines.push(`Tokens: in=${t.totalPromptTokens} out=${t.totalCompletionTokens} total=${t.totalTokens}`);
|
|
123
|
+
return lines;
|
|
124
|
+
}
|
|
125
|
+
clearRenderedArea() {
|
|
126
|
+
if (!this.isInteractive || this.renderedLines <= 0)
|
|
127
|
+
return;
|
|
128
|
+
// 将光标移动到渲染块的起始行
|
|
129
|
+
process.stdout.write(`\u001b[${this.renderedLines}F`);
|
|
130
|
+
for (let i = 0; i < this.renderedLines; i += 1) {
|
|
131
|
+
// 清除当前行并换行到下一行
|
|
132
|
+
process.stdout.write('\u001b[2K\r\n');
|
|
133
|
+
}
|
|
134
|
+
// 回到块的起始位置
|
|
135
|
+
process.stdout.write(`\u001b[${this.renderedLines}F`);
|
|
136
|
+
this.renderedLines = 0;
|
|
137
|
+
}
|
|
138
|
+
render() {
|
|
139
|
+
const lines = this.buildLines();
|
|
140
|
+
if (this.isInteractive) {
|
|
141
|
+
// 将光标移到当前块顶部并清空旧内容
|
|
142
|
+
if (this.renderedLines > 0) {
|
|
143
|
+
process.stdout.write(`\u001b[${this.renderedLines}F`);
|
|
144
|
+
}
|
|
145
|
+
for (let i = 0; i < this.renderedLines; i += 1) {
|
|
146
|
+
process.stdout.write('\u001b[2K\r\n');
|
|
147
|
+
}
|
|
148
|
+
if (this.renderedLines > 0) {
|
|
149
|
+
process.stdout.write(`\u001b[${this.renderedLines}F`);
|
|
150
|
+
}
|
|
151
|
+
// 写入新内容
|
|
152
|
+
for (const line of lines) {
|
|
153
|
+
process.stdout.write('\u001b[2K'); // 清当前行
|
|
154
|
+
process.stdout.write(line + '\n');
|
|
155
|
+
}
|
|
156
|
+
this.renderedLines = lines.length;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
// 非交互终端:不做覆盖,仅输出一次最新快照,避免光标控制符干扰日志采集。
|
|
160
|
+
for (const line of lines) {
|
|
161
|
+
process.stdout.write(line + '\n');
|
|
162
|
+
}
|
|
163
|
+
this.renderedLines = 0;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
exports.CliMultiSectionRenderer = CliMultiSectionRenderer;
|
|
168
|
+
exports.cliRenderer = new CliMultiSectionRenderer();
|
|
169
|
+
async function confirm(message, defaultAnswer = false) {
|
|
170
|
+
const { answer } = await inquirer_1.default.prompt([
|
|
171
|
+
{
|
|
172
|
+
type: 'confirm',
|
|
173
|
+
name: 'answer',
|
|
174
|
+
message,
|
|
175
|
+
default: defaultAnswer,
|
|
176
|
+
},
|
|
177
|
+
]);
|
|
178
|
+
return answer;
|
|
179
|
+
}
|
|
180
|
+
async function select(message, choices) {
|
|
181
|
+
const { selected } = await inquirer_1.default.prompt([
|
|
182
|
+
{
|
|
183
|
+
type: 'list',
|
|
184
|
+
name: 'selected',
|
|
185
|
+
message,
|
|
186
|
+
choices,
|
|
187
|
+
},
|
|
188
|
+
]);
|
|
189
|
+
return selected;
|
|
190
|
+
}
|
|
191
|
+
async function input(message, defaultValue) {
|
|
192
|
+
const { value } = await inquirer_1.default.prompt([
|
|
193
|
+
{
|
|
194
|
+
type: 'input',
|
|
195
|
+
name: 'value',
|
|
196
|
+
message,
|
|
197
|
+
default: defaultValue,
|
|
198
|
+
},
|
|
199
|
+
]);
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.normalizePath = normalizePath;
|
|
37
|
+
exports.generateProjectSlug = generateProjectSlug;
|
|
38
|
+
exports.getStoragePath = getStoragePath;
|
|
39
|
+
exports.getFileOutputPath = getFileOutputPath;
|
|
40
|
+
exports.getDirOutputPath = getDirOutputPath;
|
|
41
|
+
exports.mapLimit = mapLimit;
|
|
42
|
+
exports.getLanguageFromExtension = getLanguageFromExtension;
|
|
43
|
+
const crypto = __importStar(require("crypto"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const constants_1 = require("./constants");
|
|
46
|
+
/**
|
|
47
|
+
* 规范化路径:统一使用正斜杠、移除尾部斜杠
|
|
48
|
+
* 用于索引文件中的路径标准化和 resolve 查询时的路径匹配
|
|
49
|
+
*/
|
|
50
|
+
function normalizePath(inputPath) {
|
|
51
|
+
let normalized = inputPath.replace(/\\/g, '/');
|
|
52
|
+
if (normalized.length > 1 && normalized.endsWith('/')) {
|
|
53
|
+
normalized = normalized.slice(0, -1);
|
|
54
|
+
}
|
|
55
|
+
return normalized;
|
|
56
|
+
}
|
|
57
|
+
function generateProjectSlug(projectRoot, isGit, gitSlug) {
|
|
58
|
+
if (isGit && gitSlug) {
|
|
59
|
+
return gitSlug.replace('/', '-');
|
|
60
|
+
}
|
|
61
|
+
const dirName = path.basename(projectRoot);
|
|
62
|
+
const pathHash = crypto.createHash('md5').update(projectRoot).digest('hex').slice(0, 8);
|
|
63
|
+
return `${dirName}-${pathHash}`;
|
|
64
|
+
}
|
|
65
|
+
function getStoragePath(projectRoot, customOutputDir) {
|
|
66
|
+
const outputDir = customOutputDir || constants_1.DEFAULT_OUTPUT_DIR;
|
|
67
|
+
// 如果是相对路径,相对于项目根目录
|
|
68
|
+
if (!path.isAbsolute(outputDir)) {
|
|
69
|
+
return path.resolve(projectRoot, outputDir);
|
|
70
|
+
}
|
|
71
|
+
return outputDir;
|
|
72
|
+
}
|
|
73
|
+
function getFileOutputPath(storageRoot, filePath) {
|
|
74
|
+
const parsed = path.parse(filePath);
|
|
75
|
+
// 特殊处理 index.xxx:为避免与目录级 index.md 冲突,文件结果命名为 index.xxx.md
|
|
76
|
+
if (parsed.name === 'index' && parsed.ext) {
|
|
77
|
+
return path.join(storageRoot, parsed.dir, `index${parsed.ext}.md`);
|
|
78
|
+
}
|
|
79
|
+
return path.join(storageRoot, parsed.dir, `${parsed.name}.md`);
|
|
80
|
+
}
|
|
81
|
+
function getDirOutputPath(storageRoot, dirPath) {
|
|
82
|
+
return path.join(storageRoot, dirPath, 'index.md');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 受控并发执行:同时最多 limit 个 fn 在运行,所有 items 执行完毕后 resolve。
|
|
86
|
+
*/
|
|
87
|
+
async function mapLimit(items, limit, fn) {
|
|
88
|
+
const concurrency = Math.max(1, Number(limit) || 1);
|
|
89
|
+
let nextIndex = 0;
|
|
90
|
+
const runners = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
|
91
|
+
while (true) {
|
|
92
|
+
const idx = nextIndex++;
|
|
93
|
+
if (idx >= items.length)
|
|
94
|
+
return;
|
|
95
|
+
await fn(items[idx]);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
await Promise.all(runners);
|
|
99
|
+
}
|
|
100
|
+
function getLanguageFromExtension(ext) {
|
|
101
|
+
const map = {
|
|
102
|
+
'.js': 'javascript',
|
|
103
|
+
'.ts': 'typescript',
|
|
104
|
+
'.jsx': 'javascript',
|
|
105
|
+
'.tsx': 'typescript',
|
|
106
|
+
'.py': 'python',
|
|
107
|
+
'.java': 'java',
|
|
108
|
+
'.go': 'go',
|
|
109
|
+
'.cpp': 'cpp',
|
|
110
|
+
'.c': 'c',
|
|
111
|
+
'.cs': 'csharp',
|
|
112
|
+
'.php': 'php',
|
|
113
|
+
'.rb': 'ruby',
|
|
114
|
+
'.rs': 'rust'
|
|
115
|
+
};
|
|
116
|
+
return map[ext.toLowerCase()] || 'unknown';
|
|
117
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./interfaces"), exports);
|