jvibe 1.0.7 → 1.0.9
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/lib/migrate.js +272 -74
- package/lib/migrations/index.js +22 -0
- package/package.json +1 -1
- package/template/.claude/commands/JVibe:migrate.md +110 -60
- package/template/.claude/settings.json +4 -4
package/lib/migrate.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
const fs = require('fs-extra');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const chalk = require('chalk');
|
|
9
|
+
const pkg = require('../package.json');
|
|
9
10
|
|
|
10
11
|
// 尝试加载迁移配置(可能不存在)
|
|
11
12
|
let migrationsConfig = null;
|
|
@@ -24,6 +25,231 @@ const CORE_DOC_RENAMES = [
|
|
|
24
25
|
{ from: '附加材料.md', to: 'Appendix.md' }
|
|
25
26
|
];
|
|
26
27
|
|
|
28
|
+
async function listMarkdownFiles(dir) {
|
|
29
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
30
|
+
const files = [];
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
const fullPath = path.join(dir, entry.name);
|
|
33
|
+
if (entry.isDirectory()) {
|
|
34
|
+
files.push(...await listMarkdownFiles(fullPath));
|
|
35
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
36
|
+
files.push(fullPath);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return files;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function escapeRegExp(value) {
|
|
43
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeHeadingText(text) {
|
|
47
|
+
return text
|
|
48
|
+
.replace(/^[\d.\-、()]+\s*/g, '')
|
|
49
|
+
.replace(/\s*[((][^))]*[))]\s*$/g, '')
|
|
50
|
+
.replace(/^[\u{1F300}-\u{1FAFF}\u{2600}-\u{26FF}]+\s*/gu, '')
|
|
51
|
+
.replace(/`/g, '')
|
|
52
|
+
.replace(/\s+/g, ' ')
|
|
53
|
+
.trim()
|
|
54
|
+
.toLowerCase();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function extractHeadings(content, levels) {
|
|
58
|
+
const headings = new Set();
|
|
59
|
+
const lines = content.split(/\r?\n/);
|
|
60
|
+
for (const line of lines) {
|
|
61
|
+
const match = line.match(/^(#{2,6})\s+(.+)$/);
|
|
62
|
+
if (!match) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const level = match[1].length;
|
|
66
|
+
if (!levels.includes(level)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const normalized = normalizeHeadingText(match[2]);
|
|
70
|
+
if (normalized) {
|
|
71
|
+
headings.add(normalized);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return headings;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function extractFeatureFieldsFromTemplate(content) {
|
|
78
|
+
const lines = content.split(/\r?\n/);
|
|
79
|
+
let startIndex = -1;
|
|
80
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
81
|
+
if (/^##\s+F-\d+/m.test(lines[i])) {
|
|
82
|
+
startIndex = i;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (startIndex === -1) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
let endIndex = lines.length;
|
|
90
|
+
for (let i = startIndex + 1; i < lines.length; i += 1) {
|
|
91
|
+
if (/^##\s+F-\d+/m.test(lines[i])) {
|
|
92
|
+
endIndex = i;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const fields = new Set();
|
|
97
|
+
for (const line of lines.slice(startIndex, endIndex)) {
|
|
98
|
+
const match = line.match(/^\*\*(.+?)\*\*/);
|
|
99
|
+
if (match) {
|
|
100
|
+
const fieldName = match[1].trim();
|
|
101
|
+
if (fieldName) {
|
|
102
|
+
fields.add(fieldName);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return [...fields];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function extractFeatureBlocks(content) {
|
|
110
|
+
const lines = content.split(/\r?\n/);
|
|
111
|
+
const blocks = [];
|
|
112
|
+
let startIndex = -1;
|
|
113
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
114
|
+
if (/^##\s+F-\d+/m.test(lines[i])) {
|
|
115
|
+
if (startIndex >= 0) {
|
|
116
|
+
blocks.push(lines.slice(startIndex, i).join('\n'));
|
|
117
|
+
}
|
|
118
|
+
startIndex = i;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (startIndex >= 0) {
|
|
122
|
+
blocks.push(lines.slice(startIndex).join('\n'));
|
|
123
|
+
}
|
|
124
|
+
return blocks;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function findFirstExistingFile(projectDir, candidates) {
|
|
128
|
+
for (const candidate of candidates) {
|
|
129
|
+
const fullPath = path.join(projectDir, candidate);
|
|
130
|
+
if (await fs.pathExists(fullPath)) {
|
|
131
|
+
return fullPath;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function compareDocsToTemplate(projectDir) {
|
|
138
|
+
const result = {
|
|
139
|
+
applied: false,
|
|
140
|
+
required: false,
|
|
141
|
+
files: [],
|
|
142
|
+
missingFields: [],
|
|
143
|
+
changes: []
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const templateCoreDir = path.resolve(__dirname, '..', 'template', 'docs', 'core');
|
|
147
|
+
if (!await fs.pathExists(templateCoreDir)) {
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
result.applied = true;
|
|
151
|
+
|
|
152
|
+
const docCandidates = {
|
|
153
|
+
'Standards.md': [
|
|
154
|
+
'docs/core/Standards.md',
|
|
155
|
+
'docs/Standards.md',
|
|
156
|
+
'docs/core/规范文档.md',
|
|
157
|
+
'docs/规范文档.md'
|
|
158
|
+
],
|
|
159
|
+
'Project.md': [
|
|
160
|
+
'docs/core/Project.md',
|
|
161
|
+
'docs/Project.md',
|
|
162
|
+
'docs/core/项目文档.md',
|
|
163
|
+
'docs/项目文档.md'
|
|
164
|
+
],
|
|
165
|
+
'Feature-List.md': [
|
|
166
|
+
'docs/core/Feature-List.md',
|
|
167
|
+
'docs/Feature-List.md',
|
|
168
|
+
'docs/core/功能清单.md',
|
|
169
|
+
'docs/功能清单.md'
|
|
170
|
+
],
|
|
171
|
+
'Appendix.md': [
|
|
172
|
+
'docs/core/Appendix.md',
|
|
173
|
+
'docs/Appendix.md',
|
|
174
|
+
'docs/core/附加材料.md',
|
|
175
|
+
'docs/附加材料.md'
|
|
176
|
+
]
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
for (const [templateName, candidates] of Object.entries(docCandidates)) {
|
|
180
|
+
const templatePath = path.join(templateCoreDir, templateName);
|
|
181
|
+
if (!await fs.pathExists(templatePath)) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const projectPath = await findFirstExistingFile(projectDir, candidates);
|
|
186
|
+
if (!projectPath) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const templateContent = await fs.readFile(templatePath, 'utf-8');
|
|
191
|
+
const projectContent = await fs.readFile(projectPath, 'utf-8');
|
|
192
|
+
|
|
193
|
+
if (templateName === 'Feature-List.md') {
|
|
194
|
+
const requiredFields = extractFeatureFieldsFromTemplate(templateContent);
|
|
195
|
+
if (requiredFields.length === 0) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const featureBlocks = extractFeatureBlocks(projectContent);
|
|
199
|
+
if (featureBlocks.length === 0) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const missingFields = new Set();
|
|
203
|
+
for (const block of featureBlocks) {
|
|
204
|
+
for (const field of requiredFields) {
|
|
205
|
+
const pattern = new RegExp(`\\*\\*${escapeRegExp(field)}\\*\\*\\s*(:|:)?`, 'm');
|
|
206
|
+
if (!pattern.test(block)) {
|
|
207
|
+
missingFields.add(field);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (missingFields.size > 0) {
|
|
212
|
+
result.required = true;
|
|
213
|
+
result.files.push(projectPath);
|
|
214
|
+
const missingList = [...missingFields];
|
|
215
|
+
result.missingFields.push(...missingList);
|
|
216
|
+
result.changes.push({
|
|
217
|
+
file: path.relative(projectDir, projectPath),
|
|
218
|
+
type: 'missing_fields',
|
|
219
|
+
fields: missingList,
|
|
220
|
+
description: `功能清单与模板字段不一致,缺少:${missingList.join('、')}`
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const templateHeadings = extractHeadings(templateContent, [2]);
|
|
227
|
+
const projectHeadings = extractHeadings(projectContent, [2]);
|
|
228
|
+
const missingSections = [];
|
|
229
|
+
for (const heading of templateHeadings) {
|
|
230
|
+
if (!projectHeadings.has(heading)) {
|
|
231
|
+
missingSections.push(heading);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (missingSections.length > 0) {
|
|
235
|
+
result.required = true;
|
|
236
|
+
result.files.push(projectPath);
|
|
237
|
+
result.changes.push({
|
|
238
|
+
file: path.relative(projectDir, projectPath),
|
|
239
|
+
type: 'missing_sections',
|
|
240
|
+
sections: missingSections,
|
|
241
|
+
description: `文档缺少模板中的章节:${missingSections.join('、')}`
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (result.files.length > 0) {
|
|
247
|
+
result.files = [...new Set(result.files)];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
|
|
27
253
|
/**
|
|
28
254
|
* 版本检测结果
|
|
29
255
|
* @typedef {Object} VersionInfo
|
|
@@ -222,96 +448,56 @@ async function checkContentMigration(projectDir, currentVersion) {
|
|
|
222
448
|
changes: []
|
|
223
449
|
};
|
|
224
450
|
|
|
225
|
-
//
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const content = await fs.readFile(featurePath, 'utf-8');
|
|
239
|
-
|
|
240
|
-
// 检查是否有功能条目
|
|
241
|
-
const hasFeatures = /^## F-\d+/m.test(content);
|
|
242
|
-
if (!hasFeatures) {
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// 检查新版本字段是否存在
|
|
247
|
-
const newFields = [
|
|
248
|
-
{ field: '优先级', pattern: /\*\*优先级\*\*:/m },
|
|
249
|
-
{ field: '预估工时', pattern: /\*\*预估工时\*\*:/m },
|
|
250
|
-
{ field: '关联模块', pattern: /\*\*关联模块\*\*:/m }
|
|
251
|
-
];
|
|
252
|
-
|
|
253
|
-
const missingFields = [];
|
|
254
|
-
for (const { field, pattern } of newFields) {
|
|
255
|
-
if (!pattern.test(content)) {
|
|
256
|
-
missingFields.push(field);
|
|
451
|
+
// 检查是否存在旧的核心文档名称引用(需要 AI 更新链接/引用)
|
|
452
|
+
const docsDir = path.join(projectDir, 'docs');
|
|
453
|
+
if (await fs.pathExists(docsDir)) {
|
|
454
|
+
const mdFiles = await listMarkdownFiles(docsDir);
|
|
455
|
+
const filesWithLegacyRefs = [];
|
|
456
|
+
|
|
457
|
+
for (const filePath of mdFiles) {
|
|
458
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
459
|
+
const hasLegacyRef = LEGACY_CORE_DOCS.some(name => content.includes(name));
|
|
460
|
+
if (hasLegacyRef) {
|
|
461
|
+
filesWithLegacyRefs.push(filePath);
|
|
257
462
|
}
|
|
258
463
|
}
|
|
259
464
|
|
|
260
|
-
if (
|
|
465
|
+
if (filesWithLegacyRefs.length > 0) {
|
|
261
466
|
result.required = true;
|
|
262
|
-
|
|
263
|
-
|
|
467
|
+
for (const filePath of filesWithLegacyRefs) {
|
|
468
|
+
if (!result.files.includes(filePath)) {
|
|
469
|
+
result.files.push(filePath);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
264
472
|
result.changes.push({
|
|
265
|
-
file:
|
|
266
|
-
type: '
|
|
267
|
-
|
|
268
|
-
description: `功能清单缺少新版本字段:${missingFields.join('、')}`
|
|
473
|
+
file: 'docs/**',
|
|
474
|
+
type: 'legacy_doc_refs',
|
|
475
|
+
description: '文档内引用仍使用旧中文名称,需更新为英文命名(规范文档/项目文档/功能清单/附加材料)'
|
|
269
476
|
});
|
|
270
477
|
}
|
|
271
478
|
}
|
|
272
479
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
for (const docPath of projectDocPaths) {
|
|
282
|
-
if (!await fs.pathExists(docPath)) {
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const content = await fs.readFile(docPath, 'utf-8');
|
|
287
|
-
|
|
288
|
-
// 检查新版本章节是否存在
|
|
289
|
-
const newSections = [
|
|
290
|
-
{ section: '环境配置', pattern: /^## \d+\. 环境配置/m }
|
|
291
|
-
];
|
|
292
|
-
|
|
293
|
-
const missingSections = [];
|
|
294
|
-
for (const { section, pattern } of newSections) {
|
|
295
|
-
if (!pattern.test(content)) {
|
|
296
|
-
missingSections.push(section);
|
|
297
|
-
}
|
|
480
|
+
const templateComparison = await compareDocsToTemplate(projectDir);
|
|
481
|
+
if (templateComparison.applied) {
|
|
482
|
+
if (templateComparison.required) {
|
|
483
|
+
result.required = true;
|
|
484
|
+
result.files = [...new Set([...result.files, ...templateComparison.files])];
|
|
485
|
+
result.missingFields.push(...templateComparison.missingFields);
|
|
486
|
+
result.changes.push(...templateComparison.changes);
|
|
298
487
|
}
|
|
299
|
-
|
|
300
|
-
if (
|
|
488
|
+
const latestVersion = pkg.version || null;
|
|
489
|
+
if (latestVersion && currentVersion && currentVersion !== latestVersion) {
|
|
301
490
|
result.required = true;
|
|
302
|
-
if (!result.files.includes(docPath)) {
|
|
303
|
-
result.files.push(docPath);
|
|
304
|
-
}
|
|
305
491
|
result.changes.push({
|
|
306
|
-
file:
|
|
307
|
-
type: '
|
|
308
|
-
|
|
309
|
-
description: `项目文档缺少新版本章节:${missingSections.join('、')}`
|
|
492
|
+
file: 'docs/core/*.md',
|
|
493
|
+
type: 'rebuild',
|
|
494
|
+
description: '核心文档需要强制重构(以 template/docs/core 为准)'
|
|
310
495
|
});
|
|
311
496
|
}
|
|
497
|
+
return result;
|
|
312
498
|
}
|
|
313
499
|
|
|
314
|
-
//
|
|
500
|
+
// 模板不可用时,退回版本配置
|
|
315
501
|
if (migrationsConfig && currentVersion) {
|
|
316
502
|
const configResult = migrationsConfig.checkAIMigrationRequired(currentVersion);
|
|
317
503
|
if (configResult.required) {
|
|
@@ -323,6 +509,18 @@ async function checkContentMigration(projectDir, currentVersion) {
|
|
|
323
509
|
field: c.field,
|
|
324
510
|
description: c.description
|
|
325
511
|
})));
|
|
512
|
+
result.changes.push(...configResult.changes.modified.map(c => ({
|
|
513
|
+
file: c.file,
|
|
514
|
+
type: 'modified_field',
|
|
515
|
+
field: c.field || c.section,
|
|
516
|
+
description: c.description
|
|
517
|
+
})));
|
|
518
|
+
result.changes.push(...configResult.changes.renamed.map(c => ({
|
|
519
|
+
file: c.file,
|
|
520
|
+
type: 'renamed',
|
|
521
|
+
field: c.field,
|
|
522
|
+
description: c.description
|
|
523
|
+
})));
|
|
326
524
|
}
|
|
327
525
|
}
|
|
328
526
|
|
package/lib/migrations/index.js
CHANGED
|
@@ -47,6 +47,28 @@ const MIGRATIONS = [
|
|
|
47
47
|
},
|
|
48
48
|
aiMigrationRequired: []
|
|
49
49
|
},
|
|
50
|
+
{
|
|
51
|
+
version: '1.0.7',
|
|
52
|
+
description: '核心文档英文命名与引用更新',
|
|
53
|
+
changes: {
|
|
54
|
+
added: [],
|
|
55
|
+
modified: [],
|
|
56
|
+
removed: [],
|
|
57
|
+
renamed: [
|
|
58
|
+
{
|
|
59
|
+
file: 'docs/core/Standards.md',
|
|
60
|
+
field: 'core-docs-rename',
|
|
61
|
+
description: '核心文档更名为英文(Standards/Project/Feature-List/Appendix)并更新文档内引用'
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
aiMigrationRequired: [
|
|
66
|
+
'docs/core/Standards.md',
|
|
67
|
+
'docs/core/Project.md',
|
|
68
|
+
'docs/core/Feature-List.md',
|
|
69
|
+
'docs/core/Appendix.md'
|
|
70
|
+
]
|
|
71
|
+
},
|
|
50
72
|
{
|
|
51
73
|
version: '1.1.0',
|
|
52
74
|
description: '文档格式增强',
|
package/package.json
CHANGED
|
@@ -1,60 +1,94 @@
|
|
|
1
1
|
# /JVibe:migrate - 智能文档迁移
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> 以模板为唯一权威,对核心文档执行强制重构
|
|
4
4
|
|
|
5
5
|
## 触发场景
|
|
6
6
|
|
|
7
7
|
- 用户运行 `jvibe upgrade` 后提示需要 AI 迁移
|
|
8
8
|
- 用户主动运行 `/JVibe:migrate`
|
|
9
|
-
-
|
|
9
|
+
- 检测到版本升级或模板规则变更
|
|
10
10
|
|
|
11
11
|
## 工作流程
|
|
12
12
|
|
|
13
|
-
### 阶段 1
|
|
13
|
+
### 阶段 1:版本检测(仅参考)
|
|
14
14
|
|
|
15
|
-
1. 读取 `.claude/settings.json`
|
|
16
|
-
2. 读取 `
|
|
17
|
-
3.
|
|
15
|
+
1. 读取 `.claude/settings.json` 获取当前版本(仅参考)
|
|
16
|
+
2. 读取 `template/docs/core/*.md` 作为迁移模板
|
|
17
|
+
3. 读取以下文件判断当前文档结构:
|
|
18
18
|
- `docs/core/Feature-List.md` 或 `docs/Feature-List.md`
|
|
19
19
|
- `docs/core/Project.md` 或 `docs/Project.md`
|
|
20
20
|
|
|
21
|
-
### 阶段 2
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
####
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
21
|
+
### 阶段 2:强制重构规划
|
|
22
|
+
|
|
23
|
+
以 `template/docs/core/*.md` 为唯一权威,执行全量重构(覆盖 core 文档),不做局部补丁。
|
|
24
|
+
|
|
25
|
+
#### 重构策略(代码化)
|
|
26
|
+
```yaml
|
|
27
|
+
mode: rebuild
|
|
28
|
+
template_root: template/docs/core
|
|
29
|
+
targets:
|
|
30
|
+
- docs/core/Standards.md
|
|
31
|
+
- docs/core/Project.md
|
|
32
|
+
- docs/core/Feature-List.md
|
|
33
|
+
- docs/core/Appendix.md
|
|
34
|
+
overwrite: true
|
|
35
|
+
preserve:
|
|
36
|
+
project_facts:
|
|
37
|
+
- architecture
|
|
38
|
+
- tech_stack
|
|
39
|
+
- modules
|
|
40
|
+
- endpoints
|
|
41
|
+
- data_models
|
|
42
|
+
- dependencies
|
|
43
|
+
- code_locations
|
|
44
|
+
features:
|
|
45
|
+
- id
|
|
46
|
+
- status
|
|
47
|
+
- name
|
|
48
|
+
- description
|
|
49
|
+
- todos
|
|
50
|
+
- existing_fields
|
|
51
|
+
appendix_entries:
|
|
52
|
+
- all_ids
|
|
53
|
+
conflict_policy:
|
|
54
|
+
rules: template_wins
|
|
55
|
+
data: project_wins
|
|
56
|
+
unmapped_content: append_to_last_section
|
|
57
|
+
placeholders: "[待填写]"
|
|
58
|
+
```
|
|
42
59
|
|
|
43
|
-
### 阶段 3
|
|
60
|
+
### 阶段 3:执行重构
|
|
44
61
|
|
|
45
62
|
#### 3.1 备份原文件
|
|
46
63
|
|
|
47
64
|
```bash
|
|
48
65
|
# 创建备份
|
|
66
|
+
cp docs/core/Standards.md docs/core/Standards.md.bak
|
|
49
67
|
cp docs/core/Feature-List.md docs/core/Feature-List.md.bak
|
|
50
68
|
cp docs/core/Project.md docs/core/Project.md.bak
|
|
69
|
+
cp docs/core/Appendix.md docs/core/Appendix.md.bak
|
|
51
70
|
```
|
|
52
71
|
|
|
53
|
-
#### 3.2
|
|
72
|
+
#### 3.2 抽取并归一化现有内容
|
|
73
|
+
|
|
74
|
+
输出结构化快照,供重构阶段复用:
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
project:
|
|
78
|
+
name: "[项目名称]"
|
|
79
|
+
architecture: []
|
|
80
|
+
tech_stack: []
|
|
81
|
+
modules: []
|
|
82
|
+
features: []
|
|
83
|
+
appendix:
|
|
84
|
+
entries: []
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### 3.3 重构 Feature-List
|
|
54
88
|
|
|
55
89
|
对每个功能条目:
|
|
56
90
|
|
|
57
|
-
1.
|
|
91
|
+
1. **读取原有内容**(保留状态、描述、TODO、已有字段):
|
|
58
92
|
```markdown
|
|
59
93
|
## F-001 ✅ 用户注册
|
|
60
94
|
|
|
@@ -66,19 +100,17 @@ cp docs/core/Project.md docs/core/Project.md.bak
|
|
|
66
100
|
...
|
|
67
101
|
```
|
|
68
102
|
|
|
69
|
-
2.
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
- **关联模块**:从项目文档的模块清单中匹配
|
|
103
|
+
2. **模板字段补齐**:
|
|
104
|
+
- 模板字段由示例功能条目自动提取
|
|
105
|
+
- 仅补齐模板要求的字段,不新增额外字段
|
|
73
106
|
|
|
74
|
-
3.
|
|
107
|
+
3. **生成新格式**(完全按模板布局):
|
|
75
108
|
```markdown
|
|
76
109
|
## F-001 ✅ 用户注册
|
|
77
110
|
|
|
78
111
|
**描述**:允许新用户通过邮箱和密码创建账户...
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
**关联模块**:AuthModule
|
|
112
|
+
**(模板字段 1)**:...
|
|
113
|
+
**(模板字段 2)**:...
|
|
82
114
|
|
|
83
115
|
**TODO**
|
|
84
116
|
- [x] 设计数据库users表结构
|
|
@@ -86,32 +118,49 @@ cp docs/core/Project.md docs/core/Project.md.bak
|
|
|
86
118
|
...
|
|
87
119
|
```
|
|
88
120
|
|
|
89
|
-
#### 3.
|
|
121
|
+
#### 3.4 重构 Project
|
|
90
122
|
|
|
91
|
-
1.
|
|
92
|
-
2.
|
|
123
|
+
1. **按模板章节顺序重建**
|
|
124
|
+
2. **填充项目事实**(架构、模块、接口、数据模型、代码落点)
|
|
93
125
|
3. **更新模块功能统计**(从功能清单重新计算)
|
|
94
126
|
|
|
127
|
+
#### 3.5 重构 Standards / Appendix
|
|
128
|
+
|
|
129
|
+
- 以模板文本为准覆盖规则/约束类内容
|
|
130
|
+
- 追加保留的项目自定义条目(无法映射则放在末尾)
|
|
131
|
+
|
|
132
|
+
#### 3.6 文档引用更新
|
|
133
|
+
|
|
134
|
+
- 将文档中的旧中文文件名引用统一替换为英文:
|
|
135
|
+
- `规范文档.md` → `Standards.md`
|
|
136
|
+
- `项目文档.md` → `Project.md`
|
|
137
|
+
- `功能清单.md` → `Feature-List.md`
|
|
138
|
+
- `附加材料.md` → `Appendix.md`
|
|
139
|
+
|
|
95
140
|
### 阶段 4:验证与确认
|
|
96
141
|
|
|
97
|
-
1.
|
|
142
|
+
1. 展示重构摘要:
|
|
98
143
|
```
|
|
99
|
-
📋
|
|
144
|
+
📋 重构摘要
|
|
100
145
|
|
|
101
146
|
Feature-List.md:
|
|
102
|
-
-
|
|
103
|
-
-
|
|
147
|
+
- 重构 15 个功能条目
|
|
148
|
+
- 模板字段已补齐
|
|
104
149
|
|
|
105
150
|
Project.md:
|
|
106
|
-
-
|
|
151
|
+
- 按模板重建章节
|
|
107
152
|
- 更新了模块功能统计
|
|
108
153
|
|
|
154
|
+
Standards.md / Appendix.md:
|
|
155
|
+
- 规则文本已对齐模板
|
|
156
|
+
- 项目自定义条目已保留
|
|
157
|
+
|
|
109
158
|
备份位置:docs/core/*.md.bak
|
|
110
159
|
```
|
|
111
160
|
|
|
112
161
|
2. 询问用户确认
|
|
113
162
|
|
|
114
|
-
##
|
|
163
|
+
## 字段推断规则(仅当模板要求该字段时)
|
|
115
164
|
|
|
116
165
|
| 特征 | 优先级 |
|
|
117
166
|
|------|--------|
|
|
@@ -120,7 +169,7 @@ cp docs/core/Project.md docs/core/Project.md.bak
|
|
|
120
169
|
| 辅助功能、增强体验 | P2 |
|
|
121
170
|
| 优化、重构、技术债务 | P3 |
|
|
122
171
|
|
|
123
|
-
##
|
|
172
|
+
## 工时估算规则(仅当模板要求该字段时)
|
|
124
173
|
|
|
125
174
|
| TODO 数量 | 复杂度特征 | 预估工时 |
|
|
126
175
|
|-----------|-----------|----------|
|
|
@@ -129,7 +178,7 @@ cp docs/core/Project.md docs/core/Project.md.bak
|
|
|
129
178
|
| 7-10 个 | 包含集成测试 | 8h |
|
|
130
179
|
| 10+ 个 | 复杂功能 | 16h+ |
|
|
131
180
|
|
|
132
|
-
##
|
|
181
|
+
## 模块匹配规则(仅当模板要求该字段时)
|
|
133
182
|
|
|
134
183
|
1. 从功能编号范围推断(如 F-001~F-005 属于 AuthModule)
|
|
135
184
|
2. 从功能描述关键词匹配(如「认证」「登录」→ AuthModule)
|
|
@@ -139,33 +188,34 @@ cp docs/core/Project.md docs/core/Project.md.bak
|
|
|
139
188
|
|
|
140
189
|
- 如果无法确定某个字段的值,使用占位符 `[待填写]`
|
|
141
190
|
- 如果文档格式无法识别,提示用户手动调整
|
|
191
|
+
- 无法映射的内容追加在对应文档末尾
|
|
142
192
|
- 保留所有备份文件,便于回滚
|
|
143
193
|
|
|
144
194
|
## 输出格式
|
|
145
195
|
|
|
146
|
-
|
|
196
|
+
重构完成后输出:
|
|
147
197
|
|
|
148
198
|
```
|
|
149
|
-
✅ JVibe
|
|
199
|
+
✅ JVibe 文档重构完成
|
|
150
200
|
|
|
151
|
-
📊
|
|
152
|
-
- 功能清单:15
|
|
153
|
-
-
|
|
201
|
+
📊 重构统计:
|
|
202
|
+
- 功能清单:15 个功能条目已重构
|
|
203
|
+
- 项目文档:章节已按模板重建
|
|
154
204
|
|
|
155
205
|
📝 新增字段:
|
|
156
|
-
-
|
|
157
|
-
- 预估工时:15/15 已估算
|
|
158
|
-
- 关联模块:15/15 已匹配
|
|
206
|
+
- 模板字段:15/15 已补齐
|
|
159
207
|
|
|
160
208
|
💾 备份文件:
|
|
161
209
|
- docs/core/Feature-List.md.bak
|
|
162
210
|
- docs/core/Project.md.bak
|
|
211
|
+
- docs/core/Standards.md.bak
|
|
212
|
+
- docs/core/Appendix.md.bak
|
|
163
213
|
|
|
164
214
|
⚠️ 请检查以下需要手动确认的项目:
|
|
165
|
-
- F-007 用户资料编辑 -
|
|
166
|
-
- F-015 在线状态显示 -
|
|
215
|
+
- F-007 用户资料编辑 - 字段待确认
|
|
216
|
+
- F-015 在线状态显示 - 字段可能需要调整
|
|
167
217
|
|
|
168
|
-
运行 jvibe validate
|
|
218
|
+
运行 jvibe validate 验证重构结果
|
|
169
219
|
```
|
|
170
220
|
|
|
171
221
|
## 权限
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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
|
}
|