ccbot-cli 2.0.0 → 2.1.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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/bin/adapters/claude.js +150 -0
  3. package/bin/adapters/codex.js +439 -0
  4. package/bin/install.js +509 -349
  5. package/bin/lib/ccline.js +82 -0
  6. package/bin/lib/utils.js +87 -34
  7. package/bin/uninstall.js +48 -0
  8. package/config/AGENTS.md +630 -0
  9. package/config/CLAUDE.md +229 -20
  10. package/config/ccline/config.toml +161 -0
  11. package/config/codex-config.example.toml +22 -0
  12. package/config/settings.example.json +32 -0
  13. package/output-styles/abyss-cultivator.md +399 -0
  14. package/package.json +14 -5
  15. package/skills/SKILL.md +159 -0
  16. package/skills/domains/ai/SKILL.md +34 -0
  17. package/skills/domains/ai/agent-dev.md +242 -0
  18. package/skills/domains/ai/llm-security.md +288 -0
  19. package/skills/domains/ai/prompt-and-eval.md +279 -0
  20. package/skills/domains/ai/rag-system.md +542 -0
  21. package/skills/domains/architecture/SKILL.md +42 -0
  22. package/skills/domains/architecture/api-design.md +225 -0
  23. package/skills/domains/architecture/caching.md +299 -0
  24. package/skills/domains/architecture/cloud-native.md +285 -0
  25. package/skills/domains/architecture/message-queue.md +329 -0
  26. package/skills/domains/architecture/security-arch.md +297 -0
  27. package/skills/domains/data-engineering/SKILL.md +207 -0
  28. package/skills/domains/development/SKILL.md +46 -0
  29. package/skills/domains/development/cpp.md +246 -0
  30. package/skills/domains/development/go.md +323 -0
  31. package/skills/domains/development/java.md +277 -0
  32. package/skills/domains/development/python.md +288 -0
  33. package/skills/domains/development/rust.md +313 -0
  34. package/skills/domains/development/shell.md +313 -0
  35. package/skills/domains/development/typescript.md +277 -0
  36. package/skills/domains/devops/SKILL.md +39 -0
  37. package/skills/domains/devops/cost-optimization.md +272 -0
  38. package/skills/domains/devops/database.md +217 -0
  39. package/skills/domains/devops/devsecops.md +198 -0
  40. package/skills/domains/devops/git-workflow.md +181 -0
  41. package/skills/domains/devops/observability.md +280 -0
  42. package/skills/domains/devops/performance.md +336 -0
  43. package/skills/domains/devops/testing.md +283 -0
  44. package/skills/domains/frontend-design/SKILL.md +38 -0
  45. package/skills/domains/frontend-design/claymorphism/SKILL.md +119 -0
  46. package/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
  47. package/skills/domains/frontend-design/component-patterns.md +202 -0
  48. package/skills/domains/frontend-design/engineering.md +287 -0
  49. package/skills/domains/frontend-design/glassmorphism/SKILL.md +140 -0
  50. package/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
  51. package/skills/domains/frontend-design/liquid-glass/SKILL.md +137 -0
  52. package/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
  53. package/skills/domains/frontend-design/neubrutalism/SKILL.md +143 -0
  54. package/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
  55. package/skills/domains/frontend-design/state-management.md +680 -0
  56. package/skills/domains/frontend-design/ui-aesthetics.md +110 -0
  57. package/skills/domains/frontend-design/ux-principles.md +156 -0
  58. package/skills/domains/infrastructure/SKILL.md +200 -0
  59. package/skills/domains/mobile/SKILL.md +224 -0
  60. package/skills/domains/orchestration/SKILL.md +29 -0
  61. package/skills/domains/orchestration/multi-agent.md +263 -0
  62. package/skills/domains/security/SKILL.md +54 -0
  63. package/skills/domains/security/blue-team.md +436 -0
  64. package/skills/domains/security/code-audit.md +265 -0
  65. package/skills/domains/security/pentest.md +226 -0
  66. package/skills/domains/security/red-team.md +375 -0
  67. package/skills/domains/security/threat-intel.md +372 -0
  68. package/skills/domains/security/vuln-research.md +369 -0
  69. package/skills/orchestration/multi-agent/SKILL.md +493 -0
  70. package/skills/run_skill.js +129 -0
  71. package/skills/tools/gen-docs/SKILL.md +116 -0
  72. package/skills/tools/gen-docs/scripts/doc_generator.js +435 -0
  73. package/skills/tools/lib/shared.js +98 -0
  74. package/skills/tools/verify-change/SKILL.md +140 -0
  75. package/skills/tools/verify-change/scripts/change_analyzer.js +289 -0
  76. package/skills/tools/verify-module/SKILL.md +127 -0
  77. package/skills/tools/verify-module/scripts/module_scanner.js +171 -0
  78. package/skills/tools/verify-quality/SKILL.md +160 -0
  79. package/skills/tools/verify-quality/scripts/quality_checker.js +337 -0
  80. package/skills/tools/verify-security/SKILL.md +143 -0
  81. package/skills/tools/verify-security/scripts/security_scanner.js +283 -0
  82. package/bin/lib/registry.js +0 -61
  83. package/config/.claudeignore +0 -11
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: gen-docs
3
+ description: 文档生成器。自动分析模块结构,生成 README.md 和 DESIGN.md 骨架。当魔尊提到生成文档、创建README、创建DESIGN、文档骨架、文档模板时使用。在新建模块开始时自动触发。
4
+ license: MIT
5
+ compatibility: node>=18
6
+ user-invocable: true
7
+ disable-model-invocation: false
8
+ allowed-tools: Bash, Read, Write, Glob
9
+ argument-hint: <模块路径> [--force]
10
+ ---
11
+
12
+ # 📝 造典关卡 · 文档生成器
13
+
14
+
15
+ ## 核心原则
16
+
17
+ ```
18
+ 无文档不成模块
19
+ 文档是模块的身份证
20
+ 没有身份证的模块不允许上线
21
+ ```
22
+
23
+ ## 自动生成
24
+
25
+ 运行文档生成脚本(跨平台):
26
+
27
+ ```bash
28
+ # 在 skill 目录下运行
29
+ node scripts/doc_generator.js <模块路径>
30
+ node scripts/doc_generator.js <模块路径> --force # 强制覆盖已存在的文档
31
+ node scripts/doc_generator.js <模块路径> --json # JSON 输出
32
+ ```
33
+
34
+ ## 生成内容
35
+
36
+ ### README.md 骨架
37
+
38
+ 自动生成的 README.md 包含:
39
+
40
+ - **模块名称** — 从目录名提取
41
+ - **描述** — 从代码文档字符串提取(如有)
42
+ - **特性列表** — 待填充
43
+ - **依赖** — 从 requirements.txt/pyproject.toml 提取
44
+ - **使用方法** — 基础模板
45
+ - **API 概览** — 从代码提取类和函数列表
46
+ - **目录结构** — 自动扫描生成
47
+
48
+ ### DESIGN.md 骨架
49
+
50
+ 自动生成的 DESIGN.md 包含:
51
+
52
+ - **设计概述** — 目标与非目标模板
53
+ - **架构设计** — 架构图占位符
54
+ - **核心组件** — 从代码提取类列表
55
+ - **设计决策** — 决策记录表格模板
56
+ - **技术选型** — 自动检测语言和依赖
57
+ - **权衡取舍** — 已知限制和技术债务模板
58
+ - **安全考量** — 威胁模型和安全措施模板
59
+ - **变更历史** — 初始版本记录
60
+
61
+ ## 智能分析
62
+
63
+ ### 支持的语言
64
+
65
+ | 语言 | 分析能力 |
66
+ |------|----------|
67
+ | **Python** | 类、函数、文档字符串、依赖 |
68
+ | **Go** | 目录结构、依赖 |
69
+ | **TypeScript** | 目录结构、依赖 |
70
+ | **Rust** | 目录结构、依赖 |
71
+ | **其他** | 基础目录结构 |
72
+
73
+ ### 提取的信息
74
+
75
+ - 模块名称(目录名)
76
+ - 主要编程语言
77
+ - 代码文件列表
78
+ - 类和函数定义(Python)
79
+ - 文档字符串(Python)
80
+ - 依赖列表
81
+ - 入口点文件
82
+
83
+ ## 自动触发时机
84
+
85
+ | 场景 | 触发条件 |
86
+ |------|----------|
87
+ | 新建模块 | 模块创建开始时 |
88
+ | 缺失文档 | 检测到模块缺少文档时 |
89
+
90
+ ## 使用流程
91
+
92
+ ```
93
+ 1. 运行 doc_generator.js 生成骨架
94
+ 2. 填充 TODO 标记的内容
95
+ 3. 补充设计决策和理由
96
+ 4. 添加使用示例
97
+ 5. 运行 /verify-module 校验完整性
98
+ ```
99
+
100
+ ## 生成后检查清单
101
+
102
+ ### README.md
103
+
104
+ - [ ] 填充模块描述
105
+ - [ ] 补充特性列表
106
+ - [ ] 添加使用示例
107
+ - [ ] 确认依赖完整
108
+
109
+ ### DESIGN.md
110
+
111
+ - [ ] 明确设计目标
112
+ - [ ] 记录设计决策
113
+ - [ ] 说明技术选型理由
114
+ - [ ] 列出已知限制
115
+
116
+ ---
@@ -0,0 +1,435 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 文档生成器
4
+ * 自动生成/更新 README.md 和 DESIGN.md 骨架
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ // --- Utilities ---
11
+
12
+ function parseGitignore(modPath) {
13
+ const patterns = [];
14
+ const hardcoded = ['node_modules', '.git', '__pycache__', '.vscode', '.idea', 'dist', 'build', '.DS_Store'];
15
+
16
+ // 硬编码常见排除
17
+ hardcoded.forEach(p => patterns.push({ pattern: p, negate: false }));
18
+
19
+ // 解析 .gitignore
20
+ try {
21
+ const gitignorePath = path.join(modPath, '.gitignore');
22
+ const content = fs.readFileSync(gitignorePath, 'utf8');
23
+ content.split('\n').forEach(line => {
24
+ line = line.trim();
25
+ if (line && !line.startsWith('#')) {
26
+ const negate = line.startsWith('!');
27
+ if (negate) line = line.slice(1);
28
+ patterns.push({ pattern: line, negate });
29
+ }
30
+ });
31
+ } catch {}
32
+
33
+ return patterns;
34
+ }
35
+
36
+ function shouldIgnore(filePath, basePath, patterns) {
37
+ const relPath = path.relative(basePath, filePath);
38
+ const parts = relPath.split(path.sep);
39
+ const name = path.basename(filePath);
40
+
41
+ let ignored = false;
42
+ for (const {pattern, negate} of patterns) {
43
+ let match = false;
44
+ const cleanPattern = pattern.replace(/\/$/, '');
45
+
46
+ if (cleanPattern.includes('*')) {
47
+ // 通配符 → 正则:先转义特殊字符,再将 \* 还原为 [^/]*
48
+ const escaped = cleanPattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[^/]*');
49
+ const regex = new RegExp('^' + escaped + '$');
50
+ match = regex.test(name) || parts.some(p => regex.test(p));
51
+ } else if (cleanPattern.includes('/')) {
52
+ // 路径匹配:必须从头匹配或完整段匹配
53
+ match = relPath === cleanPattern || relPath.startsWith(cleanPattern + '/');
54
+ } else {
55
+ // 目录/文件名精确匹配
56
+ match = name === cleanPattern || parts.includes(cleanPattern);
57
+ }
58
+
59
+ if (match) ignored = !negate;
60
+ }
61
+ return ignored;
62
+ }
63
+
64
+ function rglob(dir, filter, basePath = dir) {
65
+ const patterns = parseGitignore(basePath);
66
+ const results = [];
67
+
68
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
69
+ const full = path.join(dir, entry.name);
70
+
71
+ if (shouldIgnore(full, basePath, patterns)) continue;
72
+
73
+ if (entry.isDirectory()) {
74
+ results.push(...rglob(full, filter, basePath));
75
+ } else if (!filter || filter(entry.name, full)) {
76
+ results.push(full);
77
+ }
78
+ }
79
+ return results;
80
+ }
81
+
82
+ // --- Language Detection ---
83
+
84
+ const LANG_MAP = {
85
+ '.py': 'Python', '.go': 'Go', '.rs': 'Rust', '.ts': 'TypeScript',
86
+ '.js': 'JavaScript', '.java': 'Java', '.c': 'C', '.cpp': 'C++',
87
+ };
88
+
89
+ function detectLanguage(modPath) {
90
+ const exts = {};
91
+ try {
92
+ for (const f of rglob(modPath)) {
93
+ const ext = path.extname(f).toLowerCase();
94
+ if (ext) exts[ext] = (exts[ext] || 0) + 1;
95
+ }
96
+ } catch { return 'Unknown'; }
97
+ const codeExts = Object.entries(exts).filter(([k]) => k in LANG_MAP);
98
+ if (codeExts.length) {
99
+ const best = codeExts.reduce((a, b) => b[1] > a[1] ? b : a);
100
+ return LANG_MAP[best[0]] || 'Unknown';
101
+ }
102
+ return 'Unknown';
103
+ }
104
+
105
+ // --- Python AST-lite extraction via regex ---
106
+
107
+ function analyzePythonModule(modPath) {
108
+ const info = makeInfo(modPath, 'Python');
109
+ const pyFiles = rglob(modPath, (name) => name.endsWith('.py'));
110
+ info.files = pyFiles.map(f => path.relative(modPath, f));
111
+
112
+ for (const pyFile of pyFiles) {
113
+ const basename = path.basename(pyFile);
114
+ if (basename.startsWith('test_') || basename.includes('_test')) continue;
115
+ let content;
116
+ try { content = fs.readFileSync(pyFile, 'utf-8'); } catch { continue; }
117
+
118
+ // Module docstring (triple-quoted at top)
119
+ if (!info.description) {
120
+ const docM = content.match(/^(?:#[^\n]*\n)*\s*(?:"""([\s\S]*?)"""|'''([\s\S]*?)''')/);
121
+ if (docM) info.description = (docM[1] || docM[2]).split('\n')[0].trim();
122
+ }
123
+
124
+ const rel = path.relative(modPath, pyFile);
125
+
126
+ // Functions
127
+ for (const m of content.matchAll(/^def\s+([A-Za-z]\w*)\s*\(/gm)) {
128
+ info.functions.push({ name: m[1], file: rel, doc: '' });
129
+ }
130
+ // Classes
131
+ for (const m of content.matchAll(/^class\s+([A-Za-z]\w*)\s*[:(]/gm)) {
132
+ info.classes.push({ name: m[1], file: rel, doc: '' });
133
+ }
134
+
135
+ // Entry points
136
+ if (['main.py', '__main__.py', 'cli.py', 'app.py'].includes(basename)) {
137
+ info.entry_points.push(rel);
138
+ }
139
+ }
140
+
141
+ // Dependencies
142
+ const reqPath = path.join(modPath, 'requirements.txt');
143
+ try {
144
+ const content = fs.readFileSync(reqPath, 'utf-8');
145
+ for (const line of content.split('\n')) {
146
+ const trimmed = line.trim();
147
+ if (trimmed && !trimmed.startsWith('#')) {
148
+ info.dependencies.push(trimmed.split(/[=><]/)[0]);
149
+ }
150
+ }
151
+ } catch {}
152
+
153
+ return info;
154
+ }
155
+
156
+ // --- Generic analysis (regex fallback) ---
157
+
158
+ const LANG_PATTERNS = {
159
+ 'Go': [/^\s*func\s+(\w+)/, /^\s*type\s+(\w+)\s+struct\b/],
160
+ 'Rust': [/^\s*(?:pub\s+)?fn\s+(\w+)/, /^\s*(?:pub\s+)?struct\s+(\w+)/],
161
+ 'TypeScript': [/^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)/, /^\s*(?:export\s+)?class\s+(\w+)/],
162
+ 'JavaScript': [/^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)/, /^\s*(?:export\s+)?class\s+(\w+)/],
163
+ 'Java': [/^\s*(?:public|private|protected)?\s*(?:static\s+)?\w+\s+(\w+)\s*\(/,
164
+ /^\s*(?:public\s+)?class\s+(\w+)/],
165
+ 'C++': [/^\s*(?:\w+\s+)+(\w+)\s*\([^;]*$/, /^\s*class\s+(\w+)/],
166
+ 'C': [/^\s*(?:\w+\s+)+(\w+)\s*\([^;]*$/, null],
167
+ };
168
+
169
+ const CODE_EXTS = new Set(['.py', '.go', '.rs', '.ts', '.js', '.java', '.c', '.cpp']);
170
+
171
+ function analyzeModule(modPath) {
172
+ const language = detectLanguage(modPath);
173
+ if (language === 'Python') return analyzePythonModule(modPath);
174
+
175
+ const info = makeInfo(modPath, language);
176
+ const [funcPat, clsPat] = LANG_PATTERNS[language] || [null, null];
177
+
178
+ try {
179
+ for (const f of rglob(modPath)) {
180
+ if (!CODE_EXTS.has(path.extname(f).toLowerCase())) continue;
181
+ const rel = path.relative(modPath, f);
182
+ info.files.push(rel);
183
+
184
+ if (!funcPat && !clsPat) continue;
185
+ let content;
186
+ try { content = fs.readFileSync(f, 'utf-8'); } catch { continue; }
187
+ for (const line of content.split('\n')) {
188
+ if (funcPat) {
189
+ const m = line.match(funcPat);
190
+ if (m && !m[1].startsWith('_')) info.functions.push({ name: m[1], file: rel, doc: '' });
191
+ }
192
+ if (clsPat) {
193
+ const m = line.match(clsPat);
194
+ if (m && !m[1].startsWith('_')) info.classes.push({ name: m[1], file: rel, doc: '' });
195
+ }
196
+ }
197
+ }
198
+ } catch {}
199
+
200
+ return info;
201
+ }
202
+
203
+ function makeInfo(modPath, language) {
204
+ return {
205
+ name: path.basename(modPath), path: modPath, description: '', language,
206
+ files: [], functions: [], classes: [], dependencies: [], entry_points: [],
207
+ };
208
+ }
209
+
210
+ // --- README Generation ---
211
+
212
+ function generateReadme(info) {
213
+ const L = [];
214
+ L.push(`# ${info.name}`, '');
215
+ if (info.description) {
216
+ L.push(info.description);
217
+ } else {
218
+ L.push('> 请在此描述模块的核心功能、解决的问题和主要用途。');
219
+ L.push('> 例如:本模块提供 X 功能,用于解决 Y 问题。');
220
+ }
221
+ L.push('', '## 概述', '', '<!-- 描述这个模块是什么,解决什么问题 -->', '');
222
+ L.push('## 特性', '', '<!-- 列出模块的主要特性,每项应包含简短描述 -->', '');
223
+ L.push('- **特性1**: 请描述第一个主要特性');
224
+ L.push('- **特性2**: 请描述第二个主要特性');
225
+ L.push('- **特性3**: 请描述第三个主要特性', '');
226
+
227
+ if (info.dependencies.length) {
228
+ L.push('## 依赖', '', '```');
229
+ info.dependencies.slice(0, 10).forEach(d => L.push(d));
230
+ if (info.dependencies.length > 10) L.push(`# ... 及其他 ${info.dependencies.length - 10} 个依赖`);
231
+ L.push('```', '');
232
+ }
233
+
234
+ L.push('## 使用方法', '');
235
+ if (info.entry_points.length) {
236
+ L.push('### 运行', '', '```bash');
237
+ const cmds = {
238
+ Python: `python -m ${info.name}`, Go: 'go run ./cmd/main.go',
239
+ Rust: 'cargo run', TypeScript: 'npm start', JavaScript: 'npm start'
240
+ };
241
+ L.push(cmds[info.language] || `# 请根据 ${info.language} 项目结构添加运行命令`);
242
+ L.push('```', '');
243
+ }
244
+
245
+ L.push('### 示例', '');
246
+ const EXAMPLES = {
247
+ Python: `from ${info.name.toLowerCase()} import main\n\n` +
248
+ `# 初始化\nobj = main()\n\n# 执行操作\nresult = obj.process()\nprint(result)`,
249
+ Go: `package main\n\nimport "${info.name.toLowerCase()}"\n\nfunc main() {\n` +
250
+ ` // 初始化\n obj := ${info.name.toLowerCase()}.New()\n` +
251
+ `\n // 执行操作\n result := obj.Process()\n println(result)\n}`,
252
+ Rust: `use ${info.name.toLowerCase()}::*;\n\nfn main() {\n` +
253
+ ` // 初始化\n let obj = Object::new();\n\n` +
254
+ ` // 执行操作\n let result = obj.process();\n` +
255
+ ` println!("{}", result);\n}`,
256
+ TypeScript: `import { main } from "./${info.name.toLowerCase()}";\n\n` +
257
+ `// 初始化\nconst obj = new main();\n\n` +
258
+ `// 执行操作\nconst result = obj.process();\nconsole.log(result);`,
259
+ JavaScript: `const { main } = require("./${info.name.toLowerCase()}");\n\n` +
260
+ `// 初始化\nconst obj = new main();\n\n` +
261
+ `// 执行操作\nconst result = obj.process();\nconsole.log(result);`,
262
+ };
263
+ if (EXAMPLES[info.language]) {
264
+ L.push('```' + info.language.toLowerCase(), EXAMPLES[info.language], '```');
265
+ } else {
266
+ L.push('```' + info.language.toLowerCase());
267
+ L.push(`<!-- 请根据 ${info.language} 语言特性提供使用示例 -->`);
268
+ L.push(`<!-- 示例应包含:初始化、基本操作、结果处理 -->`);
269
+ L.push('```');
270
+ }
271
+ L.push('');
272
+
273
+ if (info.classes.length || info.functions.length) {
274
+ L.push('## API 概览', '');
275
+ if (info.classes.length) {
276
+ L.push('### 类', '', '| 类名 | 描述 |', '|------|------|');
277
+ info.classes.slice(0, 10).forEach(c => L.push(`| \`${c.name}\` | ${c.doc || '请补充此类的功能描述'} |`));
278
+ L.push('');
279
+ }
280
+ if (info.functions.length) {
281
+ L.push('### 函数', '', '| 函数 | 描述 |', '|------|------|');
282
+ info.functions.slice(0, 10).forEach(f => L.push(`| \`${f.name}()\` | ${f.doc || '请补充此函数的功能描述'} |`));
283
+ L.push('');
284
+ }
285
+ }
286
+
287
+ L.push('## 目录结构', '', '```', `${info.name}/`);
288
+ info.files.sort().slice(0, 15).forEach(f => L.push(`├── ${f}`));
289
+ if (info.files.length > 15) L.push(`└── ... (${info.files.length - 15} more files)`);
290
+ L.push('```', '');
291
+ L.push('## 相关文档', '', '- [设计文档](DESIGN.md)', '');
292
+ return L.join('\n');
293
+ }
294
+
295
+ // --- DESIGN Generation ---
296
+
297
+ function generateDesign(info) {
298
+ const today = new Date().toISOString().slice(0, 10);
299
+ const L = [];
300
+ L.push(`# ${info.name} 设计文档`, '');
301
+ L.push('## 设计概述', '', '### 目标', '', '<!-- 这个模块要解决什么问题? -->', '');
302
+ L.push('### 非目标', '', '<!-- 这个模块明确不做什么? -->', '');
303
+ L.push('## 架构设计', '', '### 整体架构', '', '```');
304
+ L.push('┌─────────────────────────────────────┐');
305
+ L.push('│ 请在此绘制模块的整体架构图 │');
306
+ L.push('│ 包括主要组件、数据流、依赖关系 │');
307
+ L.push('│ 可使用 ASCII 图或 Mermaid 图表 │');
308
+ L.push('└─────────────────────────────────────┘');
309
+ L.push('```', '');
310
+ L.push('### 核心组件', '');
311
+ if (info.classes.length) {
312
+ info.classes.slice(0, 5).forEach(c => L.push(`- **${c.name}**: ${c.doc || '请描述此组件的职责和功能'}`));
313
+ } else {
314
+ L.push('<!-- 列出模块的核心组件及其职责 -->');
315
+ L.push('- **组件1**: 请描述第一个核心组件的职责');
316
+ L.push('- **组件2**: 请描述第二个核心组件的职责');
317
+ L.push('- **组件3**: 请描述第三个核心组件的职责');
318
+ }
319
+ L.push('');
320
+ L.push('## 设计决策', '', '### 决策记录', '');
321
+ L.push('| 日期 | 决策 | 理由 | 影响 |', '|------|------|------|------|');
322
+ L.push(`| ${today} | 初始设计 | - | - |`, '');
323
+ L.push('### 技术选型', '', `- **语言**: ${info.language}`);
324
+ if (info.dependencies.length) L.push(`- **主要依赖**: ${info.dependencies.slice(0, 5).join(', ')}`);
325
+ L.push('- **理由**: <!-- 请说明为什么选择这些技术栈,包括性能、可维护性、生态等考量 -->', '');
326
+ L.push('## 权衡取舍', '', '### 已知限制', '');
327
+ L.push('<!-- 列出模块的已知限制和约束条件 -->');
328
+ L.push('- **限制1**: 请描述第一个已知限制及其原因');
329
+ L.push('- **限制2**: 请描述第二个已知限制及其原因', '');
330
+ L.push('### 技术债务', '');
331
+ L.push('<!-- 记录有意引入的技术债务、临时方案及其原因 -->');
332
+ L.push('- **债务1**: 描述 | 原因:性能优先 | 计划偿还时间:v2.0', '');
333
+ L.push('## 安全考量', '', '### 威胁模型', '');
334
+ L.push('<!-- 识别潜在的安全威胁,如认证、授权、数据泄露等 -->');
335
+ L.push('- **威胁1**: 请描述潜在威胁及其影响');
336
+ L.push('- **威胁2**: 请描述潜在威胁及其影响', '');
337
+ L.push('### 安全措施', '');
338
+ L.push('<!-- 列出已实施的安全措施,如输入验证、加密、访问控制等 -->');
339
+ L.push('- **措施1**: 请描述已实施的安全措施');
340
+ L.push('- **措施2**: 请描述已实施的安全措施', '');
341
+ L.push('## 变更历史', '', `### ${today} - 初始版本`, '');
342
+ L.push('**变更内容**: 创建模块', '', '**变更理由**: 初始开发', '');
343
+ return L.join('\n');
344
+ }
345
+
346
+ // --- Core: generate_docs ---
347
+
348
+ function generateDocs(targetPath, force) {
349
+ const modPath = path.resolve(targetPath);
350
+ const result = { readme: null, design: null, status: 'success', messages: [] };
351
+
352
+ if (!fs.existsSync(modPath)) {
353
+ result.status = 'error';
354
+ result.messages.push(`路径不存在: ${modPath}`);
355
+ return result;
356
+ }
357
+
358
+ const info = analyzeModule(modPath);
359
+
360
+ const readmePath = path.join(modPath, 'README.md');
361
+ if (fs.existsSync(readmePath) && !force) {
362
+ result.messages.push('README.md 已存在,跳过(使用 --force 覆盖)');
363
+ } else {
364
+ fs.writeFileSync(readmePath, generateReadme(info));
365
+ result.readme = readmePath;
366
+ result.messages.push('已生成 README.md');
367
+ }
368
+
369
+ const designPath = path.join(modPath, 'DESIGN.md');
370
+ if (fs.existsSync(designPath) && !force) {
371
+ result.messages.push('DESIGN.md 已存在,跳过(使用 --force 覆盖)');
372
+ } else {
373
+ fs.writeFileSync(designPath, generateDesign(info));
374
+ result.design = designPath;
375
+ result.messages.push('已生成 DESIGN.md');
376
+ }
377
+
378
+ return result;
379
+ }
380
+
381
+ // --- CLI ---
382
+
383
+ function parseArgs(argv) {
384
+ const args = { path: '.', force: false, json: false, readmeOnly: false, designOnly: false };
385
+ const rest = argv.slice(2);
386
+ const positional = [];
387
+ for (const a of rest) {
388
+ if (a === '-f' || a === '--force') args.force = true;
389
+ else if (a === '--json') args.json = true;
390
+ else if (a === '--readme-only') args.readmeOnly = true;
391
+ else if (a === '--design-only') args.designOnly = true;
392
+ else if (a === '-h' || a === '--help') {
393
+ console.log('Usage: doc_generator.js [path] [-f|--force] [--json] [--readme-only] [--design-only]');
394
+ process.exit(0);
395
+ } else positional.push(a);
396
+ }
397
+ if (positional.length) args.path = positional[0];
398
+ return args;
399
+ }
400
+
401
+ function main() {
402
+ const args = parseArgs(process.argv);
403
+ const result = generateDocs(args.path, args.force);
404
+
405
+ if (args.json) {
406
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
407
+ } else {
408
+ console.log('='.repeat(50));
409
+ console.log('文档生成报告');
410
+ console.log('='.repeat(50));
411
+ for (const msg of result.messages) {
412
+ console.log(` \u2022 ${msg}`);
413
+ }
414
+ console.log('='.repeat(50));
415
+ }
416
+
417
+ process.exitCode = result.status === 'success' ? 0 : 1;
418
+ }
419
+
420
+ if (require.main === module) {
421
+ main();
422
+ }
423
+
424
+ module.exports = {
425
+ parseGitignore,
426
+ shouldIgnore,
427
+ rglob,
428
+ detectLanguage,
429
+ analyzeModule,
430
+ generateReadme,
431
+ generateDesign,
432
+ generateDocs,
433
+ parseArgs,
434
+ main,
435
+ };
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 验证工具共享库
5
+ * 消灭 verify-* 脚本间的重复代码
6
+ */
7
+
8
+ // --- CLI 参数解析 ---
9
+
10
+ function parseCliArgs(argv, extraFlags) {
11
+ const args = argv.slice(2);
12
+ const result = { target: '.', verbose: false, json: false };
13
+ if (extraFlags) Object.assign(result, extraFlags);
14
+
15
+ for (let i = 0; i < args.length; i++) {
16
+ if (args[i] === '-v' || args[i] === '--verbose') result.verbose = true;
17
+ else if (args[i] === '--json') result.json = true;
18
+ else if (args[i] === '-h' || args[i] === '--help') { result.help = true; }
19
+ else if (args[i] === '--mode' && args[i + 1]) { result.mode = args[++i]; }
20
+ else if (args[i] === '--exclude') {
21
+ result.exclude = result.exclude || [];
22
+ while (i + 1 < args.length && !args[i + 1].startsWith('-')) result.exclude.push(args[++i]);
23
+ }
24
+ else if (!args[i].startsWith('-')) result.target = args[i];
25
+ }
26
+ return result;
27
+ }
28
+
29
+ // --- 报告格式化 ---
30
+
31
+ const SEP = '='.repeat(60);
32
+ const DASH = '-'.repeat(40);
33
+ const ICONS = {
34
+ error: '\u2717', warning: '\u26A0', info: '\u2139',
35
+ critical: '\u{1F534}', high: '\u{1F7E0}', medium: '\u{1F7E1}', low: '\u{1F535}'
36
+ };
37
+
38
+ function reportHeader(title, fields) {
39
+ const lines = [SEP, title, SEP];
40
+ for (const [k, v] of Object.entries(fields)) {
41
+ lines.push(`\n${k}: ${v}`);
42
+ }
43
+ return lines;
44
+ }
45
+
46
+ function reportIssues(issues, verbose, groupBy) {
47
+ if (!issues.length) return [];
48
+ const lines = ['\n' + DASH, '问题列表:', DASH];
49
+
50
+ if (groupBy) {
51
+ const groups = {};
52
+ for (const i of issues) (groups[i[groupBy]] || (groups[i[groupBy]] = [])).push(i);
53
+ for (const cat of Object.keys(groups).sort()) {
54
+ const items = groups[cat];
55
+ lines.push(`\n【${cat}】(${items.length} 个)`);
56
+ for (const i of items.slice(0, 10)) {
57
+ lines.push(` ${ICONS[i.severity] || '\u2139'} ` +
58
+ `${i.file_path || ''}${i.line_number ? ':' + i.line_number : ''}`);
59
+ lines.push(` ${i.message}`);
60
+ if (verbose && i.suggestion) lines.push(` \u{1F4A1} ${i.suggestion}`);
61
+ if (verbose && i.recommendation) lines.push(` \u{1F4A1} ${i.recommendation}`);
62
+ }
63
+ if (items.length > 10) lines.push(` ... 及其他 ${items.length - 10} 个问题`);
64
+ }
65
+ } else {
66
+ for (const i of issues) {
67
+ const icon = ICONS[i.severity] || '\u2139';
68
+ lines.push(` ${icon} [${i.severity.toUpperCase()}] ${i.message}`);
69
+ if (i.path && verbose) lines.push(` 路径: ${i.path}`);
70
+ }
71
+ }
72
+ return lines;
73
+ }
74
+
75
+ function reportFooter() { return ['\n' + SEP]; }
76
+
77
+ function buildReport(title, fields, issues, verbose, groupBy) {
78
+ return [...reportHeader(title, fields), ...reportIssues(issues, verbose, groupBy), ...reportFooter()].join('\n');
79
+ }
80
+
81
+ // --- 通用计数 ---
82
+
83
+ function countBySeverity(issues, field) {
84
+ field = field || 'severity';
85
+ const counts = {};
86
+ for (const i of issues) counts[i[field]] = (counts[i[field]] || 0) + 1;
87
+ return counts;
88
+ }
89
+
90
+ function hasFatal(issues, fatalLevels) {
91
+ fatalLevels = fatalLevels || ['error'];
92
+ return issues.some(i => fatalLevels.includes(i.severity));
93
+ }
94
+
95
+ module.exports = {
96
+ parseCliArgs, buildReport, reportHeader, reportIssues,
97
+ reportFooter, countBySeverity, hasFatal, SEP, DASH, ICONS
98
+ };