cvte-skills-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 +173 -0
- package/bin/cli.js +62 -0
- package/lib/index.js +141 -0
- package/lib/installers/claude.js +83 -0
- package/lib/installers/codex.js +44 -0
- package/lib/installers/copilot.js +38 -0
- package/lib/installers/cursor.js +43 -0
- package/lib/installers/gemini.js +44 -0
- package/lib/installers/windsurf.js +40 -0
- package/package.json +37 -0
- package/skills/analysis-opensdk-api/SKILL.md +161 -0
- package/skills/gitlab-commit-message/SKILL.md +95 -0
package/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# CVTE Skills CLI
|
|
2
|
+
|
|
3
|
+
一个用于安装自定义 AI 编程助手 Skills 的 CLI 工具,支持多种 AI 助手。
|
|
4
|
+
|
|
5
|
+
## 支持的 AI 助手
|
|
6
|
+
|
|
7
|
+
| 助手 | 配置文件位置 |
|
|
8
|
+
|------|-------------|
|
|
9
|
+
| Claude Code | `~/.claude/settings.json` (全局) / `CLAUDE.md` (项目) |
|
|
10
|
+
| Cursor | `~/.cursor/rules` (全局) / `.cursorrules` (项目) |
|
|
11
|
+
| Windsurf | `~/.windsurf/rules` (全局) / `.windsurfrules` (项目) |
|
|
12
|
+
| GitHub Copilot | `.github/copilot-instructions.md` |
|
|
13
|
+
| Codex CLI | `~/.codex/instructions.md` (全局) / `codex.md` (项目) |
|
|
14
|
+
| Gemini CLI | `~/.gemini/instructions.md` (全局) / `GEMINI.md` (项目) |
|
|
15
|
+
|
|
16
|
+
## 包含的 Skills
|
|
17
|
+
|
|
18
|
+
| Skill | 描述 |
|
|
19
|
+
|-------|------|
|
|
20
|
+
| `gitlab-commit-message` | Git commit message 模板规范,强制使用标准化的提交信息格式 |
|
|
21
|
+
| `analysis-opensdk-api` | OpenSDK API 调用链分析工具,用于追踪 SDK 调用流程 |
|
|
22
|
+
|
|
23
|
+
## 安装
|
|
24
|
+
|
|
25
|
+
### 通过 npm 全局安装
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g cvte-skills-cli
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 从源码安装
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git clone https://github.com/YOUR_USERNAME/skills.git
|
|
35
|
+
cd skills
|
|
36
|
+
npm install
|
|
37
|
+
npm link
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 使用方法
|
|
41
|
+
|
|
42
|
+
### 初始化 Skills
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# 进入你的项目目录
|
|
46
|
+
cd /path/to/your/project
|
|
47
|
+
|
|
48
|
+
# 为指定 AI 助手安装 skills
|
|
49
|
+
cvte-skills init --ai claude # Claude Code
|
|
50
|
+
cvte-skills init --ai cursor # Cursor
|
|
51
|
+
cvte-skills init --ai windsurf # Windsurf
|
|
52
|
+
cvte-skills init --ai copilot # GitHub Copilot
|
|
53
|
+
cvte-skills init --ai codex # Codex CLI
|
|
54
|
+
cvte-skills init --ai gemini # Gemini CLI
|
|
55
|
+
cvte-skills init --ai all # 所有助手
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 全局安装
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# 安装到全局配置(而非项目级)
|
|
62
|
+
cvte-skills init --ai claude --global
|
|
63
|
+
cvte-skills init --ai cursor --global
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 选择特定 Skills
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 只安装指定的 skills
|
|
70
|
+
cvte-skills init --ai claude -s gitlab-commit-message
|
|
71
|
+
cvte-skills init --ai cursor -s gitlab-commit-message analysis-opensdk-api
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 查看可用 Skills
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
cvte-skills list
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 移除 Skills
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
cvte-skills remove --ai claude
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 命令参考
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
cvte-skills init [options]
|
|
90
|
+
--ai <assistant> 目标 AI 助手 (claude, cursor, windsurf, copilot, codex, gemini, all)
|
|
91
|
+
--global 安装到全局配置
|
|
92
|
+
-s, --skills <names> 指定要安装的 skills
|
|
93
|
+
|
|
94
|
+
cvte-skills list 列出所有可用的 skills
|
|
95
|
+
|
|
96
|
+
cvte-skills remove 移除已安装的 skills
|
|
97
|
+
--ai <assistant> 目标 AI 助手
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## 项目结构
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
skills/
|
|
104
|
+
├── package.json
|
|
105
|
+
├── bin/
|
|
106
|
+
│ └── cli.js # CLI 入口
|
|
107
|
+
├── lib/
|
|
108
|
+
│ ├── index.js # 核心逻辑
|
|
109
|
+
│ └── installers/ # 各 AI 助手的安装器
|
|
110
|
+
│ ├── claude.js
|
|
111
|
+
│ ├── cursor.js
|
|
112
|
+
│ ├── windsurf.js
|
|
113
|
+
│ ├── copilot.js
|
|
114
|
+
│ ├── codex.js
|
|
115
|
+
│ └── gemini.js
|
|
116
|
+
└── skills/ # Skill 定义
|
|
117
|
+
├── gitlab-commit-message/
|
|
118
|
+
│ └── SKILL.md
|
|
119
|
+
└── analysis-opensdk-api/
|
|
120
|
+
└── SKILL.md
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 创建新 Skill
|
|
124
|
+
|
|
125
|
+
1. 在 `skills/` 目录下创建新文件夹
|
|
126
|
+
2. 添加 `SKILL.md` 文件,格式如下:
|
|
127
|
+
|
|
128
|
+
```markdown
|
|
129
|
+
---
|
|
130
|
+
name: my-skill
|
|
131
|
+
description: "Skill 的简短描述"
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
# Skill 标题
|
|
135
|
+
|
|
136
|
+
## 使用说明
|
|
137
|
+
...
|
|
138
|
+
|
|
139
|
+
## 示例
|
|
140
|
+
...
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
3. 重新发布或本地 link 后即可使用
|
|
144
|
+
|
|
145
|
+
## 手动安装(不使用 CLI)
|
|
146
|
+
|
|
147
|
+
如果不想使用 CLI,也可以手动配置:
|
|
148
|
+
|
|
149
|
+
### Claude Code
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# 方式1: 使用 claude 命令
|
|
153
|
+
claude config add skills /path/to/skills/skills/gitlab-commit-message
|
|
154
|
+
|
|
155
|
+
# 方式2: 编辑 ~/.claude/settings.json
|
|
156
|
+
{
|
|
157
|
+
"skills": [
|
|
158
|
+
"/path/to/skills/skills/gitlab-commit-message"
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Cursor
|
|
164
|
+
|
|
165
|
+
将 skill 内容复制到项目根目录的 `.cursorrules` 文件中。
|
|
166
|
+
|
|
167
|
+
### GitHub Copilot
|
|
168
|
+
|
|
169
|
+
将 skill 内容复制到 `.github/copilot-instructions.md` 文件中。
|
|
170
|
+
|
|
171
|
+
## 许可证
|
|
172
|
+
|
|
173
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { initSkills, listSkills, removeSkills } from '../lib/index.js';
|
|
6
|
+
|
|
7
|
+
const program = new Command();
|
|
8
|
+
|
|
9
|
+
program
|
|
10
|
+
.name('cvte-skills')
|
|
11
|
+
.description('Install custom skills for AI coding assistants')
|
|
12
|
+
.version('1.0.0');
|
|
13
|
+
|
|
14
|
+
program
|
|
15
|
+
.command('init')
|
|
16
|
+
.description('Initialize skills for an AI assistant')
|
|
17
|
+
.option('--ai <assistant>', 'Target AI assistant (claude, cursor, windsurf, copilot, codex, all)')
|
|
18
|
+
.option('--global', 'Install to global config instead of project')
|
|
19
|
+
.option('-s, --skills <skills...>', 'Specific skills to install (default: all)')
|
|
20
|
+
.action(async (options) => {
|
|
21
|
+
if (!options.ai) {
|
|
22
|
+
console.log(chalk.red('Error: Please specify an AI assistant with --ai'));
|
|
23
|
+
console.log(chalk.gray('Available: claude, cursor, windsurf, copilot, codex, gemini, all'));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
await initSkills(options);
|
|
29
|
+
console.log(chalk.green('✓ Skills installed successfully!'));
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error(chalk.red('Error:'), error.message);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
program
|
|
37
|
+
.command('list')
|
|
38
|
+
.description('List available skills')
|
|
39
|
+
.action(() => {
|
|
40
|
+
listSkills();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
program
|
|
44
|
+
.command('remove')
|
|
45
|
+
.description('Remove installed skills')
|
|
46
|
+
.option('--ai <assistant>', 'Target AI assistant')
|
|
47
|
+
.action(async (options) => {
|
|
48
|
+
if (!options.ai) {
|
|
49
|
+
console.log(chalk.red('Error: Please specify an AI assistant with --ai'));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
await removeSkills(options);
|
|
55
|
+
console.log(chalk.green('✓ Skills removed successfully!'));
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(chalk.red('Error:'), error.message);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
program.parse();
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { installForClaude } from './installers/claude.js';
|
|
6
|
+
import { installForCursor } from './installers/cursor.js';
|
|
7
|
+
import { installForWindsurf } from './installers/windsurf.js';
|
|
8
|
+
import { installForCopilot } from './installers/copilot.js';
|
|
9
|
+
import { installForCodex } from './installers/codex.js';
|
|
10
|
+
import { installForGemini } from './installers/gemini.js';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
|
|
15
|
+
// 获取 skills 目录
|
|
16
|
+
export function getSkillsDir() {
|
|
17
|
+
return path.join(__dirname, '..', 'skills');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 获取所有可用的 skills
|
|
21
|
+
export function getAvailableSkills() {
|
|
22
|
+
const skillsDir = getSkillsDir();
|
|
23
|
+
const skills = [];
|
|
24
|
+
|
|
25
|
+
if (!fs.existsSync(skillsDir)) {
|
|
26
|
+
return skills;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const dirs = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
30
|
+
|
|
31
|
+
for (const dir of dirs) {
|
|
32
|
+
if (dir.isDirectory()) {
|
|
33
|
+
const skillPath = path.join(skillsDir, dir.name, 'SKILL.md');
|
|
34
|
+
if (fs.existsSync(skillPath)) {
|
|
35
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
36
|
+
const meta = parseSkillMeta(content);
|
|
37
|
+
skills.push({
|
|
38
|
+
name: dir.name,
|
|
39
|
+
path: path.join(skillsDir, dir.name),
|
|
40
|
+
skillFile: skillPath,
|
|
41
|
+
content: content,
|
|
42
|
+
...meta
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return skills;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 解析 SKILL.md 的 frontmatter
|
|
52
|
+
function parseSkillMeta(content) {
|
|
53
|
+
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
54
|
+
if (!match) return {};
|
|
55
|
+
|
|
56
|
+
const frontmatter = match[1];
|
|
57
|
+
const meta = {};
|
|
58
|
+
|
|
59
|
+
const nameMatch = frontmatter.match(/name:\s*(.+)/);
|
|
60
|
+
if (nameMatch) meta.displayName = nameMatch[1].trim();
|
|
61
|
+
|
|
62
|
+
const descMatch = frontmatter.match(/description:\s*["']?(.+?)["']?\s*$/m);
|
|
63
|
+
if (descMatch) meta.description = descMatch[1].trim();
|
|
64
|
+
|
|
65
|
+
return meta;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 获取 skill 的纯内容(去掉 frontmatter)
|
|
69
|
+
export function getSkillContent(skill) {
|
|
70
|
+
return skill.content.replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, '').trim();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 列出所有 skills
|
|
74
|
+
export function listSkills() {
|
|
75
|
+
const skills = getAvailableSkills();
|
|
76
|
+
|
|
77
|
+
console.log(chalk.bold('\nAvailable Skills:\n'));
|
|
78
|
+
|
|
79
|
+
if (skills.length === 0) {
|
|
80
|
+
console.log(chalk.gray(' No skills found'));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const skill of skills) {
|
|
85
|
+
console.log(chalk.cyan(` ${skill.name}`));
|
|
86
|
+
if (skill.description) {
|
|
87
|
+
console.log(chalk.gray(` ${skill.description}`));
|
|
88
|
+
}
|
|
89
|
+
console.log();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 安装器映射
|
|
94
|
+
const installers = {
|
|
95
|
+
claude: installForClaude,
|
|
96
|
+
cursor: installForCursor,
|
|
97
|
+
windsurf: installForWindsurf,
|
|
98
|
+
copilot: installForCopilot,
|
|
99
|
+
codex: installForCodex,
|
|
100
|
+
gemini: installForGemini,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// 初始化 skills
|
|
104
|
+
export async function initSkills(options) {
|
|
105
|
+
const { ai, global: isGlobal, skills: selectedSkills } = options;
|
|
106
|
+
|
|
107
|
+
let allSkills = getAvailableSkills();
|
|
108
|
+
|
|
109
|
+
// 过滤指定的 skills
|
|
110
|
+
if (selectedSkills && selectedSkills.length > 0) {
|
|
111
|
+
allSkills = allSkills.filter(s => selectedSkills.includes(s.name));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (allSkills.length === 0) {
|
|
115
|
+
throw new Error('No skills found to install');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log(chalk.blue(`\nInstalling ${allSkills.length} skill(s)...\n`));
|
|
119
|
+
|
|
120
|
+
const targets = ai === 'all' ? Object.keys(installers) : [ai];
|
|
121
|
+
|
|
122
|
+
for (const target of targets) {
|
|
123
|
+
const installer = installers[target];
|
|
124
|
+
if (!installer) {
|
|
125
|
+
console.log(chalk.yellow(` ⚠ Unknown assistant: ${target}, skipping`));
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log(chalk.gray(` → Installing for ${target}...`));
|
|
130
|
+
await installer(allSkills, { isGlobal });
|
|
131
|
+
console.log(chalk.green(` ✓ ${target}`));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 移除 skills
|
|
136
|
+
export async function removeSkills(options) {
|
|
137
|
+
const { ai } = options;
|
|
138
|
+
|
|
139
|
+
// TODO: 实现移除逻辑
|
|
140
|
+
console.log(chalk.yellow('Remove functionality coming soon...'));
|
|
141
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { getSkillsDir } from '../index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Claude Code 安装器
|
|
8
|
+
*
|
|
9
|
+
* Claude Code 支持两种方式:
|
|
10
|
+
* 1. 全局配置: ~/.claude/settings.json 中的 skills 数组(路径引用)
|
|
11
|
+
* 2. 项目级: .claude/settings.json 或直接写入 CLAUDE.md
|
|
12
|
+
*/
|
|
13
|
+
export async function installForClaude(skills, options = {}) {
|
|
14
|
+
const { isGlobal = false } = options;
|
|
15
|
+
|
|
16
|
+
if (isGlobal) {
|
|
17
|
+
// 全局安装:添加 skill 路径到 settings.json
|
|
18
|
+
await installGlobal(skills);
|
|
19
|
+
} else {
|
|
20
|
+
// 项目级安装:写入 CLAUDE.md
|
|
21
|
+
await installProject(skills);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function installGlobal(skills) {
|
|
26
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
27
|
+
|
|
28
|
+
// 确保目录存在
|
|
29
|
+
await fs.ensureDir(path.dirname(settingsPath));
|
|
30
|
+
|
|
31
|
+
let settings = {};
|
|
32
|
+
if (await fs.pathExists(settingsPath)) {
|
|
33
|
+
try {
|
|
34
|
+
settings = await fs.readJson(settingsPath);
|
|
35
|
+
} catch {
|
|
36
|
+
settings = {};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 初始化 skills 数组
|
|
41
|
+
if (!Array.isArray(settings.skills)) {
|
|
42
|
+
settings.skills = [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 添加 skill 路径
|
|
46
|
+
for (const skill of skills) {
|
|
47
|
+
if (!settings.skills.includes(skill.path)) {
|
|
48
|
+
settings.skills.push(skill.path);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await fs.writeJson(settingsPath, settings, { spaces: 2 });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function installProject(skills) {
|
|
56
|
+
const claudeDir = path.join(process.cwd(), '.claude');
|
|
57
|
+
const claudeMdPath = path.join(process.cwd(), 'CLAUDE.md');
|
|
58
|
+
|
|
59
|
+
// 方式1: 写入 CLAUDE.md(推荐)
|
|
60
|
+
let content = '';
|
|
61
|
+
|
|
62
|
+
if (await fs.pathExists(claudeMdPath)) {
|
|
63
|
+
content = await fs.readFile(claudeMdPath, 'utf-8');
|
|
64
|
+
content += '\n\n';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 添加分隔标记
|
|
68
|
+
content += '<!-- cvte-skills-start -->\n';
|
|
69
|
+
content += '# Custom Skills\n\n';
|
|
70
|
+
|
|
71
|
+
for (const skill of skills) {
|
|
72
|
+
const skillContent = await fs.readFile(skill.skillFile, 'utf-8');
|
|
73
|
+
// 去掉 frontmatter
|
|
74
|
+
const cleanContent = skillContent.replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, '').trim();
|
|
75
|
+
content += `## ${skill.displayName || skill.name}\n\n`;
|
|
76
|
+
content += cleanContent;
|
|
77
|
+
content += '\n\n';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
content += '<!-- cvte-skills-end -->\n';
|
|
81
|
+
|
|
82
|
+
await fs.writeFile(claudeMdPath, content);
|
|
83
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* OpenAI Codex CLI 安装器
|
|
7
|
+
*
|
|
8
|
+
* Codex 使用:
|
|
9
|
+
* - 全局: ~/.codex/instructions.md
|
|
10
|
+
* - 项目级: codex.md 或 AGENTS.md
|
|
11
|
+
*/
|
|
12
|
+
export async function installForCodex(skills, options = {}) {
|
|
13
|
+
const { isGlobal = false } = options;
|
|
14
|
+
|
|
15
|
+
const targetPath = isGlobal
|
|
16
|
+
? path.join(os.homedir(), '.codex', 'instructions.md')
|
|
17
|
+
: path.join(process.cwd(), 'codex.md');
|
|
18
|
+
|
|
19
|
+
await fs.ensureDir(path.dirname(targetPath));
|
|
20
|
+
|
|
21
|
+
let content = '';
|
|
22
|
+
|
|
23
|
+
if (await fs.pathExists(targetPath)) {
|
|
24
|
+
content = await fs.readFile(targetPath, 'utf-8');
|
|
25
|
+
content = content.replace(/<!-- cvte-skills-start -->[\s\S]*?<!-- cvte-skills-end -->\n?/g, '');
|
|
26
|
+
content = content.trim();
|
|
27
|
+
if (content) content += '\n\n';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
content += '<!-- cvte-skills-start -->\n';
|
|
31
|
+
content += '# Custom Skills\n\n';
|
|
32
|
+
|
|
33
|
+
for (const skill of skills) {
|
|
34
|
+
const skillContent = await fs.readFile(skill.skillFile, 'utf-8');
|
|
35
|
+
const cleanContent = skillContent.replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, '').trim();
|
|
36
|
+
content += `## ${skill.displayName || skill.name}\n\n`;
|
|
37
|
+
content += cleanContent;
|
|
38
|
+
content += '\n\n';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
content += '<!-- cvte-skills-end -->\n';
|
|
42
|
+
|
|
43
|
+
await fs.writeFile(targetPath, content);
|
|
44
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* GitHub Copilot 安装器
|
|
6
|
+
*
|
|
7
|
+
* Copilot 使用 .github/copilot-instructions.md 文件
|
|
8
|
+
*/
|
|
9
|
+
export async function installForCopilot(skills, options = {}) {
|
|
10
|
+
const targetDir = path.join(process.cwd(), '.github');
|
|
11
|
+
const targetPath = path.join(targetDir, 'copilot-instructions.md');
|
|
12
|
+
|
|
13
|
+
await fs.ensureDir(targetDir);
|
|
14
|
+
|
|
15
|
+
let content = '';
|
|
16
|
+
|
|
17
|
+
if (await fs.pathExists(targetPath)) {
|
|
18
|
+
content = await fs.readFile(targetPath, 'utf-8');
|
|
19
|
+
content = content.replace(/<!-- cvte-skills-start -->[\s\S]*?<!-- cvte-skills-end -->\n?/g, '');
|
|
20
|
+
content = content.trim();
|
|
21
|
+
if (content) content += '\n\n';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
content += '<!-- cvte-skills-start -->\n';
|
|
25
|
+
content += '# Custom Skills\n\n';
|
|
26
|
+
|
|
27
|
+
for (const skill of skills) {
|
|
28
|
+
const skillContent = await fs.readFile(skill.skillFile, 'utf-8');
|
|
29
|
+
const cleanContent = skillContent.replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, '').trim();
|
|
30
|
+
content += `## ${skill.displayName || skill.name}\n\n`;
|
|
31
|
+
content += cleanContent;
|
|
32
|
+
content += '\n\n';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
content += '<!-- cvte-skills-end -->\n';
|
|
36
|
+
|
|
37
|
+
await fs.writeFile(targetPath, content);
|
|
38
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Cursor 安装器
|
|
7
|
+
*
|
|
8
|
+
* Cursor 使用 .cursorrules 或 .cursor/rules 文件
|
|
9
|
+
*/
|
|
10
|
+
export async function installForCursor(skills, options = {}) {
|
|
11
|
+
const { isGlobal = false } = options;
|
|
12
|
+
|
|
13
|
+
const targetPath = isGlobal
|
|
14
|
+
? path.join(os.homedir(), '.cursor', 'rules')
|
|
15
|
+
: path.join(process.cwd(), '.cursorrules');
|
|
16
|
+
|
|
17
|
+
// 确保目录存在
|
|
18
|
+
await fs.ensureDir(path.dirname(targetPath));
|
|
19
|
+
|
|
20
|
+
let content = '';
|
|
21
|
+
|
|
22
|
+
if (await fs.pathExists(targetPath)) {
|
|
23
|
+
content = await fs.readFile(targetPath, 'utf-8');
|
|
24
|
+
// 移除旧的 skills 内容
|
|
25
|
+
content = content.replace(/<!-- cvte-skills-start -->[\s\S]*?<!-- cvte-skills-end -->\n?/g, '');
|
|
26
|
+
content = content.trim();
|
|
27
|
+
if (content) content += '\n\n';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 添加 skills
|
|
31
|
+
content += '<!-- cvte-skills-start -->\n';
|
|
32
|
+
|
|
33
|
+
for (const skill of skills) {
|
|
34
|
+
const skillContent = await fs.readFile(skill.skillFile, 'utf-8');
|
|
35
|
+
const cleanContent = skillContent.replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, '').trim();
|
|
36
|
+
content += cleanContent;
|
|
37
|
+
content += '\n\n';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
content += '<!-- cvte-skills-end -->\n';
|
|
41
|
+
|
|
42
|
+
await fs.writeFile(targetPath, content);
|
|
43
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Google Gemini CLI 安装器
|
|
7
|
+
*
|
|
8
|
+
* Gemini CLI 使用:
|
|
9
|
+
* - 全局: ~/.gemini/instructions.md
|
|
10
|
+
* - 项目级: GEMINI.md
|
|
11
|
+
*/
|
|
12
|
+
export async function installForGemini(skills, options = {}) {
|
|
13
|
+
const { isGlobal = false } = options;
|
|
14
|
+
|
|
15
|
+
const targetPath = isGlobal
|
|
16
|
+
? path.join(os.homedir(), '.gemini', 'instructions.md')
|
|
17
|
+
: path.join(process.cwd(), 'GEMINI.md');
|
|
18
|
+
|
|
19
|
+
await fs.ensureDir(path.dirname(targetPath));
|
|
20
|
+
|
|
21
|
+
let content = '';
|
|
22
|
+
|
|
23
|
+
if (await fs.pathExists(targetPath)) {
|
|
24
|
+
content = await fs.readFile(targetPath, 'utf-8');
|
|
25
|
+
content = content.replace(/<!-- cvte-skills-start -->[\s\S]*?<!-- cvte-skills-end -->\n?/g, '');
|
|
26
|
+
content = content.trim();
|
|
27
|
+
if (content) content += '\n\n';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
content += '<!-- cvte-skills-start -->\n';
|
|
31
|
+
content += '# Custom Skills\n\n';
|
|
32
|
+
|
|
33
|
+
for (const skill of skills) {
|
|
34
|
+
const skillContent = await fs.readFile(skill.skillFile, 'utf-8');
|
|
35
|
+
const cleanContent = skillContent.replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, '').trim();
|
|
36
|
+
content += `## ${skill.displayName || skill.name}\n\n`;
|
|
37
|
+
content += cleanContent;
|
|
38
|
+
content += '\n\n';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
content += '<!-- cvte-skills-end -->\n';
|
|
42
|
+
|
|
43
|
+
await fs.writeFile(targetPath, content);
|
|
44
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Windsurf 安装器
|
|
7
|
+
*
|
|
8
|
+
* Windsurf 使用 .windsurfrules 文件
|
|
9
|
+
*/
|
|
10
|
+
export async function installForWindsurf(skills, options = {}) {
|
|
11
|
+
const { isGlobal = false } = options;
|
|
12
|
+
|
|
13
|
+
const targetPath = isGlobal
|
|
14
|
+
? path.join(os.homedir(), '.windsurf', 'rules')
|
|
15
|
+
: path.join(process.cwd(), '.windsurfrules');
|
|
16
|
+
|
|
17
|
+
await fs.ensureDir(path.dirname(targetPath));
|
|
18
|
+
|
|
19
|
+
let content = '';
|
|
20
|
+
|
|
21
|
+
if (await fs.pathExists(targetPath)) {
|
|
22
|
+
content = await fs.readFile(targetPath, 'utf-8');
|
|
23
|
+
content = content.replace(/<!-- cvte-skills-start -->[\s\S]*?<!-- cvte-skills-end -->\n?/g, '');
|
|
24
|
+
content = content.trim();
|
|
25
|
+
if (content) content += '\n\n';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
content += '<!-- cvte-skills-start -->\n';
|
|
29
|
+
|
|
30
|
+
for (const skill of skills) {
|
|
31
|
+
const skillContent = await fs.readFile(skill.skillFile, 'utf-8');
|
|
32
|
+
const cleanContent = skillContent.replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, '').trim();
|
|
33
|
+
content += cleanContent;
|
|
34
|
+
content += '\n\n';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
content += '<!-- cvte-skills-end -->\n';
|
|
38
|
+
|
|
39
|
+
await fs.writeFile(targetPath, content);
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cvte-skills-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool to install custom skills for AI coding assistants",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"cvte-skills": "./bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"No tests yet\""
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"claude",
|
|
15
|
+
"cursor",
|
|
16
|
+
"copilot",
|
|
17
|
+
"ai",
|
|
18
|
+
"skills",
|
|
19
|
+
"cli"
|
|
20
|
+
],
|
|
21
|
+
"author": "",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"commander": "^12.0.0",
|
|
25
|
+
"chalk": "^5.3.0",
|
|
26
|
+
"fs-extra": "^11.2.0",
|
|
27
|
+
"inquirer": "^9.2.0"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.0.0"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"bin",
|
|
34
|
+
"lib",
|
|
35
|
+
"skills"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: analysis-opensdk-api
|
|
3
|
+
description: Analyze OpenSDK-initiated API call chains across source app → OpenSDK3.0 → UdiServer → UdiServiceCore. Use when tracing SDKSystemHelper/SDK* calls, Cmd* requests, or
|
|
4
|
+
UDI API URIs from app side. Produce a call-chain flow diagram and file/line references.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# OpenSDK API Chain Analysis
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
- User must provide or have configured project paths:
|
|
11
|
+
- SeewoOTA: Android OTA app(other source apps)
|
|
12
|
+
- OpenSDK3.0: SDK layer
|
|
13
|
+
- UdiServer: OpenSDK service bridge
|
|
14
|
+
- UdiServiceCore: UDI implementation
|
|
15
|
+
- If paths not provided, ask user before proceeding.
|
|
16
|
+
|
|
17
|
+
## References
|
|
18
|
+
- OpenSDK3.0: /Users/cvte/Documents/android/project/seewo/OpenSDK3.0/OpenSDK3.0
|
|
19
|
+
- UdiServer: /Users/cvte/Documents/android/project/udi/UdiServer
|
|
20
|
+
- UdiServiceCore: /Users/cvte/Documents/android/project/udi/UdiServiceCore
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## Scope
|
|
24
|
+
- Trace forward: App/SeewoOTA → OpenSDK3.0 → UdiServer → UdiServiceCore.
|
|
25
|
+
- Stop at these boundaries and note the final action:
|
|
26
|
+
- **Shell**: command string (e.g., `ffp_tool`, `eUpdate2`)
|
|
27
|
+
- **VMan**: FwUpgradeManager type (e.g., `TYPE_FW_UPGRADE_TOUCHBIN`)
|
|
28
|
+
- **JNI**: McutouchManager method (e.g., `upgradeTouch`)
|
|
29
|
+
- **System Service**: Intent/ComponentName (e.g., TouchPanelService)
|
|
30
|
+
- **TvApi**: ITvApiManager method
|
|
31
|
+
- Do not assume external services exist; only include if chain explicitly reaches them.
|
|
32
|
+
|
|
33
|
+
## Inputs to collect
|
|
34
|
+
- Entry keyword (API name, Cmd class, or URI), e.g. `upgradeTouch`, `CmdUpgradeTouch`, `/v1/touch/ota/upgrade`.
|
|
35
|
+
- Target app module: SeewoOTA (the usual entry point).
|
|
36
|
+
|
|
37
|
+
## Workflow
|
|
38
|
+
|
|
39
|
+
### 0) Check for VMan architecture (Android 14+)
|
|
40
|
+
- Search for VMan-based tasks:
|
|
41
|
+
- `Grep pattern="VmanUpdateTask|BaseVmanUpdateTask" path=<SeewoOTA_path>`
|
|
42
|
+
- If found, note that the chain may use VMan SDK directly for version checking.
|
|
43
|
+
|
|
44
|
+
### 1) Find app entry points (SeewoOTA)
|
|
45
|
+
- Search for SDK calls:
|
|
46
|
+
- `Grep pattern="SDKSystemHelper\.I\.|OpenSDK\.getInstance" path=<SeewoOTA_path>`
|
|
47
|
+
- If you have a method name, search directly:
|
|
48
|
+
- `Grep pattern="<keyword>" path=<SeewoOTA_path>`
|
|
49
|
+
- Record: caller class/method, dialog/trigger path, and any pre-checks.
|
|
50
|
+
|
|
51
|
+
### 1.5) Check config gates
|
|
52
|
+
- Search for config flags:
|
|
53
|
+
- `Grep pattern="R\.bool\.|R\.string\." path=<SeewoOTA_path>/src/main`
|
|
54
|
+
- Check `strings_config.xml` for defaults and flavor overrides.
|
|
55
|
+
- Record: config flag name, default value, controlling flavors.
|
|
56
|
+
|
|
57
|
+
### 2) Map to OpenSDK3.0
|
|
58
|
+
- Locate SDK method in `SDKSystemHelper.java`:
|
|
59
|
+
- `Grep pattern="<method>\(" path=<OpenSDK_path>/src/main/java/com/seewo/sdk/SDKSystemHelper.java`
|
|
60
|
+
- Open the Cmd class and note its fields.
|
|
61
|
+
- Record: `SDKSystemHelper.<method>` → `Cmd*` → `OpenSDK.postCommand/sendCommand`.
|
|
62
|
+
|
|
63
|
+
### 3) Map to UdiServer
|
|
64
|
+
- Search for Cmd class:
|
|
65
|
+
- `Grep pattern="Cmd<Name>" path=<UdiServer_path>`
|
|
66
|
+
- Identify handler method (`@HandlePostRequest`, `@HandleGetRequest`, `@HandleRequest`).
|
|
67
|
+
- Note the UDI URI in `RequestBuilder`.
|
|
68
|
+
- Record: handler class/method, request URI, event adapters.
|
|
69
|
+
|
|
70
|
+
### 4) Map to UdiServiceCore
|
|
71
|
+
- Find URI in `ProtocolDefine.kt`:
|
|
72
|
+
- `Grep pattern="/v1/.*" path=<UdiServiceCore_path>/.../ProtocolDefine.kt`
|
|
73
|
+
- Find `@SetterHandler`/`@GetterHandler` in service core.
|
|
74
|
+
- Trace into implementation class and capture branching logic.
|
|
75
|
+
- Record: service method, implementation class, branch rules, final action.
|
|
76
|
+
|
|
77
|
+
### 4.5) Trace result/status notifications
|
|
78
|
+
- Check for related notify endpoints:
|
|
79
|
+
- `Grep pattern="<base_uri>/(result|status)" path=<UdiServiceCore_path>`
|
|
80
|
+
- Record: result URI, notify class, callback mechanism.
|
|
81
|
+
|
|
82
|
+
### 5) Summarize and diagram
|
|
83
|
+
- Provide bullet call chain with file paths + line numbers.
|
|
84
|
+
- Provide ASCII flow diagram.
|
|
85
|
+
- Include branch rules if applicable.
|
|
86
|
+
- Note boundary and final action type.
|
|
87
|
+
|
|
88
|
+
## Output format
|
|
89
|
+
|
|
90
|
+
### Structure
|
|
91
|
+
1. Short intro: what chain traced and entry keyword.
|
|
92
|
+
2. Call-chain bullets (with file:line refs).
|
|
93
|
+
3. Flow diagram (ASCII).
|
|
94
|
+
4. Boundary note with final action type.
|
|
95
|
+
|
|
96
|
+
### Flow diagram templates
|
|
97
|
+
|
|
98
|
+
#### Linear chain
|
|
99
|
+
┌───────────────────────────────┐
|
|
100
|
+
│ SeewoOTA │
|
|
101
|
+
│ entry → SDKSystemHelper.* │
|
|
102
|
+
└───────────────────────────────┘
|
|
103
|
+
↓
|
|
104
|
+
┌───────────────────────────────┐
|
|
105
|
+
│ OpenSDK3.0 │
|
|
106
|
+
│ SDKSystemHelper → Cmd* │
|
|
107
|
+
│ OpenSDK.postCommand │
|
|
108
|
+
└───────────────────────────────┘
|
|
109
|
+
↓
|
|
110
|
+
┌───────────────────────────────┐
|
|
111
|
+
│ UdiServer │
|
|
112
|
+
│ Handler.* → POST /v1/... │
|
|
113
|
+
└───────────────────────────────┘
|
|
114
|
+
↓
|
|
115
|
+
┌───────────────────────────────┐
|
|
116
|
+
│ UdiServiceCore │
|
|
117
|
+
│ Service.* → Impl.* │
|
|
118
|
+
└───────────────────────────────┘
|
|
119
|
+
|
|
120
|
+
#### Branching at UdiServiceCore
|
|
121
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
122
|
+
│ UdiServiceCore │
|
|
123
|
+
│ Service.* → Impl.* │
|
|
124
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
125
|
+
│ │ Branch rules: │ │
|
|
126
|
+
│ │ • IR_HX/FCT/CD → Service: TouchPanelService │ │
|
|
127
|
+
│ │ • IR_FF → Shell: ffp_tool -f USB -i $path │ │
|
|
128
|
+
│ │ • CAP_EETI → Shell: eUpdate2 -f $path ... │ │
|
|
129
|
+
│ │ • IR_KTC → JNI: McutouchManager.upgradeTouch │ │
|
|
130
|
+
│ │ • IR_TY_NOAAR → VMan: TYPE_FW_UPGRADE_TOUCHBIN │ │
|
|
131
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
132
|
+
└─────────────────────────────────────────────────────────────┘
|
|
133
|
+
|
|
134
|
+
#### VMan direct chain (Android 14+)
|
|
135
|
+
┌───────────────────────────────┐
|
|
136
|
+
│ SeewoOTA │
|
|
137
|
+
│ *VmanUpdateTask │
|
|
138
|
+
│ FwTouchCtrlManager.fwTouchList│
|
|
139
|
+
└───────────────────────────────┘
|
|
140
|
+
↓
|
|
141
|
+
┌───────────────────────────────┐
|
|
142
|
+
│ VMan SDK │
|
|
143
|
+
│ EntityFwTouch │
|
|
144
|
+
│ otaUpgradeTouchBinPath │
|
|
145
|
+
└───────────────────────────────┘
|
|
146
|
+
↓
|
|
147
|
+
┌───────────────────────────────┐
|
|
148
|
+
│ SDKSystemHelper.upgradeTouch │
|
|
149
|
+
│ (continues to OpenSDK chain) │
|
|
150
|
+
└───────────────────────────────┘
|
|
151
|
+
|
|
152
|
+
## Quality checklist
|
|
153
|
+
- [ ] At least one file:line reference per module
|
|
154
|
+
- [ ] Exact UDI URI and Cmd class named
|
|
155
|
+
- [ ] Property/config gates noted (if present)
|
|
156
|
+
- [ ] Result event endpoints named (if used)
|
|
157
|
+
- [ ] Branch rules documented (if applicable)
|
|
158
|
+
- [ ] Boundary type and final action specified
|
|
159
|
+
- [ ] ASCII only in diagrams
|
|
160
|
+
|
|
161
|
+
---
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
———
|
|
2
|
+
|
|
3
|
+
name: gitlab-commit-message
|
|
4
|
+
description: "Enforce the git commit message template and checklist. Use when preparing commit messages or committing."
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Commit Message Template
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
Use this skill to format git commit messages. Keep commits small and cohesive, and always follow the required template below.
|
|
13
|
+
|
|
14
|
+
## Workflow
|
|
15
|
+
|
|
16
|
+
1. Check recent commit messages for tone and granularity (optional).
|
|
17
|
+
2. Ensure the change set is cohesive; split commits if multiple intents exist.
|
|
18
|
+
3. Write the commit message strictly using the template.
|
|
19
|
+
|
|
20
|
+
## Commit Message Template (Mandatory)
|
|
21
|
+
|
|
22
|
+
[类型: bugfix | feature | improve | sonar | ...] 简练总结性的描述
|
|
23
|
+
|
|
24
|
+
[why] 为什么修改,修复什么场景,优化什么功能
|
|
25
|
+
[how] 怎么做,做了什么,后续维护
|
|
26
|
+
[influence] 受影响功能模块
|
|
27
|
+
[check] 预期 xxx
|
|
28
|
+
|
|
29
|
+
[jira] NA 占位
|
|
30
|
+
|
|
31
|
+
### Rules
|
|
32
|
+
|
|
33
|
+
- Keep the summary short and specific.
|
|
34
|
+
- Use only these tags: [why], [how], [influence], [check], [jira].
|
|
35
|
+
- Make [check] steps reproducible and verifiable.
|
|
36
|
+
|
|
37
|
+
## Examples
|
|
38
|
+
|
|
39
|
+
### Good
|
|
40
|
+
|
|
41
|
+
[bugfix] 修复自定义图片加载问题
|
|
42
|
+
|
|
43
|
+
[why] 下次打开没有加载上次上传的自定义背景
|
|
44
|
+
[how] 统一使用 ThemeResHelper 管理,修复数据源不一致的问题
|
|
45
|
+
[influence] Background
|
|
46
|
+
[check] 在菜单-主题-背景,上传自定义图片,关闭应用再打开,看是否还存在,应用到背景是否正常
|
|
47
|
+
|
|
48
|
+
[jira] NA
|
|
49
|
+
|
|
50
|
+
### Bad (overly broad)
|
|
51
|
+
|
|
52
|
+
[feature] 完成 module_formula
|
|
53
|
+
module_rego,module_func 全面重构升级,新增数学计算和图形可视化功能
|
|
54
|
+
|
|
55
|
+
[why] 原有公式模块功能单一且存在稳定性问题,需要提升为支持复杂数学计算和可视化的现代化模块,同时修复LaTeX解析、TabLayout切换、图形渲染等关键问题
|
|
56
|
+
[how] 1.完成100% Java→Kotlin迁移
|
|
57
|
+
2.新增FormulaCalculator数学计算引擎和ExpressionParser表达式解析器
|
|
58
|
+
3.实现FunctionGraphView函数图形可视化
|
|
59
|
+
4.重构UI交互:Done按钮替代回车键确认、修复TabLayout切换机制
|
|
60
|
+
5.优化LaTeX标准化器,修复正则表达式崩溃问
|
|
61
|
+
6.新增完整测试用例和demo界面 7.完善文档结构和PlantUML类图
|
|
62
|
+
[influence] module_formula, module_demo, 依赖公式功能的所有白板模块
|
|
63
|
+
[check] 验证公式编辑器弹窗是否正常;TabLayout四个分类切换正常;数学表达式计算功能正常(如x^2+2x-1),函数图形绘制显示正常;FormulaShape Canvas集成绘制正常;
|
|
64
|
+
|
|
65
|
+
[jira] SXRD-17429
|
|
66
|
+
|
|
67
|
+
### Bad (too vague)
|
|
68
|
+
|
|
69
|
+
[bugfix] 修复
|
|
70
|
+
|
|
71
|
+
[why] 崩溃
|
|
72
|
+
[how] 加判断
|
|
73
|
+
[influence] all
|
|
74
|
+
[check] 无崩溃
|
|
75
|
+
|
|
76
|
+
[jira] S15250039-752
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
## Output Format
|
|
80
|
+
|
|
81
|
+
Use EOF format commit message,keep the code style.
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
[bugfix] ..
|
|
85
|
+
|
|
86
|
+
[why] ..
|
|
87
|
+
[how] ..
|
|
88
|
+
[influence] ..
|
|
89
|
+
[check] ..
|
|
90
|
+
|
|
91
|
+
[jira] ..
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|