@gamehoo/vibe-spec 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 +61 -0
- package/bin/vibe-spec.js +5 -0
- package/package.json +18 -0
- package/specs//346/217/220/344/272/244/350/247/204/350/214/203.md +50 -0
- package/specs//346/226/207/346/241/243/350/247/204/350/214/203.md +72 -0
- package/src/commands/init.js +68 -0
- package/src/commands/list.js +29 -0
- package/src/commands/sync.js +99 -0
- package/src/index.js +44 -0
- package/src/inject.js +53 -0
- package/src/paths.js +15 -0
- package/src/projects.js +22 -0
- package/src/registry.js +35 -0
- package/workflows//346/226/207/346/241/243/346/243/200/346/237/245.md +8 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# vibe-spec
|
|
2
|
+
|
|
3
|
+
跨项目通用 AI 编程规范管理工具。
|
|
4
|
+
|
|
5
|
+
把提交规范、文档规范、工作流程集中维护在一个地方,一条命令同步到所有项目。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g vibe-spec
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 快速开始
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cd your-project
|
|
17
|
+
vibe-spec init
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
默认全选所有规范,直接回车即可。项目中会生成:
|
|
21
|
+
|
|
22
|
+
- `.docs/规范/` — 规范文件
|
|
23
|
+
- `.vibespecrc.json` — 项目配置
|
|
24
|
+
- `CLAUDE.md` 中插入规范引用区域
|
|
25
|
+
|
|
26
|
+
## 命令
|
|
27
|
+
|
|
28
|
+
### `vibe-spec init`
|
|
29
|
+
|
|
30
|
+
在当前项目初始化,交互式选择要使用的规范和工作流。
|
|
31
|
+
|
|
32
|
+
### `vibe-spec sync`
|
|
33
|
+
|
|
34
|
+
将当前项目的规范同步到最新版本。
|
|
35
|
+
|
|
36
|
+
### `vibe-spec sync --all`
|
|
37
|
+
|
|
38
|
+
一次性同步所有已注册的项目。适合修改规范后批量推送。
|
|
39
|
+
|
|
40
|
+
### `vibe-spec list`
|
|
41
|
+
|
|
42
|
+
查看可用的规范、工作流和已注册的项目列表。
|
|
43
|
+
|
|
44
|
+
## 内置规范
|
|
45
|
+
|
|
46
|
+
| 名称 | 类型 | 说明 |
|
|
47
|
+
|------|------|------|
|
|
48
|
+
| 提交规范 | 规范 | 基于 Conventional Commits,类型英文 + 描述中文 |
|
|
49
|
+
| 文档规范 | 规范 | CLAUDE.md / .docs/ 分类与写作规则 |
|
|
50
|
+
| 文档检查 | 工作流 | 索引完整性、格式、时效性检查 |
|
|
51
|
+
|
|
52
|
+
## 开发
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
git clone <repo>
|
|
56
|
+
cd vibe-spec
|
|
57
|
+
npm install
|
|
58
|
+
npm link # 全局 vibe-spec 指向本地仓库
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`npm link` 后修改 `specs/` 下的文件立刻生效,`vibe-spec sync --all` 直接读取本地文件。
|
package/bin/vibe-spec.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gamehoo/vibe-spec",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "跨项目通用 AI 编程规范管理工具",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vibe-spec": "./bin/vibe-spec.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/",
|
|
12
|
+
"specs/",
|
|
13
|
+
"workflows/"
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@inquirer/prompts": "^7.0.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Git 提交规范
|
|
2
|
+
|
|
3
|
+
基于 [Conventional Commits](https://www.conventionalcommits.org/) 规范,类型关键字使用英文,描述使用中文。
|
|
4
|
+
|
|
5
|
+
## 格式
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
<类型>(范围): 简短描述
|
|
9
|
+
|
|
10
|
+
可选的详细说明
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- **类型**:必填,英文小写。
|
|
14
|
+
- **范围**:可选,改动所属模块。改动范围明确时加,跨模块或杂务类可省略。
|
|
15
|
+
- **简短描述**:必填,中文,不超过 50 字,不加句号。
|
|
16
|
+
- **详细说明**:可选,中文,用空行与标题分隔,解释"为什么"而非"做了什么"。
|
|
17
|
+
- **不添加** `Co-Authored-By` 等自动生成的尾部标记。
|
|
18
|
+
|
|
19
|
+
## 类型
|
|
20
|
+
|
|
21
|
+
| 类型 | 用途 |
|
|
22
|
+
|------|------|
|
|
23
|
+
| `feat` | 新功能 |
|
|
24
|
+
| `fix` | 修复 bug |
|
|
25
|
+
| `refactor` | 重构(不改变外部行为) |
|
|
26
|
+
| `docs` | 文档变更 |
|
|
27
|
+
| `style` | 代码格式调整(不影响逻辑) |
|
|
28
|
+
| `chore` | 构建、配置、依赖等杂务 |
|
|
29
|
+
| `test` | 测试相关 |
|
|
30
|
+
| `perf` | 性能优化 |
|
|
31
|
+
|
|
32
|
+
## 示例
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
feat(kanban): 添加任务拖拽排序
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
fix(data): 修复任务删除后列排序错误
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
chore: 升级 Electron 至 v33
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
refactor(layout): 将面板尺寸逻辑抽取为 useResizable hook
|
|
48
|
+
|
|
49
|
+
原先的 inline 计算散落在多个组件中,难以复用和测试。
|
|
50
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# 文档规范
|
|
2
|
+
|
|
3
|
+
## 文档分类
|
|
4
|
+
|
|
5
|
+
| 位置 | 用途 | 版本控制 |
|
|
6
|
+
|------|------|----------|
|
|
7
|
+
| `CLAUDE.md` | AI 协作指令:项目理念、开发规范、工作流程、工具、文档索引 | 入库 |
|
|
8
|
+
| `CLAUDE.local.md` | 本机特有配置:个人路径、镜像地址、本地环境变量等 | 不入库 |
|
|
9
|
+
| `.docs/` | 项目知识文档,按子目录分类(见下方) | 入库 |
|
|
10
|
+
|
|
11
|
+
判断原则:
|
|
12
|
+
- 只对当前机器有意义 → `CLAUDE.local.md`
|
|
13
|
+
- 对所有开发者有意义、指导 AI 行为 → `CLAUDE.md`
|
|
14
|
+
- 需要详细展开的项目知识 → `.docs/`,并在 `CLAUDE.md` 文档索引中建立链接
|
|
15
|
+
- 可以从代码或 git 历史推断的信息不写文档
|
|
16
|
+
|
|
17
|
+
## .docs/ 目录结构
|
|
18
|
+
|
|
19
|
+
| 子目录 | 用途 | 更新方式 | 命名规则 |
|
|
20
|
+
|--------|------|----------|----------|
|
|
21
|
+
| `规范/` | 开发约定,长期稳定 | 随项目迭代更新已有文档 | `<主题>.md` |
|
|
22
|
+
| `概览/` | 复杂模块的抽象总结,帮助理解大局 | 随项目迭代更新已有文档 | `<主题>.md` |
|
|
23
|
+
| `方案/` | 具体改动的技术方案,一事一文档 | 每次新增,写完后基本不变 | `YYYY-MM-DD <主题>.md` |
|
|
24
|
+
| `测试/` | 功能的手动测试用例,发布前全量回归 | 随功能新增或变更同步更新 | `<功能名>/<子项>.md` |
|
|
25
|
+
| `产品/` | 需求和产品定义 | 每次新增,写完后基本不变 | `YYYY-MM-DD <主题>.md` |
|
|
26
|
+
|
|
27
|
+
- 新建文档前先确认是否可以合并到已有文档中
|
|
28
|
+
- 不符合任何分类的内容不建文档
|
|
29
|
+
- 单次性的调研、笔记不进 `.docs/`
|
|
30
|
+
|
|
31
|
+
## 命名规范
|
|
32
|
+
|
|
33
|
+
- 文档统一使用中文命名
|
|
34
|
+
- 避免使用特殊字符,中文与英文、数字之间加空格
|
|
35
|
+
|
|
36
|
+
## 内容格式
|
|
37
|
+
|
|
38
|
+
- 语言:中文为主,专有名词保留英文原文(如 Electron、SQLite、Agent)
|
|
39
|
+
|
|
40
|
+
## 方案文档写作要求
|
|
41
|
+
|
|
42
|
+
方案文档(`.docs/方案/`)的读者可能不了解项目、不了解相关技术。写作时遵循两个原则:**容易理解**和**阅读高效**。
|
|
43
|
+
|
|
44
|
+
### 渐进式展开
|
|
45
|
+
|
|
46
|
+
文档按抽象层级组织,读者可以只看前面就理解全貌,按需深入细节:
|
|
47
|
+
|
|
48
|
+
1. **摘要**(1 段话)— 做什么、怎么做、关键决策,30 秒读完
|
|
49
|
+
2. **全局设计**(图 + 文字)— 架构、UI、数据流,不含代码,5 分钟读完
|
|
50
|
+
3. **详细设计**(代码 + 配置)— 各模块的接口、类型、实现要点,按需查阅
|
|
51
|
+
4. **验证与证据**(表格)— 可行性验证、SDK 映射等,仅在需要核查时阅读
|
|
52
|
+
|
|
53
|
+
每一层都应该是自包含的——只读摘要就能决定是否继续读;只读全局设计就能参与讨论。
|
|
54
|
+
|
|
55
|
+
### 术语处理
|
|
56
|
+
|
|
57
|
+
- 技术术语在**首次出现**时用括号解释,例如:`IPC(进程间通信,Electron 中两个进程交换数据的方式)`
|
|
58
|
+
- 之后可以直接使用缩写
|
|
59
|
+
- 如果术语较多,在文档末尾附「术语表」
|
|
60
|
+
|
|
61
|
+
### 全局设计不放代码
|
|
62
|
+
|
|
63
|
+
架构图、UI 布局、数据流等全局设计章节只用图和文字描述,不放代码片段。代码属于详细设计层级——想看实现的读者会往下翻,只想理解方案的读者不应该被代码块打断。
|
|
64
|
+
|
|
65
|
+
### 表格胜于段落
|
|
66
|
+
|
|
67
|
+
当信息有多个并列维度时(如"每种消息类型的来源"、"每个组件是内置还是自定义"),优先用表格,不要用段落罗列。
|
|
68
|
+
|
|
69
|
+
## 维护原则
|
|
70
|
+
|
|
71
|
+
- `CLAUDE.md` 保持精简,详细内容下沉到 `.docs/` 子文档
|
|
72
|
+
- 新增 `.docs/` 文档后,必须在 `CLAUDE.md` 文档索引中添加条目,包含链接和使用场景说明(告诉 AI 什么时候需要参考该文档)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { checkbox } from '@inquirer/prompts'
|
|
4
|
+
import { getAvailableItems, getPackageVersion } from '../registry.js'
|
|
5
|
+
import { registerProject } from '../projects.js'
|
|
6
|
+
import { buildInjection, injectIntoClaude } from '../inject.js'
|
|
7
|
+
|
|
8
|
+
const SPEC_DIR = '.docs/规范'
|
|
9
|
+
|
|
10
|
+
export async function init() {
|
|
11
|
+
const cwd = process.cwd()
|
|
12
|
+
const rcPath = path.join(cwd, '.vibespecrc.json')
|
|
13
|
+
|
|
14
|
+
if (fs.existsSync(rcPath)) {
|
|
15
|
+
console.log('当前项目已初始化,使用 vibe-spec sync 同步最新规范')
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { specs, workflows } = getAvailableItems()
|
|
20
|
+
const allItems = [
|
|
21
|
+
...specs.map((s) => ({ name: `${s.name} (规范)`, value: s, checked: true })),
|
|
22
|
+
...workflows.map((w) => ({ name: `${w.name} (工作流)`, value: w, checked: true })),
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
const selected = await checkbox({
|
|
26
|
+
message: '规范和工作流(取消选择用空格,确认用回车)',
|
|
27
|
+
choices: allItems,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
if (selected.length === 0) {
|
|
31
|
+
console.log('未选择任何规范,退出')
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const selectedSpecs = selected.filter((s) => s.type === 'spec')
|
|
36
|
+
const selectedWorkflows = selected.filter((s) => s.type === 'workflow')
|
|
37
|
+
|
|
38
|
+
// 复制规范文件
|
|
39
|
+
const destDir = path.join(cwd, SPEC_DIR)
|
|
40
|
+
fs.mkdirSync(destDir, { recursive: true })
|
|
41
|
+
|
|
42
|
+
for (const item of selected) {
|
|
43
|
+
const dest = path.join(destDir, path.basename(item.file))
|
|
44
|
+
fs.copyFileSync(item.file, dest)
|
|
45
|
+
console.log(`✔ 已写入 ${SPEC_DIR}/${path.basename(item.file)}`)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 注入 CLAUDE.md
|
|
49
|
+
const specNames = selectedSpecs.map((s) => s.name)
|
|
50
|
+
const workflowNames = selectedWorkflows.map((w) => w.name)
|
|
51
|
+
const injection = buildInjection(specNames, workflowNames, SPEC_DIR)
|
|
52
|
+
injectIntoClaude(cwd, injection)
|
|
53
|
+
console.log('✔ 已注入 CLAUDE.md [vibe-spec 区域]')
|
|
54
|
+
|
|
55
|
+
// 创建 .vibespecrc.json
|
|
56
|
+
const rc = {
|
|
57
|
+
specs: specNames,
|
|
58
|
+
workflows: workflowNames,
|
|
59
|
+
specDir: SPEC_DIR,
|
|
60
|
+
version: getPackageVersion(),
|
|
61
|
+
}
|
|
62
|
+
fs.writeFileSync(rcPath, JSON.stringify(rc, null, 2) + '\n')
|
|
63
|
+
console.log('✔ 已创建 .vibespecrc.json')
|
|
64
|
+
|
|
65
|
+
// 注册项目
|
|
66
|
+
registerProject(cwd)
|
|
67
|
+
console.log('✔ 已注册项目路径(sync --all 时会同步此项目)')
|
|
68
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getAvailableItems } from '../registry.js'
|
|
2
|
+
import { loadProjects } from '../projects.js'
|
|
3
|
+
|
|
4
|
+
export async function list() {
|
|
5
|
+
const { specs, workflows } = getAvailableItems()
|
|
6
|
+
const projects = loadProjects()
|
|
7
|
+
|
|
8
|
+
console.log('规范:')
|
|
9
|
+
for (const s of specs) {
|
|
10
|
+
console.log(` ${s.name}`)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (workflows.length > 0) {
|
|
14
|
+
console.log('\n工作流:')
|
|
15
|
+
for (const w of workflows) {
|
|
16
|
+
console.log(` ${w.name}`)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log(`\n已注册项目(${projects.length}):`)
|
|
21
|
+
if (projects.length === 0) {
|
|
22
|
+
console.log(' (无)')
|
|
23
|
+
} else {
|
|
24
|
+
const home = process.env.HOME || ''
|
|
25
|
+
for (const p of projects) {
|
|
26
|
+
console.log(` ${p.replace(home, '~')}`)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { SPECS_DIR, WORKFLOWS_DIR } from '../paths.js'
|
|
4
|
+
import { getPackageVersion } from '../registry.js'
|
|
5
|
+
import { loadProjects, saveProjects } from '../projects.js'
|
|
6
|
+
import { buildInjection, injectIntoClaude } from '../inject.js'
|
|
7
|
+
|
|
8
|
+
export async function sync(all) {
|
|
9
|
+
if (all) {
|
|
10
|
+
await syncAll()
|
|
11
|
+
} else {
|
|
12
|
+
await syncProject(process.cwd())
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function syncProject(projectDir) {
|
|
17
|
+
const rcPath = path.join(projectDir, '.vibespecrc.json')
|
|
18
|
+
if (!fs.existsSync(rcPath)) {
|
|
19
|
+
throw new Error('未找到 .vibespecrc.json,请先运行 vibe-spec init')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const rc = JSON.parse(fs.readFileSync(rcPath, 'utf-8'))
|
|
23
|
+
const pkgVersion = getPackageVersion()
|
|
24
|
+
const destDir = path.join(projectDir, rc.specDir)
|
|
25
|
+
const updated = []
|
|
26
|
+
|
|
27
|
+
fs.mkdirSync(destDir, { recursive: true })
|
|
28
|
+
|
|
29
|
+
// 同步 specs
|
|
30
|
+
for (const name of rc.specs) {
|
|
31
|
+
const src = path.join(SPECS_DIR, `${name}.md`)
|
|
32
|
+
const dest = path.join(destDir, `${name}.md`)
|
|
33
|
+
if (syncFile(src, dest)) {
|
|
34
|
+
updated.push(name)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 同步 workflows
|
|
39
|
+
for (const name of rc.workflows) {
|
|
40
|
+
const src = path.join(WORKFLOWS_DIR, `${name}.md`)
|
|
41
|
+
const dest = path.join(destDir, `${name}.md`)
|
|
42
|
+
if (syncFile(src, dest)) {
|
|
43
|
+
updated.push(name)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 注入 CLAUDE.md
|
|
48
|
+
const injection = buildInjection(rc.specs, rc.workflows, rc.specDir)
|
|
49
|
+
injectIntoClaude(projectDir, injection)
|
|
50
|
+
|
|
51
|
+
// 更新版本号
|
|
52
|
+
rc.version = pkgVersion
|
|
53
|
+
fs.writeFileSync(rcPath, JSON.stringify(rc, null, 2) + '\n')
|
|
54
|
+
|
|
55
|
+
return { updated, projectDir }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function syncAll() {
|
|
59
|
+
const projects = loadProjects()
|
|
60
|
+
|
|
61
|
+
if (projects.length === 0) {
|
|
62
|
+
console.log('没有已注册的项目,请先在项目中运行 vibe-spec init')
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 过滤掉已不存在的项目
|
|
67
|
+
const valid = projects.filter((p) => fs.existsSync(p))
|
|
68
|
+
if (valid.length < projects.length) {
|
|
69
|
+
saveProjects(valid)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log(`同步 ${valid.length} 个项目:`)
|
|
73
|
+
|
|
74
|
+
for (const projectDir of valid) {
|
|
75
|
+
try {
|
|
76
|
+
const { updated } = await syncProject(projectDir)
|
|
77
|
+
const home = process.env.HOME || ''
|
|
78
|
+
const display = projectDir.replace(home, '~')
|
|
79
|
+
const detail = updated.length > 0 ? updated.join(', ') + ' 已更新' : '无变化'
|
|
80
|
+
console.log(` ✓ ${display} ${detail}`)
|
|
81
|
+
} catch (err) {
|
|
82
|
+
const home = process.env.HOME || ''
|
|
83
|
+
const display = projectDir.replace(home, '~')
|
|
84
|
+
console.log(` ✗ ${display} ${err.message}`)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function syncFile(src, dest) {
|
|
90
|
+
if (!fs.existsSync(src)) return false
|
|
91
|
+
|
|
92
|
+
const srcContent = fs.readFileSync(src, 'utf-8')
|
|
93
|
+
const destContent = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf-8') : ''
|
|
94
|
+
|
|
95
|
+
if (srcContent === destContent) return false
|
|
96
|
+
|
|
97
|
+
fs.copyFileSync(src, dest)
|
|
98
|
+
return true
|
|
99
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { init } from './commands/init.js'
|
|
2
|
+
import { sync } from './commands/sync.js'
|
|
3
|
+
import { list } from './commands/list.js'
|
|
4
|
+
|
|
5
|
+
const HELP = `
|
|
6
|
+
vibe-spec — 跨项目通用 AI 编程规范管理工具
|
|
7
|
+
|
|
8
|
+
命令:
|
|
9
|
+
vibe-spec init 新项目接入,交互式选择规范并安装
|
|
10
|
+
vibe-spec sync 同步当前项目到最新版本
|
|
11
|
+
vibe-spec sync --all 一次性推送到所有已注册项目
|
|
12
|
+
vibe-spec list 查看可用的规范、工作流和已注册项目
|
|
13
|
+
vibe-spec --help 显示帮助信息
|
|
14
|
+
`.trim()
|
|
15
|
+
|
|
16
|
+
export async function run(args) {
|
|
17
|
+
const command = args[0]
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
switch (command) {
|
|
21
|
+
case 'init':
|
|
22
|
+
await init()
|
|
23
|
+
break
|
|
24
|
+
case 'sync':
|
|
25
|
+
await sync(args.includes('--all'))
|
|
26
|
+
break
|
|
27
|
+
case 'list':
|
|
28
|
+
await list()
|
|
29
|
+
break
|
|
30
|
+
case '--help':
|
|
31
|
+
case '-h':
|
|
32
|
+
case undefined:
|
|
33
|
+
console.log(HELP)
|
|
34
|
+
break
|
|
35
|
+
default:
|
|
36
|
+
console.error(`未知命令: ${command}`)
|
|
37
|
+
console.log(HELP)
|
|
38
|
+
process.exit(1)
|
|
39
|
+
}
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error(`错误: ${err.message}`)
|
|
42
|
+
process.exit(1)
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/inject.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
const START_MARKER = '<!-- vibe-spec:start -->'
|
|
5
|
+
const END_MARKER = '<!-- vibe-spec:end -->'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 根据选中的 specs 和 workflows 生成注入内容
|
|
9
|
+
*/
|
|
10
|
+
export function buildInjection(specNames, workflowNames, specDir) {
|
|
11
|
+
const lines = [START_MARKER, '## 开发规范', '']
|
|
12
|
+
|
|
13
|
+
for (const name of specNames) {
|
|
14
|
+
lines.push(`- [${name}](./${specDir}/${name}.md)`)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (workflowNames.length > 0) {
|
|
18
|
+
lines.push('', '## 工作流程', '')
|
|
19
|
+
for (const name of workflowNames) {
|
|
20
|
+
lines.push(`- [${name}](./${specDir}/${name}.md)`)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
lines.push(END_MARKER)
|
|
25
|
+
return lines.join('\n')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 将内容注入 CLAUDE.md 的标记区域
|
|
30
|
+
* 如果标记不存在,追加到文件末尾
|
|
31
|
+
*/
|
|
32
|
+
export function injectIntoClaude(projectDir, content) {
|
|
33
|
+
const claudePath = path.join(projectDir, 'CLAUDE.md')
|
|
34
|
+
|
|
35
|
+
if (!fs.existsSync(claudePath)) {
|
|
36
|
+
fs.writeFileSync(claudePath, content + '\n')
|
|
37
|
+
return 'created'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const existing = fs.readFileSync(claudePath, 'utf-8')
|
|
41
|
+
const startIdx = existing.indexOf(START_MARKER)
|
|
42
|
+
const endIdx = existing.indexOf(END_MARKER)
|
|
43
|
+
|
|
44
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
45
|
+
const before = existing.slice(0, startIdx)
|
|
46
|
+
const after = existing.slice(endIdx + END_MARKER.length)
|
|
47
|
+
fs.writeFileSync(claudePath, before + content + after)
|
|
48
|
+
return 'updated'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fs.writeFileSync(claudePath, existing.trimEnd() + '\n\n' + content + '\n')
|
|
52
|
+
return 'appended'
|
|
53
|
+
}
|
package/src/paths.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
|
|
5
|
+
// npm 包根目录(specs/、workflows/ 所在位置)
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
export const PKG_ROOT = path.resolve(__dirname, '..')
|
|
8
|
+
|
|
9
|
+
// 全局数据目录
|
|
10
|
+
export const DATA_DIR = path.join(os.homedir(), '.vibespec')
|
|
11
|
+
export const PROJECTS_FILE = path.join(DATA_DIR, 'projects.json')
|
|
12
|
+
|
|
13
|
+
// npm 包内的资源目录
|
|
14
|
+
export const SPECS_DIR = path.join(PKG_ROOT, 'specs')
|
|
15
|
+
export const WORKFLOWS_DIR = path.join(PKG_ROOT, 'workflows')
|
package/src/projects.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { DATA_DIR, PROJECTS_FILE } from './paths.js'
|
|
4
|
+
|
|
5
|
+
export function loadProjects() {
|
|
6
|
+
if (!fs.existsSync(PROJECTS_FILE)) return []
|
|
7
|
+
return JSON.parse(fs.readFileSync(PROJECTS_FILE, 'utf-8'))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function saveProjects(projects) {
|
|
11
|
+
fs.mkdirSync(DATA_DIR, { recursive: true })
|
|
12
|
+
fs.writeFileSync(PROJECTS_FILE, JSON.stringify(projects, null, 2) + '\n')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function registerProject(projectPath) {
|
|
16
|
+
const projects = loadProjects()
|
|
17
|
+
const abs = path.resolve(projectPath)
|
|
18
|
+
if (!projects.includes(abs)) {
|
|
19
|
+
projects.push(abs)
|
|
20
|
+
saveProjects(projects)
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/registry.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { SPECS_DIR, WORKFLOWS_DIR, PKG_ROOT } from './paths.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 读取 npm 包内所有可用的 specs 和 workflows
|
|
7
|
+
*/
|
|
8
|
+
export function getAvailableItems() {
|
|
9
|
+
const specs = readDir(SPECS_DIR).map((file) => ({
|
|
10
|
+
name: path.basename(file, '.md'),
|
|
11
|
+
file: path.join(SPECS_DIR, file),
|
|
12
|
+
type: 'spec',
|
|
13
|
+
}))
|
|
14
|
+
|
|
15
|
+
const workflows = readDir(WORKFLOWS_DIR).map((file) => ({
|
|
16
|
+
name: path.basename(file, '.md'),
|
|
17
|
+
file: path.join(WORKFLOWS_DIR, file),
|
|
18
|
+
type: 'workflow',
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
return { specs, workflows }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 获取当前包版本号
|
|
26
|
+
*/
|
|
27
|
+
export function getPackageVersion() {
|
|
28
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf-8'))
|
|
29
|
+
return pkg.version
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readDir(dir) {
|
|
33
|
+
if (!fs.existsSync(dir)) return []
|
|
34
|
+
return fs.readdirSync(dir).filter((f) => f.endsWith('.md'))
|
|
35
|
+
}
|