jvibe 1.0.8 → 1.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 CHANGED
@@ -158,8 +158,10 @@ TODO 完成情况 → 功能状态
158
158
  | `jvibe init` | 初始化 JVibe 项目 |
159
159
  | `jvibe init --mode=minimal` | 最小化初始化(仅 Core 文档) |
160
160
  | `jvibe init --force` | 强制覆盖已存在的配置 |
161
- | `jvibe upgrade` | 升级到最新版本 |
161
+ | `jvibe upgrade` | 升级到最新版本(默认卸载重装) |
162
162
  | `jvibe upgrade --check` | 仅检查更新 |
163
+ | `jvibe upgrade --migrate` | 仅执行旧版迁移 |
164
+ | `jvibe uninstall` | 卸载项目内 JVibe 配置 |
163
165
  | `jvibe status` | 查看项目配置状态 |
164
166
  | `jvibe validate` | 验证项目配置 |
165
167
 
package/bin/jvibe.js CHANGED
@@ -34,7 +34,7 @@ program
34
34
  // upgrade 命令
35
35
  program
36
36
  .command('upgrade')
37
- .description('升级到最新版本(支持旧版本自动迁移)')
37
+ .description('升级到最新版本(默认卸载重装)')
38
38
  .option('--check', '仅检查更新,不执行升级', false)
39
39
  .option('--force', '强制升级,跳过确认', false)
40
40
  .option('--migrate', '仅执行迁移,不更新到最新版本', false)
@@ -53,6 +53,20 @@ program
53
53
  await upgrade({ ...options, migrate: true });
54
54
  });
55
55
 
56
+ // uninstall 命令
57
+ program
58
+ .command('uninstall')
59
+ .description('卸载项目内的 JVibe 配置与核心文档')
60
+ .option('--purge-project-docs', '同时移除 docs/project', false)
61
+ .option('--no-backup', '不创建备份', false)
62
+ .action(async (options) => {
63
+ const uninstall = require('../scripts/uninstall');
64
+ await uninstall({
65
+ purgeProjectDocs: options.purgeProjectDocs,
66
+ backup: options.backup
67
+ });
68
+ });
69
+
56
70
  // status 命令
57
71
  program
58
72
  .command('status')
package/lib/migrate.js CHANGED
@@ -494,10 +494,9 @@ async function checkContentMigration(projectDir, currentVersion) {
494
494
  description: '核心文档需要强制重构(以 template/docs/core 为准)'
495
495
  });
496
496
  }
497
- return result;
498
497
  }
499
498
 
