growork 1.0.1 → 1.1.2

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.
@@ -0,0 +1,215 @@
1
+ # GroWork 1.1.0 PRD
2
+
3
+ ## 概述
4
+
5
+ | 项目 | 内容 |
6
+ | -------- | ----------------------------- |
7
+ | 产品名称 | GroWork |
8
+ | 版本 | 1.1.0 |
9
+ | 状态 | ✅ 已完成 |
10
+ | 产品形态 | CLI |
11
+ | 核心改动 | 引入版本和 feature 层级结构 |
12
+
13
+ ## 背景与目标
14
+
15
+ ### 背景
16
+
17
+ 当前 1.0.0 配置是扁平的 docs 列表,没有版本和 features 概念,不便于按功能模块组织文档。实际开发中,每个功能都是按 feature 闭环的,包含 PRD、设计、接口、测试等多种文档。
18
+
19
+ ### 目标
20
+
21
+ 1. 引入 **版本 → feature → 文档类型** 的层级结构
22
+ 2. 简化配置,最简情况只需写 URL
23
+ 3. 支持全局文档(不归属版本)
24
+
25
+ ## 用户场景
26
+
27
+ 使用 Claude Code 等 Agent 工具的开发者,希望同步远程文档到本地,让 AI 直接读取进行开发。文档按版本和功能模块组织,便于 AI 理解项目结构。
28
+
29
+ ## 功能需求
30
+
31
+ ### 文档类型定义
32
+
33
+ | 类型 | 说明 |
34
+ | -------- | -------------------------- |
35
+ | `prd` | 产品需求文档 |
36
+ | `design` | 设计稿/交互文档 |
37
+ | `api` | 接口/技术方案 |
38
+ | `test` | 测试用例 |
39
+ | `custom` | 全局文档,不归属任何版本 |
40
+
41
+ ### 配置文件格式
42
+
43
+ ```yaml
44
+ # growork.config.yaml
45
+ feishu:
46
+ appId: "cli_xxx"
47
+ appSecret: "xxx"
48
+ domain: "feishu" # feishu(中国版) 或 lark(国际版)
49
+
50
+ notion:
51
+ token: "ntn_xxx"
52
+
53
+ # 输出根目录(默认 "docs")
54
+ outputDir: "docs"
55
+
56
+ # 全局文档(不跟版本)
57
+ custom:
58
+ - https://feishu.cn/xxx # 最简写法,name 从文档标题获取
59
+ - url: https://notion.so/yyy
60
+ name: 技术架构 # 可选:自定义名称
61
+
62
+ # 版本化文档
63
+ versions:
64
+ v1.2:
65
+ 用户登录: # feature 名称
66
+ prd:
67
+ - https://feishu.cn/xxx
68
+ design:
69
+ - https://feishu.cn/yyy
70
+ api:
71
+ - https://feishu.cn/zzz
72
+ test:
73
+ - https://feishu.cn/aaa
74
+
75
+ # 简单 feature 可不分类
76
+ 小优化:
77
+ - https://feishu.cn/bbb
78
+ ```
79
+
80
+ ### 输出目录结构
81
+
82
+ ```
83
+ docs/
84
+ ├── custom/ # 全局文档
85
+ │ ├── 项目总览.md
86
+ │ └── 技术架构.md
87
+ └── v1.2/
88
+ ├── 用户登录/
89
+ │ ├── prd/
90
+ │ │ └── {文档标题}.md
91
+ │ ├── design/
92
+ │ │ └── {文档标题}.md
93
+ │ ├── api/
94
+ │ │ └── {文档标题}.md
95
+ │ └── test/
96
+ │ └── {文档标题}.md
97
+ └── 小优化/
98
+ └── {文档标题}.md
99
+ ```
100
+
101
+ ### 默认值规则
102
+
103
+ | 字段 | 默认值 |
104
+ | --------- | --------------------------------------------------- |
105
+ | outputDir | `docs` |
106
+ | name | 从文档标题自动获取,自动去除特殊字符 |
107
+ | output | `{outputDir}/{version}/{feature}/{type}/{name}.md` |
108
+ | type | 自动从 URL 推断(feishu.cn/larksuite.com → feishu,notion.so/notion.site → notion)|
109
+
110
+ ### 文件名处理
111
+
112
+ 文档标题作为文件名时,自动去除以下特殊字符:
113
+ - 文件系统保留字符:`/ \ : * ? " < > |`
114
+ - 空格替换为 `-`
115
+ - 连续的 `-` 合并为单个
116
+ - 首尾 `-` 去除
117
+
118
+ 示例:`用户登录 / 注册流程 (v1.0)` → `用户登录-注册流程-v1.0.md`
119
+
120
+ ### 命令列表
121
+
122
+ | 命令 | 说明 | 状态 |
123
+ |-----|-----|-----|
124
+ | `growork init` | 生成 1.1.0 配置文件模板 | ✅ |
125
+ | `growork sync` | 同步所有文档 | ✅ |
126
+ | `growork sync --ver <version>` | 只同步指定版本 | ✅ |
127
+ | `growork sync -f <feature>` | 只同步指定 feature | ✅ |
128
+ | `growork sync -c` | 只同步全局文档 | ✅ |
129
+ | `growork list` | 列出所有文档(按层级展示) | ✅ |
130
+ | `growork migrate` | 将 1.0.0 配置迁移到 1.1.0 | ✅ |
131
+
132
+ ### 同步命令参数
133
+
134
+ ```bash
135
+ growork sync # 同步全部
136
+ growork sync --ver v1.2 # 只同步指定版本(避免与 --version 冲突)
137
+ growork sync -f 用户登录 # 只同步指定 feature
138
+ growork sync -c # 只同步全局文档
139
+ ```
140
+
141
+ > 注:使用 `--ver` 而非 `--version` 是因为 commander 保留了 `--version` 用于显示版本号
142
+
143
+ ### 核心流程
144
+
145
+ 1. 用户编辑 `growork.config.yaml`
146
+ 2. 运行 `growork sync` 命令
147
+ 3. 工具解析配置,按层级同步文档
148
+ 4. 文档输出到对应目录,name 从远程文档标题获取
149
+
150
+ ## 技术设计
151
+
152
+ ### 配置解析
153
+
154
+ ```typescript
155
+ // 文档输入支持字符串(URL)或对象形式
156
+ type DocInput = string | { url: string; name?: string }
157
+
158
+ // feature 可以是数组(简单形式)或分类对象
159
+ type FeatureValue = DocInput[] | {
160
+ prd?: DocInput[]
161
+ design?: DocInput[]
162
+ api?: DocInput[]
163
+ test?: DocInput[]
164
+ }
165
+
166
+ interface GroworkConfigV2 {
167
+ feishu?: {
168
+ appId: string
169
+ appSecret: string
170
+ domain?: 'feishu' | 'lark'
171
+ }
172
+ notion?: { token: string }
173
+ outputDir?: string // 输出根目录,默认 "docs"
174
+ custom?: DocInput[]
175
+ versions?: {
176
+ [version: string]: {
177
+ [feature: string]: FeatureValue
178
+ }
179
+ }
180
+ }
181
+
182
+ // 同步选项
183
+ interface SyncOptions {
184
+ version?: string
185
+ feature?: string
186
+ custom?: boolean
187
+ }
188
+ ```
189
+
190
+ ### 兼容性
191
+
192
+ - 1.1.0 配置格式与 1.0.0 不兼容
193
+ - ✅ 提供 `growork migrate` 命令将 1.0.0 配置迁移到 1.1.0
194
+ - 迁移时自动备份原配置到 `growork.config.yaml.v1.backup`
195
+
196
+ ## 验收标准
197
+
198
+ - [x] 支持 custom 全局文档同步
199
+ - [x] 支持 versions 层级配置解析
200
+ - [x] 支持 prd/design/api/test 四种文档类型
201
+ - [x] 支持简写(只写 URL)和完整写法
202
+ - [x] name 默认从文档标题获取(从 Markdown 内容提取 H1 标题)
203
+ - [x] 输出目录按 `版本/feature/类型/` 结构
204
+ - [x] `--ver` 参数过滤指定版本
205
+ - [x] `-f/--feature` 参数过滤指定功能
206
+ - [x] `-c/--custom` 参数只同步全局文档
207
+ - [x] `growork migrate` 命令支持 1.0.0 到 1.1.0 迁移
208
+ - [x] `growork list` 命令按层级展示文档列表
209
+ - [x] 支持自定义输出根目录(outputDir 配置)
210
+
211
+ ## 开放问题
212
+
213
+ - [ ] 是否需要支持多个配置文件?
214
+ - [ ] 是否支持增量同步(仅同步有变更的文档)?
215
+ - [ ] 凭证是否改用环境变量?
@@ -0,0 +1,136 @@
1
+ # GroWork 1.1.2 PRD
2
+
3
+ ## 概述
4
+
5
+ | 项目 | 内容 |
6
+ | -------- | ----------------------------- |
7
+ | 产品名称 | GroWork |
8
+ | 版本 | 1.1.2 |
9
+ | 状态 | 📝 待开发 |
10
+ | 产品形态 | CLI |
11
+ | 核心改动 | design 字段支持 Figma 链接 |
12
+
13
+ ## 背景与目标
14
+
15
+ ### 背景
16
+
17
+ 当前 design 字段只支持飞书/Notion 文档链接,但设计稿通常在 Figma 中。开发者希望在 design 字段直接配置 Figma 地址,同步时自动生成包含链接的 MD 文件。
18
+
19
+ ### 目标
20
+
21
+ 1. design 字段支持 Figma URL
22
+ 2. Figma URL 直接生成 MD 文件(不下载),内容为链接本身
23
+ 3. 保持现有配置结构不变
24
+
25
+ ## 功能需求
26
+
27
+ ### 配置文件格式
28
+
29
+ ```yaml
30
+ # growork.config.yaml(结构不变)
31
+
32
+ versions:
33
+ v1.9:
34
+ QuickCard:
35
+ prd:
36
+ - "https://xxx.feishu.cn/docx/xxx" # 飞书:下载内容
37
+ design:
38
+ - "https://www.figma.com/design/xxx?node-id=1-20" # Figma:生成链接
39
+ - "https://www.figma.com/design/xxx?node-id=1-30"
40
+ api:
41
+ - "https://xxx.feishu.cn/docx/yyy" # 飞书:下载内容
42
+ ```
43
+
44
+ ### URL 类型识别
45
+
46
+ | URL 特征 | 类型 | 处理方式 |
47
+ |---------|------|---------|
48
+ | `feishu.cn` / `larksuite.com` | 飞书 | 调用 API 下载内容 |
49
+ | `notion.so` / `notion.site` | Notion | 调用 API 下载内容 |
50
+ | `figma.com` | Figma | 直接生成链接 MD |
51
+
52
+ ### 输出目录结构
53
+
54
+ ```
55
+ docs/
56
+ └── v1.9/
57
+ └── QuickCard/
58
+ ├── prd/
59
+ │ └── xxx.md # 飞书文档内容
60
+ ├── design/
61
+ │ └── design.md # Figma 链接列表
62
+ └── api/
63
+ └── yyy.md # 飞书文档内容
64
+ ```
65
+
66
+ ### 生成的 design.md 内容
67
+
68
+ ```markdown
69
+ # QuickCard 设计稿
70
+
71
+ - https://www.figma.com/design/xxx?node-id=1-20
72
+ - https://www.figma.com/design/xxx?node-id=1-30
73
+ ```
74
+
75
+ ## 技术设计
76
+
77
+ ### inferDocType 扩展
78
+
79
+ ```typescript
80
+ export function inferDocType(url: string): 'feishu' | 'notion' | 'figma' {
81
+ if (url.includes('feishu.cn') || url.includes('larksuite.com')) {
82
+ return 'feishu';
83
+ }
84
+ if (url.includes('notion.so') || url.includes('notion.site')) {
85
+ return 'notion';
86
+ }
87
+ if (url.includes('figma.com')) {
88
+ return 'figma';
89
+ }
90
+ throw new Error(`无法从 URL 推断文档类型: ${url}`);
91
+ }
92
+ ```
93
+
94
+ ### 同步逻辑调整
95
+
96
+ ```typescript
97
+ // sync.ts
98
+ for (const doc of docs) {
99
+ if (doc.type === 'figma') {
100
+ // Figma:直接写入链接,不调用 API
101
+ // 同一 feature 的多个 Figma URL 合并到一个 design.md
102
+ continue;
103
+ }
104
+ // 飞书/Notion:调用 API 下载内容
105
+ // ...现有逻辑
106
+ }
107
+ ```
108
+
109
+ ### Figma 文档合并
110
+
111
+ 同一 feature 下的多个 Figma URL 合并到一个 `design.md` 文件:
112
+
113
+ ```typescript
114
+ // 按 outputPath 分组 Figma 文档
115
+ const figmaGroups = new Map<string, string[]>();
116
+ for (const doc of figmaDocs) {
117
+ const dir = path.dirname(doc.outputPath);
118
+ const urls = figmaGroups.get(dir) || [];
119
+ urls.push(doc.url);
120
+ figmaGroups.set(dir, urls);
121
+ }
122
+
123
+ // 生成合并后的 design.md
124
+ for (const [dir, urls] of figmaGroups) {
125
+ const content = `# 设计稿\n\n${urls.map(u => `- ${u}`).join('\n')}\n`;
126
+ writeFile(`${dir}/design.md`, content);
127
+ }
128
+ ```
129
+
130
+ ## 验收标准
131
+
132
+ - [ ] `inferDocType` 支持识别 Figma URL
133
+ - [ ] Figma URL 不调用 API,直接生成 MD
134
+ - [ ] 同一 feature 的多个 Figma URL 合并到一个 `design.md`
135
+ - [ ] 飞书/Notion 文档同步逻辑不受影响
136
+ - [ ] `--ver` 和 `-f` 参数对 Figma 同样有效
@@ -0,0 +1,65 @@
1
+ # [产品名称] v[版本] PRD
2
+
3
+ ## 概述
4
+
5
+ | 项目 | 内容 |
6
+ |-----|-----|
7
+ | 产品名称 | xxx |
8
+ | 版本 | v1.0 |
9
+ | 产品形态 | CLI / Web / App |
10
+
11
+ ## 背景与目标
12
+
13
+ ### 背景
14
+
15
+ 为什么做?当前痛点是什么?
16
+
17
+ ### 目标
18
+
19
+ 做成什么样?成功标准是什么?
20
+
21
+ ## 用户场景
22
+
23
+ 谁在什么场景下用?解决什么问题?
24
+
25
+ ## 功能需求
26
+
27
+ ### 功能列表
28
+
29
+ | 功能 | 说明 | 优先级 |
30
+ |-----|-----|-------|
31
+ | 功能A | 做什么 | P0 |
32
+ | 功能B | 做什么 | P1 |
33
+
34
+ ### 核心流程
35
+
36
+ ```
37
+ 用户操作 → 系统处理 → 输出结果
38
+ ```
39
+
40
+ ## 技术设计
41
+
42
+ ### 技术选型
43
+
44
+ | 用途 | 选择 |
45
+ |-----|-----|
46
+ | 语言 | xxx |
47
+ | 框架 | xxx |
48
+
49
+ ### 项目结构
50
+
51
+ ```
52
+ project/
53
+ ├── src/
54
+ └── docs/
55
+ ```
56
+
57
+ ## 验收标准
58
+
59
+ - [ ] 场景1:预期结果
60
+ - [ ] 场景2:预期结果
61
+
62
+ ## 开放问题
63
+
64
+ - [ ] 待确认问题1
65
+ - [ ] 待确认问题2
@@ -1,43 +1,46 @@
1
- # Growork 配置文件
1
+ # Growork v2.0 配置文件(从 v1.0 迁移)
2
2
 
3
- # 飞书应用凭证
4
3
  feishu:
5
4
  appId: "cli_a9f679f7d8389ed1"
6
5
  appSecret: "M75JzaxTI8kAn50Ss1GgEbBVAIP551RX"
7
- domain: "lark" # 使用国际版 Lark
6
+ domain: "lark"
8
7
 
9
- # Notion 凭证
10
8
  notion:
11
9
  token: "ntn_B47151172375xyBGfoq9heNvlzfhvgSBQymC4zO7f997XO"
12
10
 
13
- # 文档同步配置
14
- docs:
15
- # 五牌阵需求文档 (飞书)
16
- - name: prd-5spread
17
- type: feishu
18
- url: "https://romangic.sg.larksuite.com/wiki/UrBKwmmJNizGlzkoFU3l90wxgOd"
19
- output: "test/prd-5spread.md"
11
+ # 输出根目录
12
+ outputDir: "test_export"
20
13
 
21
- # 后端AI文档 (飞书)
22
- - name: backend-ai
23
- type: feishu
24
- url: "https://romangic.sg.larksuite.com/wiki/ZiCuw4GnXiBsPnkN159lNE0mgLe"
25
- output: "test/backend-ai.md"
14
+ # docs 列表(可手动整理到 versions 中)
15
+ custom:
16
+ - url: "https://romangic.sg.larksuite.com/wiki/UrBKwmmJNizGlzkoFU3l90wxgOd"
17
+ name: "prd-5spread"
18
+ - url: "https://romangic.sg.larksuite.com/wiki/ZiCuw4GnXiBsPnkN159lNE0mgLe"
19
+ name: "backend-ai"
20
+ - url: "https://romangic.sg.larksuite.com/wiki/EB9NwMUP2ipF0Pk0SZBlXOS5gcb"
21
+ name: "push"
22
+ - url: "https://www.notion.so/2f91190afab5806aa853c073a24edd28"
23
+ name: "push-prd-notion"
24
+ - url: "https://romangic.sg.larksuite.com/wiki/QtUuwKpXyiFmAckrIaOlqRDNgJe"
25
+ name: "backend-api"
26
+ - url: "https://www.notion.so/2f91190afab580a8acf1daf80ae02ac6"
27
+ name: "test-cases"
26
28
 
27
- # Push 需求文档 (飞书)
28
- - name: push
29
- type: feishu
30
- url: "https://romangic.sg.larksuite.com/wiki/EB9NwMUP2ipF0Pk0SZBlXOS5gcb"
31
- output: "test/push.md"
32
-
33
- # Push PRD (Notion)
34
- - name: push-prd-notion
35
- type: notion
36
- url: "https://www.notion.so/2f91190afab5806aa853c073a24edd28"
37
- output: "test/push-prd-notion.md"
38
-
39
- # 后端接口文档 (飞书)
40
- - name: backend-api
41
- type: feishu
42
- url: "https://romangic.sg.larksuite.com/wiki/QtUuwKpXyiFmAckrIaOlqRDNgJe"
43
- output: "test/backend-api.md"
29
+ # 版本化文档
30
+ versions:
31
+ v1.0:
32
+ 五牌阵:
33
+ prd:
34
+ - "https://romangic.sg.larksuite.com/wiki/UrBKwmmJNizGlzkoFU3l90wxgOd"
35
+ api:
36
+ - "https://romangic.sg.larksuite.com/wiki/ZiCuw4GnXiBsPnkN159lNE0mgLe"
37
+ Push通知:
38
+ prd:
39
+ - "https://romangic.sg.larksuite.com/wiki/EB9NwMUP2ipF0Pk0SZBlXOS5gcb"
40
+ test:
41
+ - "https://www.notion.so/2f91190afab580a8acf1daf80ae02ac6"
42
+ v1.9:
43
+ QuickCard:
44
+ design:
45
+ - "https://www.figma.com/design/xxx?node-id=1-20"
46
+ - "https://www.figma.com/design/xxx?node-id=1-30"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "growork",
3
- "version": "1.0.1",
3
+ "version": "1.1.2",
4
4
  "description": "将飞书文档同步到本地,为 AI Agent 提供完整上下文",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -4,11 +4,7 @@ import chalk from 'chalk';
4
4
  import { getConfigPath, configExists, getDefaultConfig } from '../utils/config.js';
5
5
 
6
6
  const DIRS_TO_CREATE = [
7
- 'docs/product',
8
- 'docs/design',
9
- 'docs/api',
10
- 'docs/tech',
11
- 'docs/test',
7
+ 'docs/custom',
12
8
  ];
13
9
 
14
10
  export async function initCommand(): Promise<void> {
@@ -1,7 +1,10 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
1
  import chalk from 'chalk';
4
- import { loadConfig, configExists } from '../utils/config.js';
2
+ import { configExists, loadConfigV2, parseDocInput } from '../utils/config.js';
3
+
4
+ function shortenUrl(url: string, maxLen: number = 40): string {
5
+ if (url.length <= maxLen) return url;
6
+ return url.slice(0, maxLen - 3) + '...';
7
+ }
5
8
 
6
9
  export async function listCommand(): Promise<void> {
7
10
  if (!configExists()) {
@@ -9,29 +12,59 @@ export async function listCommand(): Promise<void> {
9
12
  process.exit(1);
10
13
  }
11
14
 
12
- const config = loadConfig();
13
- const cwd = process.cwd();
15
+ const config = loadConfigV2();
14
16
 
15
17
  console.log(chalk.blue('📋 文档列表\n'));
16
18
 
17
- console.log(chalk.gray(' 名称'.padEnd(18) + '类型'.padEnd(10) + '输出路径'.padEnd(30) + '状态'));
18
- console.log(chalk.gray(' ' + '-'.repeat(70)));
19
+ let totalCount = 0;
19
20
 
20
- for (const doc of config.docs) {
21
- const outputPath = path.join(cwd, doc.output);
22
- const exists = fs.existsSync(outputPath);
21
+ // 显示 custom 文档
22
+ if (config.custom && config.custom.length > 0) {
23
+ console.log(chalk.cyan('📁 custom (全局文档)'));
24
+ for (const input of config.custom) {
25
+ const { url, name } = parseDocInput(input);
26
+ const displayName = name || shortenUrl(url);
27
+ console.log(chalk.gray(` - ${displayName}`));
28
+ totalCount++;
29
+ }
30
+ console.log('');
31
+ }
23
32
 
24
- const status = exists
25
- ? chalk.green('已同步')
26
- : chalk.yellow('未同步');
33
+ // 显示 versions 文档
34
+ if (config.versions) {
35
+ for (const [version, features] of Object.entries(config.versions)) {
36
+ console.log(chalk.cyan(`📁 ${version}`));
27
37
 
28
- const name = ` ${doc.name}`.padEnd(18);
29
- const type = doc.type.padEnd(10);
30
- const output = doc.output.padEnd(30);
38
+ for (const [feature, value] of Object.entries(features)) {
39
+ if (Array.isArray(value)) {
40
+ // 简单 feature
41
+ console.log(chalk.white(` └─ ${feature}`));
42
+ for (const input of value) {
43
+ const { url, name } = parseDocInput(input);
44
+ const displayName = name || shortenUrl(url, 30);
45
+ console.log(chalk.gray(` - ${displayName}`));
46
+ totalCount++;
47
+ }
48
+ } else {
49
+ // 分类型的 feature
50
+ console.log(chalk.white(` └─ ${feature}`));
51
+ for (const docType of ['prd', 'design', 'api', 'test'] as const) {
52
+ const docInputs = value[docType];
53
+ if (!docInputs || docInputs.length === 0) continue;
31
54
 
32
- console.log(`${name}${chalk.gray(type)}${output}${status}`);
55
+ console.log(chalk.yellow(` ${docType}:`));
56
+ for (const input of docInputs) {
57
+ const { url, name } = parseDocInput(input);
58
+ const displayName = name || shortenUrl(url, 25);
59
+ console.log(chalk.gray(` - ${displayName}`));
60
+ totalCount++;
61
+ }
62
+ }
63
+ }
64
+ }
65
+ console.log('');
66
+ }
33
67
  }
34
68
 
35
- console.log('');
36
- console.log(chalk.gray(`共 ${config.docs.length} 个文档配置`));
69
+ console.log(chalk.gray(`共 ${totalCount} 个文档配置`));
37
70
  }