@rongyan/opensource-analyzer 0.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.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # @rongyan/opensource-analyzer
2
+
3
+ 把 `opensource-analyzer` 技能作为 npm 包分发,并通过 `npx` 安装到不同 AI Agent 的技能目录。
4
+
5
+ ## 使用
6
+
7
+ ```bash
8
+ npx @rongyan/opensource-analyzer install --target codex
9
+ npx @rongyan/opensource-analyzer install --target claude-code
10
+ npx @rongyan/opensource-analyzer install --target opencode
11
+ ```
12
+
13
+ ## 安装位置
14
+
15
+ | Target | 默认安装目录 |
16
+ | --- | --- |
17
+ | `codex` | `$CODEX_HOME/skills/opensource-analyzer`,未设置时为 `~/.codex/skills/opensource-analyzer` |
18
+ | `claude-code` | `~/.claude/skills/opensource-analyzer` |
19
+ | `opencode` | `$XDG_CONFIG_HOME/opencode/skills/opensource-analyzer`,未设置时为 `~/.config/opencode/skills/opensource-analyzer` |
20
+
21
+ ## CLI 选项
22
+
23
+ ```bash
24
+ npx @rongyan/opensource-analyzer install --target codex --dry-run
25
+ npx @rongyan/opensource-analyzer install --target codex --force
26
+ npx @rongyan/opensource-analyzer install --target codex --dest /custom/skill/path
27
+ ```
28
+
29
+ - `--force`:覆盖已有但不是由本包安装的同名技能目录中的技能文件。
30
+ - `--dry-run`:只展示目标路径,不写入文件。
31
+ - `--dest`:覆盖默认安装目录,主要用于测试或自定义 Agent 配置。
32
+
33
+ ## 发布
34
+
35
+ ```bash
36
+ npm test
37
+ npm pack --dry-run
38
+ npm publish --access public
39
+ ```
40
+
41
+ 发布前请根据你的授权方式更新 `package.json` 的 `license` 字段;当前保守标记为 `UNLICENSED`。
package/SKILL.md ADDED
@@ -0,0 +1,179 @@
1
+ ---
2
+ name: opensource-analyzer
3
+ description: 深度分析 GitHub 或本地开源项目,生成结构化介绍文章、选型报告或分享稿。Use when the user asks to analyze an open-source project, explain a GitHub repository, research a library, compare similar projects, write a technical article about a repo, or evaluate whether a project is production-ready. 中文请求默认输出中文,技术术语保留英文原文。
4
+ ---
5
+
6
+ # 开源项目分析 SKILL
7
+
8
+ ## 目标
9
+ 基于用户提供的 GitHub 仓库、包名或本地仓库路径,按六层分析框架完成深度研究,最终生成可直接发布的介绍文章、选型报告或分享稿。
10
+
11
+ ---
12
+
13
+ ## 第一步:信息收集
14
+
15
+ 在分析前,使用可用的 Web、GitHub、包管理器或本地文件工具抓取以下数据。所有动态数据都要注明查询日期;如果无法获取,不要编造,直接标注"未查到"或"无法验证"。
16
+
17
+ **来源优先级:**
18
+ 1. 官方仓库、README、文档站、Release notes、源码
19
+ 2. GitHub Issues/Discussions/PR、维护者博客
20
+ 3. 包管理器页面(npm/PyPI/crates.io 等)、基准测试、第三方测评
21
+ 4. Hacker News、Reddit、社交媒体讨论(仅作社区反馈,不当成事实来源)
22
+
23
+ **必须获取:**
24
+ - 仓库主页(README 全文)
25
+ - Star 数、Fork 数、Watch 数
26
+ - 最近 commit 时间、commit 频率(过去 30 天)
27
+ - Issue 数量(open vs closed)、PR 状态
28
+ - License 类型
29
+ - 主要编程语言占比
30
+ - 贡献者数量及核心贡献者背景
31
+
32
+ **尽量获取:**
33
+ - Releases 页面(最新版本、更新节奏)
34
+ - 项目官网或文档站
35
+ - 相关技术博客、论文或讨论(Hacker News、Reddit 等)
36
+ - npm/PyPI/crates.io 等包管理器的下载量
37
+
38
+ > 如果仓库是私有的或无法访问,告知用户并请求提供 README 文本或代码片段。
39
+ > 如果用户提供本地路径或当前工作区,优先读取本地 README、源码、配置和测试;外部数据只用于补充维护状态、社区热度和生态采用情况。
40
+ > 默认不要执行未知仓库的代码。确需运行示例或测试时,先说明风险,并尽量使用隔离环境。
41
+
42
+ ---
43
+
44
+ ## 第二步:六层分析框架
45
+
46
+ 按顺序完成每一层,每层都要形成明确结论,不要跳过。
47
+
48
+ ### 层1:定位分析
49
+ 回答三个问题:
50
+ 1. **诞生背景**:在什么时间点、什么技术背景下出现?有没有明确的"替代品"或"竞争对手"?
51
+ 2. **核心痛点**:它解决了什么已有方案解决不好的问题?(直接引用 README 中的原文定位)
52
+ 3. **目标用户**:个人开发者?企业用户?特定语言/框架生态的用户?
53
+
54
+ ### 层2:技术解剖
55
+ 1. **架构概览**:核心模块有哪些?整体数据流是什么?
56
+ 2. **关键设计决策**:有没有打破常规的实现方式?(例如:不用 ORM 而直接写 SQL、用 Rust 重写性能关键路径等)
57
+ 3. **代码亮点**:找到 1-2 个最能体现该项目价值的核心代码片段或 API 设计,并解释为什么巧妙
58
+ 4. **依赖分析**:主要依赖什么?有没有重量级依赖可能带来问题?
59
+
60
+ ### 层3:生态评估
61
+ 1. **维护健康度**:最近 commit 距今多久?Issue 响应速度如何?有无长期悬而未决的关键 bug?
62
+ 2. **社区活跃度**:贡献者多样性、Discord/Slack/论坛是否活跃
63
+ 3. **采用情况**:谁在用?有没有知名公司背书或 case study?
64
+ 4. **竞品对比**:列出 2-3 个同类方案,对比核心差异(见 references/competitor-matrix.md 获取对比维度模板)
65
+
66
+ ### 层4:实战评价
67
+ 1. **上手曲线**:安装配置需要几步?有无前置知识门槛?
68
+ 2. **典型使用场景**:最适合什么规模/场景的项目?
69
+ 3. **已知局限**:文档或 issue 中提到的主要限制、性能瓶颈、不支持的场景
70
+ 4. **生产就绪度**:是否有人在生产环境中大规模使用?稳定性如何?
71
+
72
+ ### 层5:风险与成本
73
+ 1. **技术风险**:API 稳定性、破坏性变更频率、性能边界、安全漏洞历史
74
+ 2. **维护风险**:bus factor、核心维护者是否活跃、关键 issue 是否长期未解决
75
+ 3. **迁移成本**:引入成本、替换成本、锁定效应、与现有技术栈的耦合
76
+ 4. **合规风险**:License 是否允许商业使用,有无依赖许可证冲突
77
+
78
+ ### 层6:综合判断
79
+ 给出明确观点,而不是"视情况而定"。
80
+ - **适合谁**:列出最匹配的用户、团队规模和项目阶段
81
+ - **不适合谁**:列出至少 2 类不建议采用的场景
82
+ - **选型结论**:推荐/谨慎试用/暂不推荐,并说明关键理由
83
+ - **评分(选型类文章必需)**:从易用性、文档质量、性能、社区活跃度、生产可靠性给 1-5 分
84
+
85
+ ---
86
+
87
+ ## 第三步:文章生成
88
+
89
+ ### 确认受众
90
+ 在写作前,先明确目标读者(如果用户没有说明,默认为"中级开发者"):
91
+ - **入门级**:少用技术术语,多用类比,重点放在"能做什么"
92
+ - **中级开发者**:平衡原理与实践,给出可运行的代码示例
93
+ - **高级/架构师**:深入设计决策与权衡,关注性能、可扩展性
94
+
95
+ ### 文章结构模板
96
+
97
+ 严格按以下结构写作,每个部分都不能省略:
98
+
99
+ ```
100
+ # {项目名}:{一句话价值主张}
101
+
102
+ ## TL;DR
103
+ (3-5 句话:是什么、解决什么问题、适合谁用)
104
+
105
+ ## 背景:为什么需要它
106
+ (讲清楚现有方案的痛点,引出这个项目的出现)
107
+
108
+ ## 核心功能
109
+ (2-4 个核心功能,每个配一个最小代码示例)
110
+
111
+ ## 它是怎么工作的
112
+ (架构原理,用图表或伪代码辅助说明,重点讲设计亮点)
113
+
114
+ ## 谁在用它,怎么用的
115
+ (真实案例或典型使用场景,避免空泛)
116
+
117
+ ## 优势与局限
118
+ (客观列出,局限部分至少 2 条,这是文章可信度的关键)
119
+
120
+ ## 5 分钟上手
121
+ (最精简的安装 + 运行步骤,确保读者能跑起来)
122
+
123
+ ## 与同类方案对比
124
+ (对比表格,聚焦差异而非罗列所有特性)
125
+
126
+ ## 总结与展望
127
+ (作者观点:适合场景、不适合场景、对未来的判断)
128
+ ```
129
+
130
+ ### 写作规范
131
+ - **代码示例**:每个可演示的功能点至少一个可运行的最小示例;如果不是库项目,用 CLI、配置或伪代码展示核心工作流
132
+ - **客观性**:不要只写优点,局限性部分要具体(避免"有些场景不适用"这种废话)
133
+ - **引用来源**:技术描述尽量引用官方文档或源码,不要凭空描述
134
+ - **不确定性**:对无法验证的采用案例、性能数据或维护状态,要明确写出不确定性
135
+ - **长度控制**:技术博客文章目标 2000-4000 字,选型报告可到 5000 字
136
+
137
+ ---
138
+
139
+ ## 特殊场景处理
140
+
141
+ ### 场景:用户给了本地仓库路径
142
+ 读取本地 README、package/pyproject/Cargo 等配置、目录结构、核心源码和测试。再按需联网补充 Star、Release、Issue、包下载量等动态信息。
143
+
144
+ ### 场景:用户只给了一个库名,没有 URL
145
+ 先用 web_search 确认仓库地址,确认后再开始分析。如果有多个同名仓库,列出让用户选择。
146
+
147
+ ### 场景:用户要做选型对比
148
+ 参考 `references/competitor-matrix.md`,生成结构化的对比矩阵,最后给出明确建议("推荐 A 用于 X 场景,推荐 B 用于 Y 场景")。
149
+
150
+ ### 场景:仓库很新(< 6 个月)或 Star 数很少(< 500)
151
+ 在文章开头加风险提示:"该项目尚处于早期阶段,API 可能不稳定,不建议直接用于生产环境。"
152
+
153
+ ### 场景:用户要求中文文章
154
+ 默认输出中文,技术术语保留英文原文(如:`middleware`、`tree-shaking`),不要强行翻译成生僻中文。
155
+
156
+ ### 场景:用户要生成 PPT/分享稿而非文章
157
+ 参考六层框架,但输出结构改为:背景(1页)→ 核心价值(1页)→ 工作原理(2页)→ Demo(1页)→ 适用场景(1页)→ Q&A。
158
+
159
+ ---
160
+
161
+ ## 质量检查清单
162
+
163
+ 文章写完后,逐项自检:
164
+
165
+ - [ ] 有没有说清楚"它解决了什么问题"(不只是"它能做什么")
166
+ - [ ] 动态数据有没有标注查询日期?
167
+ - [ ] 代码示例能直接复制运行吗?
168
+ - [ ] 局限性部分有 2 条以上具体描述
169
+ - [ ] 有竞品对比
170
+ - [ ] 有上手指南(不依赖读者自己找文档)
171
+ - [ ] 有对"适合谁"的明确定位
172
+ - [ ] 对无法验证的信息有没有明确标注不确定性?
173
+
174
+ ---
175
+
176
+ ## 参考文件
177
+
178
+ - `references/competitor-matrix.md`:用户要求选型对比或需要竞品矩阵时读取
179
+ - `references/article-examples.md`:需要校准文章质量、开头写法、局限性表达或结论立场时读取
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Open Source Analyzer"
3
+ short_description: "Analyze open-source projects and repos"
4
+ default_prompt: "Use $opensource-analyzer to analyze this GitHub repository: https://github.com/owner/repo"
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("node:fs");
5
+ const os = require("node:os");
6
+ const path = require("node:path");
7
+
8
+ const packageRoot = path.resolve(__dirname, "..");
9
+ const packageJson = require(path.join(packageRoot, "package.json"));
10
+
11
+ const skillName = "opensource-analyzer";
12
+ const manifestFile = ".opensource-analyzer-install.json";
13
+ const installItems = ["SKILL.md", "references", "agents"];
14
+
15
+ const targetAliases = {
16
+ claude: "claude-code",
17
+ claudecode: "claude-code",
18
+ "claude_code": "claude-code",
19
+ "open-code": "opencode",
20
+ open_code: "opencode"
21
+ };
22
+
23
+ const targets = {
24
+ codex: {
25
+ label: "Codex",
26
+ directory() {
27
+ const codexHome = process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
28
+ return path.join(codexHome, "skills", skillName);
29
+ }
30
+ },
31
+ "claude-code": {
32
+ label: "Claude Code",
33
+ directory() {
34
+ return path.join(os.homedir(), ".claude", "skills", skillName);
35
+ }
36
+ },
37
+ opencode: {
38
+ label: "OpenCode",
39
+ directory() {
40
+ const configHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
41
+ return path.join(configHome, "opencode", "skills", skillName);
42
+ }
43
+ }
44
+ };
45
+
46
+ function printHelp() {
47
+ console.log(`@rongyan/opensource-analyzer ${packageJson.version}
48
+
49
+ Usage:
50
+ npx @rongyan/opensource-analyzer install --target codex
51
+ npx @rongyan/opensource-analyzer install --target claude-code
52
+ npx @rongyan/opensource-analyzer install --target opencode
53
+
54
+ Commands:
55
+ install Install or update the skill for one agent.
56
+
57
+ Options:
58
+ --target <agent> codex, claude-code, or opencode.
59
+ --dest <path> Override the final skill directory.
60
+ --force Overwrite an existing unmanaged skill directory.
61
+ --dry-run Show what would be installed without writing files.
62
+ -h, --help Show this help.
63
+ -v, --version Show package version.
64
+ `);
65
+ }
66
+
67
+ function fail(message) {
68
+ console.error(`Error: ${message}`);
69
+ process.exitCode = 1;
70
+ }
71
+
72
+ function parseArgs(argv) {
73
+ const options = {
74
+ command: argv[0],
75
+ target: undefined,
76
+ dest: undefined,
77
+ force: false,
78
+ dryRun: false,
79
+ help: false,
80
+ version: false
81
+ };
82
+
83
+ for (let index = 0; index < argv.length; index += 1) {
84
+ const arg = argv[index];
85
+
86
+ if (arg === "-h" || arg === "--help") {
87
+ options.help = true;
88
+ } else if (arg === "-v" || arg === "--version") {
89
+ options.version = true;
90
+ } else if (arg === "--force") {
91
+ options.force = true;
92
+ } else if (arg === "--dry-run") {
93
+ options.dryRun = true;
94
+ } else if (arg === "--target") {
95
+ options.target = readOptionValue(argv, index, "--target");
96
+ index += 1;
97
+ } else if (arg.startsWith("--target=")) {
98
+ options.target = arg.slice("--target=".length);
99
+ } else if (arg === "--dest") {
100
+ options.dest = readOptionValue(argv, index, "--dest");
101
+ index += 1;
102
+ } else if (arg.startsWith("--dest=")) {
103
+ options.dest = arg.slice("--dest=".length);
104
+ } else if (index > 0 && !arg.startsWith("-")) {
105
+ fail(`Unknown argument: ${arg}`);
106
+ return null;
107
+ } else if (index > 0) {
108
+ fail(`Unknown option: ${arg}`);
109
+ return null;
110
+ }
111
+ }
112
+
113
+ return options;
114
+ }
115
+
116
+ function readOptionValue(argv, index, optionName) {
117
+ const value = argv[index + 1];
118
+ if (!value || value.startsWith("-")) {
119
+ fail(`${optionName} requires a value.`);
120
+ process.exit(1);
121
+ }
122
+ return value;
123
+ }
124
+
125
+ function normalizeTarget(rawTarget) {
126
+ if (!rawTarget) {
127
+ return undefined;
128
+ }
129
+
130
+ const normalized = rawTarget.toLowerCase();
131
+ return targetAliases[normalized] || normalized;
132
+ }
133
+
134
+ function readManifest(destination) {
135
+ const manifestPath = path.join(destination, manifestFile);
136
+ if (!fs.existsSync(manifestPath)) {
137
+ return null;
138
+ }
139
+
140
+ try {
141
+ return JSON.parse(fs.readFileSync(manifestPath, "utf8"));
142
+ } catch {
143
+ return null;
144
+ }
145
+ }
146
+
147
+ function assertSkillSources() {
148
+ const missing = installItems.filter((item) => !fs.existsSync(path.join(packageRoot, item)));
149
+ if (missing.length > 0) {
150
+ fail(`Package is missing required skill file(s): ${missing.join(", ")}`);
151
+ process.exit(1);
152
+ }
153
+ }
154
+
155
+ function copyItem(source, destination) {
156
+ const stat = fs.statSync(source);
157
+ if (stat.isDirectory()) {
158
+ fs.rmSync(destination, { recursive: true, force: true });
159
+ fs.cpSync(source, destination, { recursive: true });
160
+ return;
161
+ }
162
+
163
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
164
+ fs.copyFileSync(source, destination);
165
+ }
166
+
167
+ function writeManifest(destination, target) {
168
+ const manifest = {
169
+ packageName: packageJson.name,
170
+ packageVersion: packageJson.version,
171
+ skillName,
172
+ target,
173
+ installedAt: new Date().toISOString(),
174
+ managedFiles: installItems
175
+ };
176
+
177
+ fs.writeFileSync(
178
+ path.join(destination, manifestFile),
179
+ `${JSON.stringify(manifest, null, 2)}\n`,
180
+ "utf8"
181
+ );
182
+ }
183
+
184
+ function installSkill(options) {
185
+ const targetName = normalizeTarget(options.target);
186
+ if (!targetName || !targets[targetName]) {
187
+ fail("Missing or invalid --target. Use one of: codex, claude-code, opencode.");
188
+ process.exit(1);
189
+ }
190
+
191
+ assertSkillSources();
192
+
193
+ const destination = path.resolve(options.dest || targets[targetName].directory());
194
+ const existing = fs.existsSync(destination);
195
+ const manifest = existing ? readManifest(destination) : null;
196
+ const isManaged = Boolean(manifest && manifest.packageName === packageJson.name);
197
+
198
+ if (existing && !fs.statSync(destination).isDirectory()) {
199
+ fail(`Destination exists but is not a directory: ${destination}`);
200
+ process.exit(1);
201
+ }
202
+
203
+ if (existing && !isManaged && !options.force) {
204
+ fail(
205
+ `Destination already exists and was not installed by this package: ${destination}\n` +
206
+ "Use --force to overwrite the skill files there, or choose another --dest."
207
+ );
208
+ process.exit(1);
209
+ }
210
+
211
+ const action = existing ? "Update" : "Install";
212
+ const label = targets[targetName].label;
213
+ console.log(`${action} ${skillName} for ${label}`);
214
+ console.log(`Destination: ${destination}`);
215
+
216
+ if (options.dryRun) {
217
+ console.log("Dry run: no files written.");
218
+ return;
219
+ }
220
+
221
+ fs.mkdirSync(destination, { recursive: true });
222
+ for (const item of installItems) {
223
+ copyItem(path.join(packageRoot, item), path.join(destination, item));
224
+ }
225
+ writeManifest(destination, targetName);
226
+
227
+ console.log(`Done. Restart or reload ${label} if it does not pick up new skills immediately.`);
228
+ }
229
+
230
+ function main() {
231
+ const options = parseArgs(process.argv.slice(2));
232
+ if (!options) {
233
+ return;
234
+ }
235
+
236
+ if (options.version) {
237
+ console.log(packageJson.version);
238
+ return;
239
+ }
240
+
241
+ if (options.help || !options.command) {
242
+ printHelp();
243
+ return;
244
+ }
245
+
246
+ if (options.command !== "install") {
247
+ fail(`Unknown command: ${options.command}`);
248
+ process.exit(1);
249
+ }
250
+
251
+ installSkill(options);
252
+ }
253
+
254
+ main();
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@rongyan/opensource-analyzer",
3
+ "version": "0.1.0",
4
+ "description": "Install the opensource-analyzer AI agent skill with npx.",
5
+ "type": "commonjs",
6
+ "bin": {
7
+ "opensource-analyzer": "./bin/opensource-analyzer.js"
8
+ },
9
+ "files": [
10
+ "SKILL.md",
11
+ "references",
12
+ "agents",
13
+ "bin",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "test": "node scripts/smoke-test.js",
18
+ "pack:dry-run": "npm pack --dry-run"
19
+ },
20
+ "keywords": [
21
+ "ai-agent",
22
+ "codex-skill",
23
+ "claude-code-skill",
24
+ "opencode-skill",
25
+ "opensource"
26
+ ],
27
+ "engines": {
28
+ "node": ">=18.0.0"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "license": "UNLICENSED"
34
+ }
@@ -0,0 +1,87 @@
1
+ # 高质量开源库介绍文章的特征分析
2
+
3
+ 本文档总结了优秀技术文章的共同特征,供写作时参考对标。
4
+
5
+ ---
6
+
7
+ ## 什么让一篇开源库介绍文章变得出色
8
+
9
+ ### 1. 开篇立即抓住痛点
10
+ **差的写法:** "今天介绍一个很棒的开源库叫做 Zod,它是一个 TypeScript 优先的模式验证库。"
11
+ **好的写法:** "你有没有写过这样的代码:后端 API 定义了类型,前端 TypeScript 也定义了类型,但两边的类型完全独立,一旦后端改了字段,前端只能在运行时才发现?Zod 要解决的就是这个问题。"
12
+
13
+ **原则:** 开篇应该让读者说"对!这就是我的问题",而不是描述这个库是什么。
14
+
15
+ ---
16
+
17
+ ### 2. 最小可用示例要真正"最小"
18
+ **差的写法:** 给出一个需要先配置 5 个文件才能运行的示例
19
+ **好的写法:**
20
+ ```typescript
21
+ // 3 行代码展示核心价值
22
+ import { z } from 'zod'
23
+ const User = z.object({ name: z.string(), age: z.number() })
24
+ User.parse({ name: "Alice", age: "25" }) // 抛出错误:age 应为 number
25
+ ```
26
+
27
+ **原则:** 好的最小示例让读者在 30 秒内理解这个库"值在哪里"。
28
+
29
+ ---
30
+
31
+ ### 3. 原理解释要有"啊哈"时刻
32
+ 单纯列举 API 是文档的工作,不是文章的工作。好的原理解释应该让读者理解设计者"为什么这样设计"。
33
+
34
+ **方法:**
35
+ - 先展示"不用这个库,你会怎么做"——揭示繁琐之处
36
+ - 再展示"用了之后"——对比落差产生啊哈时刻
37
+ - 解释关键设计决策背后的权衡
38
+
39
+ ---
40
+
41
+ ### 4. 局限性要具体,不要模糊
42
+ **差的写法:** "在某些复杂场景下可能存在性能问题。"
43
+ **好的写法:** "当 schema 嵌套超过 5 层时,类型推导的 TypeScript 编译速度会明显下降。如果你的项目有大量深层嵌套的类型,需要考虑这个问题(issue #xxx 有详细讨论)。"
44
+
45
+ ---
46
+
47
+ ### 5. 结论要有立场
48
+ **差的写法:** "总体来说,这是一个不错的库,适合需要验证的项目使用。"
49
+ **好的写法:** "如果你在用 TypeScript 且有运行时数据验证需求(表单、API 返回值、环境变量),Zod 应该是你的默认选择,没有理由不用。如果你在 JavaScript 项目里,或者你的验证逻辑非常简单,可以考虑更轻量的 yup 或直接手写。"
50
+
51
+ ---
52
+
53
+ ## 不同类型文章的侧重点
54
+
55
+ ### 技术博客(个人/公司博客)
56
+ - 语气:可以有个人观点,第一人称
57
+ - 侧重:实际使用经验,踩过的坑
58
+ - 长度:1500-3000 字
59
+ - 代码比例:30-40%
60
+
61
+ ### 开源推荐(掘金/InfoQ/微信公众号)
62
+ - 语气:中立客观,教程风格
63
+ - 侧重:核心功能演示,上手路径
64
+ - 长度:2000-4000 字
65
+ - 代码比例:40-50%
66
+
67
+ ### 选型报告(内部决策文档)
68
+ - 语气:正式,有数据支撑
69
+ - 侧重:竞品对比,生产就绪度,风险
70
+ - 长度:3000-6000 字
71
+ - 代码比例:20-30%(更多表格和数据)
72
+
73
+ ### 团队分享/PPT
74
+ - 语气:口语化,有故事性
75
+ - 侧重:Demo 演示,Why Now
76
+ - 长度:800-1500 字脚本
77
+ - 代码比例:代码只在 Demo 部分
78
+
79
+ ---
80
+
81
+ ## 常见错误
82
+
83
+ 1. **把 README 翻译一遍**:这不是分析,只是搬运。文章价值在于作者视角的筛选和解读。
84
+ 2. **只写优点**:读者不信任只有优点的文章,会觉得是软文。
85
+ 3. **代码示例过长**:超过 30 行的示例应该放到 GitHub Gist,文章里只保留关键部分。
86
+ 4. **忽略版本信息**:技术文章要标注基于哪个版本,API 变化很快。
87
+ 5. **对比时贬低竞品**:应该客观陈述差异,不要说"A 比 B 差",而是说"B 更适合 X 场景"。
@@ -0,0 +1,97 @@
1
+ # 竞品对比维度模板
2
+
3
+ 当用户需要做开源库选型对比时,使用以下维度框架。
4
+
5
+ ---
6
+
7
+ ## 标准对比维度
8
+
9
+ ### 基础信息
10
+ | 维度 | 说明 |
11
+ |------|------|
12
+ | 首次发布时间 | 项目成熟度参考 |
13
+ | 最新版本 | 是否活跃迭代 |
14
+ | License | 商业使用限制 |
15
+ | 主要语言 | 技术栈匹配度 |
16
+ | 包大小 | 对 bundle size 有影响的场景重要 |
17
+
18
+ ### 功能对比
19
+ | 维度 | 说明 |
20
+ |------|------|
21
+ | 核心功能覆盖 | 解决同一问题的程度 |
22
+ | 扩展/插件生态 | 能否满足定制需求 |
23
+ | TypeScript 支持 | 类型安全 |
24
+ | 框架集成 | React/Vue/Svelte 等 |
25
+ | SSR 支持 | 服务端渲染场景 |
26
+
27
+ ### 性能
28
+ | 维度 | 说明 |
29
+ |------|------|
30
+ | 基准测试结果 | 官方或第三方 benchmark |
31
+ | 内存占用 | 长期运行场景 |
32
+ | 冷启动时间 | Serverless 场景 |
33
+ | 并发处理能力 | 高并发场景 |
34
+
35
+ ### 开发体验
36
+ | 维度 | 说明 |
37
+ |------|------|
38
+ | 学习曲线 | 上手所需时间(小时/天) |
39
+ | 文档质量 | 完整性、示例丰富度 |
40
+ | 错误信息友好度 | 调试难易程度 |
41
+ | DevTools 支持 | 有无调试工具 |
42
+ | CLI 工具 | 脚手架、代码生成 |
43
+
44
+ ### 生态与社区
45
+ | 维度 | 说明 |
46
+ |------|------|
47
+ | GitHub Stars | 社区认可度(注意:不代表质量) |
48
+ | npm 周下载量 | 实际使用规模 |
49
+ | 贡献者数量 | 社区健康度 |
50
+ | Issue 响应速度 | 维护积极性 |
51
+ | Stack Overflow 问题数 | 遇到问题时能否找到答案 |
52
+
53
+ ### 生产就绪度
54
+ | 维度 | 说明 |
55
+ |------|------|
56
+ | 知名公司采用 | 背书可信度 |
57
+ | 稳定性记录 | 有无重大安全漏洞历史 |
58
+ | 版本兼容性 | 升级破坏性变更频率 |
59
+ | 长期支持(LTS) | 企业采购关注点 |
60
+
61
+ ---
62
+
63
+ ## 对比表格模板
64
+
65
+ ```markdown
66
+ | | {库A} | {库B} | {库C} |
67
+ |--|-------|-------|-------|
68
+ | **定位** | | | |
69
+ | **Star 数** | | | |
70
+ | **包大小** | | | |
71
+ | **学习曲线** | 低/中/高 | | |
72
+ | **性能** | | | |
73
+ | **TypeScript** | ✅/❌ | | |
74
+ | **适合场景** | | | |
75
+ | **不适合场景** | | | |
76
+ | **推荐指数** | ⭐⭐⭐ | | |
77
+ ```
78
+
79
+ ---
80
+
81
+ ## 结论撰写规范
82
+
83
+ 不要写"各有优缺点,视情况而定"这种废话。
84
+ 必须给出明确的场景建议,格式如下:
85
+
86
+ **选 A,如果:**
87
+ - 你的项目是 XXX 场景
88
+ - 你的团队更熟悉 XXX 技术栈
89
+ - 性能是首要考虑因素
90
+
91
+ **选 B,如果:**
92
+ - 你需要更丰富的生态/插件
93
+ - 项目规模较小,更看重上手速度
94
+ - 已有 XXX 基础设施
95
+
96
+ **都不选,如果:**
97
+ - 你的场景是 XXX(推荐考虑 C 方案)