500
- // 模板不可用时,退回版本配置
499
+ // 模板检测后继续合并版本迁移配置
501
500
  if (migrationsConfig && currentVersion) {
502
501
  const configResult = migrationsConfig.checkAIMigrationRequired(currentVersion);
503
502
  if (configResult.required) {
@@ -226,7 +226,13 @@ function generateMigrationPrompt(fromVersion) {
226
226
  if (result.changes.renamed.length > 0) {
227
227
  prompt += `## 重命名的字段\n\n`;
228
228
  for (const change of result.changes.renamed) {
229
- prompt += `- ${change.file}: ${change.oldName} ${change.newName}\n`;
229
+ const renameLabel = change.oldName && change.newName
230
+ ? `${change.oldName} → ${change.newName}`
231
+ : (change.field || change.description || '重命名');
232
+ prompt += `- ${change.file}: ${renameLabel}\n`;
233
+ if (change.description && renameLabel !== change.description) {
234
+ prompt += ` - ${change.description}\n`;
235
+ }
230
236
  }
231
237
  prompt += `\n`;
232
238
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jvibe",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "description": "\u6587\u6863\u9a71\u52a8\u7684 AI \u8f85\u52a9\u5f00\u53d1\u7cfb\u7edf - Doc-driven AI-assisted development system for Claude Code",
5
5
  "main": "bin/jvibe.js",
6
6
  "bin": {
@@ -0,0 +1,83 @@
1
+ /**
2
+ * JVibe Uninstall Script
3
+ * 卸载项目内的 JVibe 配置与核心文档
4
+ */
5
+
6
+ const fs = require('fs-extra');
7
+ const path = require('path');
8
+ const chalk = require('chalk');
9
+
10
+ /**
11
+ * 卸载 JVibe 配置
12
+ * @param {Object} options - 卸载选项
13
+ * @param {boolean} options.purgeProjectDocs - 是否移除 docs/project
14
+ * @param {boolean} options.backup - 是否创建备份
15
+ * @param {boolean} options.showNextSteps - 是否输出下一步提示
16
+ */
17
+ async function uninstall(options = {}) {
18
+ const purgeProjectDocs = options.purgeProjectDocs || false;
19
+ const backupEnabled = options.backup !== false;
20
+ const showNextSteps = options.showNextSteps !== false;
21
+ const cwd = process.cwd();
22
+
23
+ console.log(chalk.blue('\n🧹 正在卸载 JVibe...\n'));
24
+
25
+ const targets = [
26
+ { relPath: '.claude', label: '.claude/' },
27
+ { relPath: 'docs/core', label: 'docs/core/' },
28
+ { relPath: 'docs/.jvibe', label: 'docs/.jvibe/' },
29
+ { relPath: '.jvibe-state.json', label: '.jvibe-state.json' },
30
+ { relPath: 'docs/.jvibe-state.json', label: 'docs/.jvibe-state.json' },
31
+ { relPath: 'docs/project', label: 'docs/project/', optional: true }
32
+ ];
33
+
34
+ const existingTargets = [];
35
+ for (const target of targets) {
36
+ if (target.optional && !purgeProjectDocs) {
37
+ continue;
38
+ }
39
+ const fullPath = path.join(cwd, target.relPath);
40
+ if (await fs.pathExists(fullPath)) {
41
+ existingTargets.push({ ...target, fullPath });
42
+ }
43
+ }
44
+
45
+ if (existingTargets.length === 0) {
46
+ console.log(chalk.yellow('⚠️ 未发现可卸载的 JVibe 配置'));
47
+ return;
48
+ }
49
+
50
+ let backupDir = null;
51
+ if (backupEnabled) {
52
+ backupDir = path.join(cwd, `.jvibe-uninstall-backup-${Date.now()}`);
53
+ await fs.ensureDir(backupDir);
54
+
55
+ for (const target of existingTargets) {
56
+ const destPath = path.join(backupDir, target.relPath);
57
+ await fs.ensureDir(path.dirname(destPath));
58
+ await fs.copy(target.fullPath, destPath);
59
+ }
60
+ }
61
+
62
+ for (const target of existingTargets) {
63
+ await fs.remove(target.fullPath);
64
+ }
65
+
66
+ console.log(chalk.green('\n✅ JVibe 卸载完成!'));
67
+ console.log(chalk.white('\n已移除:'));
68
+ for (const target of existingTargets) {
69
+ console.log(chalk.gray(` - ${target.label}`));
70
+ }
71
+
72
+ if (backupDir) {
73
+ console.log(chalk.gray(`\n备份位置:${path.basename(backupDir)}/`));
74
+ }
75
+
76
+ if (showNextSteps) {
77
+ console.log(chalk.yellow('\n📝 下一步:'));
78
+ console.log(chalk.white(' 1. 重新运行 jvibe init 或 /JVibe:init 初始化'));
79
+ console.log(chalk.white(' 2. 如需恢复,可从备份目录手动还原\n'));
80
+ }
81
+ }
82
+
83
+ module.exports = uninstall;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * JVibe Upgrade Script
3
3
  * 升级项目的 JVibe 配置到最新版本
4
- * 支持旧版本自动检测和迁移
4
+ * 默认执行卸载重装,可选保留旧迁移策略
5
5
  */
6
6
 
7
7
  const fs = require('fs-extra');
@@ -13,6 +13,8 @@ const {
13
13
  executeMigration,
14
14
  printMigrationSummary
15
15
  } = require('../lib/migrate');
16
+ const init = require('./init');
17
+ const uninstall = require('./uninstall');
16
18
 
17
19
  const TEMPLATE_DIR = path.join(__dirname, '../template');
18
20
 
@@ -63,23 +65,47 @@ async function upgrade(options = {}) {
63
65
  if (checkOnly) {
64
66
  if (migrationPlan.needsMigration) {
65
67
  console.log(chalk.yellow('\n📦 检测到旧版本,需要迁移'));
66
- console.log(chalk.white(' 运行 jvibe upgrade 进行升级和迁移\n'));
68
+ console.log(chalk.white(' 运行 jvibe upgrade 执行卸载重装\n'));
67
69
  } else if (currentVersion === latestVersion) {
68
70
  console.log(chalk.green('\n✅ 已是最新版本!\n'));
69
71
  } else {
70
72
  console.log(chalk.yellow(`\n📦 有新版本可用: ${latestVersion}`));
71
- console.log(chalk.white(' 运行 jvibe upgrade 进行升级\n'));
73
+ console.log(chalk.white(' 运行 jvibe upgrade 执行卸载重装\n'));
72
74
  }
73
75
  return;
74
76
  }
75
77
 
76
- // 4. 检查是否需要任何操作
78
+ if (!migrateOnly) {
79
+ if (!force) {
80
+ console.log(chalk.yellow('\n⚠️ 将执行卸载重装(重置 .claude/ 与 docs/core/)'));
81
+ console.log(chalk.white(' 使用 --force 选项跳过此确认'));
82
+ }
83
+
84
+ let mode = 'full';
85
+ if (await fs.pathExists(settingsPath)) {
86
+ try {
87
+ const settings = await fs.readJson(settingsPath);
88
+ mode = settings.jvibe?.mode || mode;
89
+ } catch (e) {
90
+ // 读取失败则使用默认模式
91
+ }
92
+ }
93
+
94
+ await uninstall({ purgeProjectDocs: false, backup: true, showNextSteps: false });
95
+ await init({ mode, force: false });
96
+
97
+ console.log(chalk.green(`\n✅ 升级完成!`));
98
+ console.log(chalk.green(` 版本: ${currentVersion} → ${latestVersion}`));
99
+ console.log(chalk.gray('\n 已执行卸载重装(保留 docs/project/)\n'));
100
+ return;
101
+ }
102
+
103
+ // migrate-only 模式保留旧迁移逻辑
77
104
  if (!migrationPlan.needsMigration && currentVersion === latestVersion) {
78
- console.log(chalk.green('\n✅ 已是最新版本,无需升级!\n'));
105
+ console.log(chalk.green('\n✅ 已是最新版本,无需迁移!\n'));
79
106
  return;
80
107
  }
81
108
 
82
- // 5. 确认升级(如果没有 --force)
83
109
  if (!force && (migrationPlan.needsMigration || currentVersion !== latestVersion)) {
84
110
  console.log(chalk.yellow('\n⚠️ 即将执行以下操作:'));
85
111
 
@@ -92,12 +118,8 @@ async function upgrade(options = {}) {
92
118
 
93
119
  console.log(chalk.white('\n 使用 --force 选项跳过此确认'));
94
120
  console.log(chalk.white(' 或重新运行命令继续...\n'));
95
-
96
- // 在非交互模式下直接继续
97
- // 实际项目中可能需要 readline 来获取用户确认
98
121
  }
99
122
 
100
- // 6. 创建备份
101
123
  console.log(chalk.gray('\n 创建备份...'));
102
124
  const backupDir = path.join(cwd, '.jvibe-backup-' + Date.now());
103
125
  if (hasClaudeDir) {
@@ -108,78 +130,11 @@ async function upgrade(options = {}) {
108
130
  }
109
131
  console.log(chalk.gray(` 备份已保存到: ${path.basename(backupDir)}/`));
110
132
 
111
- // 7. 执行迁移(如果需要)
112
133
  if (migrationPlan.needsMigration) {
113
134
  await executeMigration(cwd, TEMPLATE_DIR, migrationPlan, latestVersion);
114
135
  }
115
136
 
116
- // 8. 执行常规升级(如果不是仅迁移模式)
117
- if (!migrateOnly && currentVersion !== latestVersion) {
118
- console.log(chalk.yellow(`\n📦 正在升级到 ${latestVersion}...\n`));
119
-
120
- // 更新 agents(如果迁移时没有更新)
121
- if (migrationPlan.details.agentsToUpdate.length === 0) {
122
- console.log(chalk.gray(' 更新 agents...'));
123
- await fs.copy(
124
- path.join(TEMPLATE_DIR, '.claude/agents'),
125
- path.join(cwd, '.claude/agents'),
126
- { overwrite: true }
127
- );
128
- }
129
-
130
- // 更新 commands(如果迁移时没有更新)
131
- if (migrationPlan.details.commandsToRename.length === 0) {
132
- console.log(chalk.gray(' 更新 commands...'));
133
- await fs.copy(
134
- path.join(TEMPLATE_DIR, '.claude/commands'),
135
- path.join(cwd, '.claude/commands'),
136
- { overwrite: true }
137
- );
138
- }
139
-
140
- // 更新 hooks(如果迁移时没有更新)
141
- if (migrationPlan.details.hooksToUpdate.length === 0) {
142
- console.log(chalk.gray(' 更新 hooks...'));
143
- await fs.copy(
144
- path.join(TEMPLATE_DIR, '.claude/hooks'),
145
- path.join(cwd, '.claude/hooks'),
146
- { overwrite: true }
147
- );
148
- }
149
-
150
- // 补充任务交接文件(如不存在)
151
- const handoffSrc = path.join(TEMPLATE_DIR, 'docs/.jvibe/tasks.yaml');
152
- const handoffDir = path.join(cwd, 'docs/.jvibe');
153
- const handoffDest = path.join(handoffDir, 'tasks.yaml');
154
- if (await fs.pathExists(handoffSrc) && !await fs.pathExists(handoffDest)) {
155
- await fs.ensureDir(handoffDir);
156
- await fs.copy(handoffSrc, handoffDest, { overwrite: false });
157
- }
158
-
159
- // 更新版本信息
160
- let settings = {};
161
- if (await fs.pathExists(settingsPath)) {
162
- try {
163
- settings = await fs.readJson(settingsPath);
164
- } catch (e) {
165
- // 读取失败则创建新配置
166
- }
167
- }
168
-
169
- settings.jvibe = {
170
- ...settings.jvibe,
171
- version: latestVersion,
172
- upgradedAt: new Date().toISOString()
173
- };
174
- await fs.writeJson(settingsPath, settings, { spaces: 2 });
175
- }
176
-
177
- // 9. 清理旧备份(保留最新的)
178
- // 可选:保留备份供用户手动清理
179
-
180
- // 10. 输出成功信息
181
- console.log(chalk.green(`\n✅ 升级完成!`));
182
-
137
+ console.log(chalk.green(`\n✅ 迁移完成!`));
183
138
  if (migrationPlan.needsMigration) {
184
139
  console.log(chalk.green(' 已完成旧版本迁移'));
185
140
  }
@@ -190,7 +145,6 @@ async function upgrade(options = {}) {
190
145
  console.log(chalk.gray(`\n 备份位置: ${path.basename(backupDir)}/`));
191
146
  console.log(chalk.gray(' 如需回滚,请手动恢复备份文件'));
192
147
 
193
- // 11. 检查是否需要 AI 内容迁移
194
148
  if (migrationPlan.needsAIMigration) {
195
149
  console.log(chalk.yellow('\n⚠️ 检测到文档内容需要智能迁移'));
196
150
  console.log(chalk.yellow(' 以下内容需要 AI 介入处理:'));
@@ -207,7 +161,7 @@ async function upgrade(options = {}) {
207
161
  console.error(chalk.red('\n❌ 升级失败:'), error.message);
208
162
 
209
163
  // 提示备份位置
210
- const backups = await findBackups(cwd);
164
+ const backups = await findBackups(cwd, ['.jvibe-backup-', '.jvibe-uninstall-backup-']);
211
165
  if (backups.length > 0) {
212
166
  console.log(chalk.yellow(` 最新备份: ${backups[0]}`));
213
167
  }
@@ -221,11 +175,11 @@ async function upgrade(options = {}) {
221
175
  * @param {string} dir - 项目目录
222
176
  * @returns {Promise<string[]>}
223
177
  */
224
- async function findBackups(dir) {
178
+ async function findBackups(dir, prefixes = ['.jvibe-backup-']) {
225
179
  try {
226
180
  const files = await fs.readdir(dir);
227
181
  return files
228
- .filter(f => f.startsWith('.jvibe-backup-'))
182
+ .filter(f => prefixes.some(prefix => f.startsWith(prefix)))
229
183
  .sort()
230
184
  .reverse();
231
185
  } catch (e) {
@@ -29,6 +29,7 @@ description: 初始化 JVibe 项目文档结构
29
29
  - 模块清单(根据 AI 分析生成)
30
30
  - 模块依赖关系
31
31
  - 模块功能统计表(初始为空)
32
+ - 环境配置(项目根路径、代码根路径)
32
33
 
33
34
  ### Feature-List.md
34
35
 
@@ -207,13 +208,19 @@ questions:
207
208
  3. **AI 分析规划**:生成模块架构、依赖关系、技术栈推荐
208
209
  4. **用户确认/调整**:让用户确认或修改架构
209
210
  5. 创建 `docs/` 目录
210
- 6. 从模板复制并填充项目信息
211
- 7. 创建 4 个核心文档:
211
+ 6. 自动识别项目路径并写入 Project.md:
212
+ ```yaml
213
+ detect_paths:
214
+ project_root: $PWD (包含 .git 或 .claude)
215
+ code_root: src/ | app/ | lib/ | .
216
+ ```
217
+ 7. 从模板复制并填充项目信息
218
+ 8. 创建 4 个核心文档:
212
219
  - **Project.md**:填充模块清单、架构图、技术栈
213
220
  - **Feature-List.md**:空模板(后续逐个添加功能)
214
221
  - **Standards.md**:基础模板
215
222
  - **Appendix.md**:基础模板
216
- 8. **创建状态标记文件** `.jvibe-state.json`:
223
+ 9. **创建状态标记文件** `.jvibe-state.json`:
217
224
  ```json
218
225
  {
219
226
  "initialized": true,
@@ -222,7 +229,7 @@ questions:
222
229
  "createdAt": "2026-01-11T10:00:00Z"
223
230
  }
224
231
  ```
225
- 9. 输出确认信息
232
+ 10. 输出确认信息
226
233
 
227
234
  ## 输出要求
228
235
 
@@ -79,6 +79,9 @@ project:
79
79
  architecture: []
80
80
  tech_stack: []
81
81
  modules: []
82
+ paths:
83
+ project_root: $PWD (包含 .git 或 .claude)
84
+ code_root: src/ | app/ | lib/ | .
82
85
  features: []
83
86
  appendix:
84
87
  entries: []
@@ -121,7 +124,7 @@ appendix:
121
124
  #### 3.4 重构 Project
122
125
 
123
126
  1. **按模板章节顺序重建**
124
- 2. **填充项目事实**(架构、模块、接口、数据模型、代码落点)
127
+ 2. **填充项目事实**(架构、模块、接口、数据模型、代码落点、项目路径)
125
128
  3. **更新模块功能统计**(从功能清单重新计算)
126
129
 
127
130
  #### 3.5 重构 Standards / Appendix
@@ -6,7 +6,7 @@
6
6
  "hooks": [
7
7
  {
8
8
  "type": "command",
9
- "command": ".claude/hooks/load-context.sh",
9
+ "command": "sh -c 'hook=\"load-context.sh\"; dir=\"$PWD\"; while true; do path=\"$dir/.claude/hooks/$hook\"; if [ -f \"$path\" ]; then /bin/bash \"$path\"; exit 0; fi; if [ \"$dir\" = \"/\" ]; then break; fi; dir=\"$(dirname \"$dir\")\"; done; exit 0'",
10
10
  "timeout": 5000,
11
11
  "description": "加载项目上下文,显示功能状态统计"
12
12
  }
@@ -19,7 +19,7 @@
19
19
  "hooks": [
20
20
  {
21
21
  "type": "command",
22
- "command": ".claude/hooks/sync-feature-status.sh",
22
+ "command": "sh -c 'hook=\"sync-feature-status.sh\"; dir=\"$PWD\"; while true; do path=\"$dir/.claude/hooks/$hook\"; if [ -f \"$path\" ]; then /bin/bash \"$path\"; exit 0; fi; if [ \"$dir\" = \"/\" ]; then break; fi; dir=\"$(dirname \"$dir\")\"; done; exit 0'",
23
23
  "timeout": 10000,
24
24
  "description": "当功能清单被修改时,自动推导功能状态"
25
25
  }
@@ -32,13 +32,13 @@
32
32
  "hooks": [
33
33
  {
34
34
  "type": "command",
35
- "command": ".claude/hooks/guard-output.sh",
35
+ "command": "sh -c 'hook=\"guard-output.sh\"; dir=\"$PWD\"; while true; do path=\"$dir/.claude/hooks/$hook\"; if [ -f \"$path\" ]; then /bin/bash \"$path\"; exit 0; fi; if [ \"$dir\" = \"/\" ]; then break; fi; dir=\"$(dirname \"$dir\")\"; done; exit 0'",
36
36
  "timeout": 3000,
37
37
  "description": "Warn on long outputs without structured blocks"
38
38
  },
39
39
  {
40
40
  "type": "command",
41
- "command": ".claude/hooks/sync-stats.sh",
41
+ "command": "sh -c 'hook=\"sync-stats.sh\"; dir=\"$PWD\"; while true; do path=\"$dir/.claude/hooks/$hook\"; if [ -f \"$path\" ]; then /bin/bash \"$path\"; exit 0; fi; if [ \"$dir\" = \"/\" ]; then break; fi; dir=\"$(dirname \"$dir\")\"; done; exit 0'",
42
42
  "timeout": 5000,
43
43
  "description": "Agent 完成后输出项目统计信息"
44
44
  }
@@ -228,3 +228,12 @@ src/modules/chat/
228
228
  | chat_rooms | ChatModule | id, name, type, created_at | name |
229
229
  | messages | ChatModule | id, room_id, sender_id, content, sent_at | room_id, sender_id, sent_at |
230
230
  | room_members | ChatModule | id, room_id, user_id, joined_at | room_id, user_id |
231
+
232
+ ---
233
+
234
+ ## 7. 环境配置
235
+
236
+ | 项目项 | 值 | 说明 |
237
+ |------|----|------|
238
+ | 项目根路径 | `[自动填充]` | 运行 `pwd` 或识别 `.git/.claude` 所在目录 |
239
+ | 代码根路径 | `[自动填充]` | 常见为 `src/`,若无则为项目根 |
@@ -50,6 +50,21 @@ docs/
50
50
  | P-001 | API文档 | `docs/project/api.md` | REST API端点 | active |
51
51
  | P-002 | 数据库Schema | `docs/project/database.md` | 表结构和ER图 | active |
52
52
 
53
+ **自动创建规则**:
54
+ ```yaml
55
+ project_docs:
56
+ create_if:
57
+ has_api_layer: docs/project/api.md
58
+ has_database: docs/project/database.md
59
+ has_frontend: docs/project/frontend.md
60
+ has_backend: docs/project/backend.md
61
+ ```
62
+
63
+ **使用方式**:
64
+ - 新增文档必须先登记到注册表(§2.2)
65
+ - 文档开头写明用途、更新时机、信息来源
66
+ - 内容更新以模块变更为触发,不做长期遗漏
67
+
53
68
  ### 2.3 任务交接文件(Task Handoff)
54
69
 
55
70
  **位置**:`docs/.jvibe/tasks.yaml`
@@ -146,6 +161,23 @@ planner 分析需求(必要时反问澄清)
146
161
  ...
147
162
  ```
148
163
 
164
+ ### 3.5 环境隔离与依赖安装
165
+
166
+ ```yaml
167
+ env_isolation:
168
+ required: true
169
+ forbid: [base, global]
170
+ allowed:
171
+ - .venv
172
+ - .conda
173
+ install:
174
+ python: "<env>/bin/python -m pip install ..."
175
+ node: "npm ci (project root)"
176
+ ```
177
+
178
+ - 任何测试或脚本必须使用项目隔离环境,不允许默认 base 环境
179
+ - 环境选择与激活方式写入项目文档(如无相关章节则新增“环境配置”)
180
+
149
181
  ---
150
182
 
151
183
  ## 4. 文档编写规范
@@ -197,6 +229,14 @@ planner 分析需求(必要时反问澄清)
197
229
  1. 在注册表(§2.2)添加记录
198
230
  2. 在文档开头说明用途和更新时机
199
231
 
232
+ 必须包含项目路径信息:
233
+ ```yaml
234
+ project_paths:
235
+ project_root: required
236
+ code_root: required
237
+ source: auto_detected
238
+ ```
239
+
200
240
  ---
201
241
 
202
242
  ## 5. 文档同步规则