@localsummer/incspec 0.0.9 → 0.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 +17 -0
- package/commands/archive.mjs +2 -1
- package/commands/help.mjs +5 -2
- package/commands/reset.mjs +144 -21
- package/index.mjs +2 -1
- package/lib/spec.mjs +5 -5
- package/lib/workflow.mjs +75 -0
- package/package.json +1 -1
- package/templates/AGENTS.md +1 -1
- package/templates/inc-spec-skill/SKILL.md +15 -3
package/README.md
CHANGED
|
@@ -444,6 +444,18 @@ incspec/
|
|
|
444
444
|
3. **保留新基线** - 归档时新基线会被复制(而非移动)到归档目录,同时保留在 `baselines/` 作为下一轮起点。
|
|
445
445
|
4. **定期清理** - 对于过期的归档,可手动删除或移至外部存储。
|
|
446
446
|
|
|
447
|
+
## 回退与重置
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
incspec reset # 全量重置,归档所有产出
|
|
451
|
+
incspec reset --to=3 # 回退到步骤 3,保留 1-3,重置 4-7
|
|
452
|
+
incspec reset -t 3 # 短选项形式
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
**回退行为:** 保留目标步骤及之前状态,重置后续步骤为 pending,被重置步骤的产出自动归档。
|
|
456
|
+
|
|
457
|
+
**限制:** 目标步骤必须已完成(1-6),快速模式下不能回退到被跳过的步骤(3、4)。
|
|
458
|
+
|
|
447
459
|
## 目录结构
|
|
448
460
|
|
|
449
461
|
```
|
|
@@ -543,6 +555,10 @@ incspec archive --workflow # 同上,显式指定
|
|
|
543
555
|
incspec archive <file> # 归档指定文件
|
|
544
556
|
incspec archive <file> --keep # 复制而非移动
|
|
545
557
|
incspec archive -y # 跳过确认提示
|
|
558
|
+
|
|
559
|
+
incspec reset # 重置当前工作流(别名:rs)
|
|
560
|
+
incspec reset --to=3 # 回退到步骤 3,保留步骤 1-3 的状态
|
|
561
|
+
incspec reset -t 3 # 同上,短选项形式
|
|
546
562
|
```
|
|
547
563
|
|
|
548
564
|
</details>
|
|
@@ -564,6 +580,7 @@ incspec archive -y # 跳过确认提示
|
|
|
564
580
|
| `validate` | `v` | 验证 |
|
|
565
581
|
| `sync` | `s` | 同步 |
|
|
566
582
|
| `update` | `up` | 更新 |
|
|
583
|
+
| `reset` | `rs` | 重置/回退 |
|
|
567
584
|
| `help` | `h` | 帮助 |
|
|
568
585
|
|
|
569
586
|
</details>
|
package/commands/archive.mjs
CHANGED
|
@@ -122,7 +122,8 @@ function parseWorkflowTime(value) {
|
|
|
122
122
|
function isOutputArchived(archivedFiles, outputFile, completedAt) {
|
|
123
123
|
const ext = path.extname(outputFile);
|
|
124
124
|
const base = path.basename(outputFile, ext);
|
|
125
|
-
|
|
125
|
+
// Match: base.ext, base-2.ext, base-3.ext, ...
|
|
126
|
+
const pattern = new RegExp(`^${escapeRegExp(base)}(-\\d+)?${escapeRegExp(ext)}$`);
|
|
126
127
|
return archivedFiles.some(file => pattern.test(file.name) && file.mtime >= completedAt);
|
|
127
128
|
}
|
|
128
129
|
|
package/commands/help.mjs
CHANGED
|
@@ -119,9 +119,12 @@ const COMMANDS = {
|
|
|
119
119
|
],
|
|
120
120
|
},
|
|
121
121
|
reset: {
|
|
122
|
-
usage: 'incspec reset',
|
|
122
|
+
usage: 'incspec reset [--to=<step>]',
|
|
123
123
|
aliases: ['rs'],
|
|
124
|
-
description: '
|
|
124
|
+
description: '重置当前工作流,归档已有产出(支持部分回退)',
|
|
125
|
+
options: [
|
|
126
|
+
['-t, --to=<step>', '回退到指定步骤(1-6),保留该步骤及之前的状态'],
|
|
127
|
+
],
|
|
125
128
|
},
|
|
126
129
|
sync: {
|
|
127
130
|
usage: 'incspec sync [--cursor] [--claude] [--all] [--project|--global]',
|
package/commands/reset.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* reset command - Reset current workflow
|
|
2
|
+
* reset command - Reset current workflow (full or partial)
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import * as fs from 'fs';
|
|
@@ -12,7 +12,9 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
readWorkflow,
|
|
14
14
|
archiveWorkflow,
|
|
15
|
+
resetToStep,
|
|
15
16
|
STATUS,
|
|
17
|
+
STEPS,
|
|
16
18
|
} from '../lib/workflow.mjs';
|
|
17
19
|
import { archiveSpec } from '../lib/spec.mjs';
|
|
18
20
|
import {
|
|
@@ -22,6 +24,7 @@ import {
|
|
|
22
24
|
printSuccess,
|
|
23
25
|
printInfo,
|
|
24
26
|
printWarning,
|
|
27
|
+
printError,
|
|
25
28
|
} from '../lib/terminal.mjs';
|
|
26
29
|
|
|
27
30
|
/**
|
|
@@ -77,7 +80,7 @@ function getOutputDirForStepIndex(stepIndex) {
|
|
|
77
80
|
* Collect output files from workflow steps
|
|
78
81
|
* @param {string} projectRoot
|
|
79
82
|
* @param {Object} workflow
|
|
80
|
-
* @returns {Array<{path: string, name: string}>}
|
|
83
|
+
* @returns {Array<{path: string, name: string, stepIndex: number}>}
|
|
81
84
|
*/
|
|
82
85
|
function collectWorkflowOutputs(projectRoot, workflow) {
|
|
83
86
|
const outputs = [];
|
|
@@ -102,6 +105,7 @@ function collectWorkflowOutputs(projectRoot, workflow) {
|
|
|
102
105
|
outputs.push({
|
|
103
106
|
path: outputPath,
|
|
104
107
|
name: step.output,
|
|
108
|
+
stepIndex: index,
|
|
105
109
|
});
|
|
106
110
|
}
|
|
107
111
|
});
|
|
@@ -110,24 +114,104 @@ function collectWorkflowOutputs(projectRoot, workflow) {
|
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
/**
|
|
113
|
-
*
|
|
114
|
-
* @param {
|
|
117
|
+
* Collect output files for specific step indices
|
|
118
|
+
* @param {string} projectRoot
|
|
119
|
+
* @param {Array<{index: number, output: string}>} resetOutputs
|
|
120
|
+
* @returns {Array<{path: string, name: string, stepIndex: number}>}
|
|
115
121
|
*/
|
|
116
|
-
|
|
117
|
-
const
|
|
122
|
+
function collectResetOutputs(projectRoot, resetOutputs) {
|
|
123
|
+
const outputs = [];
|
|
118
124
|
|
|
119
|
-
|
|
120
|
-
|
|
125
|
+
for (const item of resetOutputs) {
|
|
126
|
+
const dir = getOutputDirForStepIndex(item.index);
|
|
127
|
+
if (!dir) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
121
130
|
|
|
122
|
-
|
|
131
|
+
const outputPath = path.join(projectRoot, INCSPEC_DIR, dir, item.output);
|
|
132
|
+
if (fs.existsSync(outputPath)) {
|
|
133
|
+
outputs.push({
|
|
134
|
+
path: outputPath,
|
|
135
|
+
name: item.output,
|
|
136
|
+
stepIndex: item.index,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return outputs;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Archive output files
|
|
146
|
+
* @param {string} projectRoot
|
|
147
|
+
* @param {Array<{path: string, name: string}>} outputs
|
|
148
|
+
* @param {string} moduleName
|
|
149
|
+
* @returns {number} Number of archived files
|
|
150
|
+
*/
|
|
151
|
+
function archiveOutputFiles(projectRoot, outputs, moduleName) {
|
|
152
|
+
let archivedCount = 0;
|
|
153
|
+
|
|
154
|
+
for (const output of outputs) {
|
|
155
|
+
try {
|
|
156
|
+
archiveSpec(projectRoot, output.path, true, moduleName);
|
|
157
|
+
print(colorize(` - ${output.name}`, colors.dim));
|
|
158
|
+
archivedCount++;
|
|
159
|
+
} catch (e) {
|
|
160
|
+
printWarning(`归档失败: ${output.name} - ${e.message}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return archivedCount;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Execute partial reset (reset to specific step)
|
|
169
|
+
* @param {string} projectRoot
|
|
170
|
+
* @param {number} targetStep
|
|
171
|
+
*/
|
|
172
|
+
function executePartialReset(projectRoot, targetStep) {
|
|
123
173
|
const workflow = readWorkflow(projectRoot);
|
|
174
|
+
const workflowName = workflow.currentWorkflow;
|
|
175
|
+
const moduleName = extractModuleName(workflowName);
|
|
176
|
+
|
|
177
|
+
print('');
|
|
178
|
+
print(colorize(' incspec 工作流部分回退', colors.bold, colors.cyan));
|
|
179
|
+
print(colorize(' ───────────────────────', colors.dim));
|
|
180
|
+
print('');
|
|
181
|
+
print(colorize(`工作流: ${workflowName}`, colors.dim));
|
|
182
|
+
print(colorize(`回退目标: 步骤 ${targetStep}`, colors.dim));
|
|
183
|
+
print('');
|
|
184
|
+
|
|
185
|
+
// Perform partial reset
|
|
186
|
+
const { workflow: updatedWorkflow, resetOutputs } = resetToStep(projectRoot, targetStep);
|
|
187
|
+
|
|
188
|
+
// Collect and archive output files from reset steps
|
|
189
|
+
const outputs = collectResetOutputs(projectRoot, resetOutputs);
|
|
190
|
+
|
|
191
|
+
if (outputs.length > 0) {
|
|
192
|
+
print(colorize('归档被重置步骤的产出文件:', colors.dim));
|
|
193
|
+
|
|
194
|
+
const archivedCount = archiveOutputFiles(projectRoot, outputs, moduleName);
|
|
124
195
|
|
|
125
|
-
if (!workflow?.currentWorkflow) {
|
|
126
196
|
print('');
|
|
127
|
-
|
|
128
|
-
|
|
197
|
+
printSuccess(`已归档 ${archivedCount} 个产出文件到 archives/${new Date().toISOString().slice(0, 7)}/${moduleName}/`);
|
|
198
|
+
} else {
|
|
199
|
+
printInfo('被重置步骤无产出文件需要归档');
|
|
129
200
|
}
|
|
130
201
|
|
|
202
|
+
// Show reset result
|
|
203
|
+
print('');
|
|
204
|
+
const targetStepInfo = STEPS[targetStep - 1];
|
|
205
|
+
printSuccess(`已回退到步骤 ${targetStep} (${targetStepInfo.name})`);
|
|
206
|
+
print(colorize(`当前步骤已设为: ${updatedWorkflow.currentStep}`, colors.dim));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Execute full reset
|
|
211
|
+
* @param {string} projectRoot
|
|
212
|
+
*/
|
|
213
|
+
function executeFullReset(projectRoot) {
|
|
214
|
+
const workflow = readWorkflow(projectRoot);
|
|
131
215
|
const workflowName = workflow.currentWorkflow;
|
|
132
216
|
const moduleName = extractModuleName(workflowName);
|
|
133
217
|
|
|
@@ -145,17 +229,10 @@ export async function resetCommand(ctx) {
|
|
|
145
229
|
if (outputs.length > 0) {
|
|
146
230
|
print(colorize('归档产出文件:', colors.dim));
|
|
147
231
|
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
const archivePath = archiveSpec(projectRoot, output.path, true, moduleName);
|
|
151
|
-
print(colorize(` - ${output.name}`, colors.dim));
|
|
152
|
-
} catch (e) {
|
|
153
|
-
printWarning(`归档失败: ${output.name} - ${e.message}`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
232
|
+
const archivedCount = archiveOutputFiles(projectRoot, outputs, moduleName);
|
|
156
233
|
|
|
157
234
|
print('');
|
|
158
|
-
printSuccess(`已归档 ${
|
|
235
|
+
printSuccess(`已归档 ${archivedCount} 个产出文件到 archives/${new Date().toISOString().slice(0, 7)}/${moduleName}/`);
|
|
159
236
|
} else {
|
|
160
237
|
printInfo('无产出文件需要归档');
|
|
161
238
|
}
|
|
@@ -164,3 +241,49 @@ export async function resetCommand(ctx) {
|
|
|
164
241
|
archiveWorkflow(projectRoot);
|
|
165
242
|
printSuccess('工作流已重置');
|
|
166
243
|
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Execute reset command
|
|
247
|
+
* @param {Object} ctx - Command context
|
|
248
|
+
*/
|
|
249
|
+
export async function resetCommand(ctx) {
|
|
250
|
+
const { cwd, options } = ctx;
|
|
251
|
+
|
|
252
|
+
// Ensure initialized
|
|
253
|
+
const projectRoot = ensureInitialized(cwd);
|
|
254
|
+
|
|
255
|
+
// Read current workflow
|
|
256
|
+
const workflow = readWorkflow(projectRoot);
|
|
257
|
+
|
|
258
|
+
if (!workflow?.currentWorkflow) {
|
|
259
|
+
print('');
|
|
260
|
+
printInfo('当前无活跃工作流,无需重置');
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Check for --to option
|
|
265
|
+
const targetStepStr = options.to || options.t;
|
|
266
|
+
|
|
267
|
+
if (targetStepStr) {
|
|
268
|
+
// Partial reset mode
|
|
269
|
+
const targetStep = parseInt(targetStepStr, 10);
|
|
270
|
+
|
|
271
|
+
if (isNaN(targetStep)) {
|
|
272
|
+
print('');
|
|
273
|
+
printError(`无效的步骤编号: ${targetStepStr}`);
|
|
274
|
+
print(colorize('用法: incspec reset --to=<步骤编号>', colors.dim));
|
|
275
|
+
print(colorize('示例: incspec reset --to=3', colors.dim));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
executePartialReset(projectRoot, targetStep);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
print('');
|
|
283
|
+
printError(error.message);
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
// Full reset mode (original behavior)
|
|
287
|
+
executeFullReset(projectRoot);
|
|
288
|
+
}
|
|
289
|
+
}
|
package/index.mjs
CHANGED
|
@@ -34,13 +34,14 @@ function parseArgs(args) {
|
|
|
34
34
|
options: {},
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
const valueOptions = new Set(['module', 'feature', 'source-dir', 'output', 'workflow']);
|
|
37
|
+
const valueOptions = new Set(['module', 'feature', 'source-dir', 'output', 'workflow', 'to']);
|
|
38
38
|
const shortValueMap = new Map([
|
|
39
39
|
['m', 'module'],
|
|
40
40
|
['f', 'feature'],
|
|
41
41
|
['s', 'source-dir'],
|
|
42
42
|
['o', 'output'],
|
|
43
43
|
['w', 'workflow'],
|
|
44
|
+
['t', 'to'],
|
|
44
45
|
]);
|
|
45
46
|
let i = 0;
|
|
46
47
|
|
package/lib/spec.mjs
CHANGED
|
@@ -21,13 +21,13 @@ function ensureUniqueArchivePath(filePath) {
|
|
|
21
21
|
const dir = path.dirname(filePath);
|
|
22
22
|
const ext = path.extname(filePath);
|
|
23
23
|
const base = path.basename(filePath, ext);
|
|
24
|
-
let counter =
|
|
25
|
-
let candidate
|
|
24
|
+
let counter = 2;
|
|
25
|
+
let candidate;
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
candidate = path.join(dir, `${base}
|
|
27
|
+
do {
|
|
28
|
+
candidate = path.join(dir, `${base}-${counter}${ext}`);
|
|
29
29
|
counter += 1;
|
|
30
|
-
}
|
|
30
|
+
} while (fs.existsSync(candidate));
|
|
31
31
|
|
|
32
32
|
return candidate;
|
|
33
33
|
}
|
package/lib/workflow.mjs
CHANGED
|
@@ -507,6 +507,81 @@ export function archiveWorkflow(projectRoot) {
|
|
|
507
507
|
return workflow;
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
+
/**
|
|
511
|
+
* Reset workflow to a specific step (partial reset)
|
|
512
|
+
* Keeps steps 1 to targetStep, resets steps after targetStep to pending
|
|
513
|
+
* @param {string} projectRoot
|
|
514
|
+
* @param {number} targetStep - 1-based step number to reset to (1-6)
|
|
515
|
+
* @returns {{workflow: Object, resetOutputs: Array<{index: number, stepNumber: number, output: string}>}}
|
|
516
|
+
*/
|
|
517
|
+
export function resetToStep(projectRoot, targetStep) {
|
|
518
|
+
const workflow = readWorkflow(projectRoot);
|
|
519
|
+
if (!workflow || !workflow.currentWorkflow) {
|
|
520
|
+
throw new Error('工作流未初始化或无活跃工作流');
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Validate targetStep: must be 1-6 (step 7 is archive, cannot be a reset target)
|
|
524
|
+
if (targetStep < 1 || targetStep > 6) {
|
|
525
|
+
throw new Error(`无效的目标步骤: ${targetStep},有效范围为 1-6`);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Check if target step is completed
|
|
529
|
+
const targetIndex = targetStep - 1;
|
|
530
|
+
const targetStepData = workflow.steps[targetIndex];
|
|
531
|
+
if (!targetStepData || targetStepData.status !== STATUS.COMPLETED) {
|
|
532
|
+
throw new Error(`目标步骤 ${targetStep} 尚未完成,无法回退到此步骤`);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Handle quick mode: cannot reset to skipped steps
|
|
536
|
+
const mode = workflow.mode || MODE.FULL;
|
|
537
|
+
if (mode === MODE.QUICK && QUICK_MODE_SKIPPED.includes(targetStep)) {
|
|
538
|
+
throw new Error(`快速模式下步骤 ${targetStep} 被跳过,无法回退到此步骤`);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Collect outputs from steps that will be reset (targetStep+1 to 7)
|
|
542
|
+
const resetOutputs = [];
|
|
543
|
+
for (let i = targetStep; i < STEPS.length; i++) {
|
|
544
|
+
const stepData = workflow.steps[i];
|
|
545
|
+
if (stepData && stepData.output && stepData.output !== '-') {
|
|
546
|
+
resetOutputs.push({
|
|
547
|
+
index: i,
|
|
548
|
+
stepNumber: i + 1,
|
|
549
|
+
output: stepData.output,
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Reset steps after targetStep
|
|
555
|
+
const now = formatLocalDateTime(new Date());
|
|
556
|
+
for (let i = targetStep; i < STEPS.length; i++) {
|
|
557
|
+
const stepNumber = i + 1;
|
|
558
|
+
// In quick mode, keep skipped steps as skipped
|
|
559
|
+
if (mode === MODE.QUICK && QUICK_MODE_SKIPPED.includes(stepNumber)) {
|
|
560
|
+
workflow.steps[i] = {
|
|
561
|
+
status: STATUS.SKIPPED,
|
|
562
|
+
output: null,
|
|
563
|
+
completedAt: workflow.steps[i]?.completedAt || now,
|
|
564
|
+
};
|
|
565
|
+
} else {
|
|
566
|
+
workflow.steps[i] = {
|
|
567
|
+
status: STATUS.PENDING,
|
|
568
|
+
output: null,
|
|
569
|
+
completedAt: null,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Set currentStep to next step after targetStep
|
|
575
|
+
workflow.currentStep = getNextStep(targetStep, mode) || targetStep + 1;
|
|
576
|
+
|
|
577
|
+
writeWorkflow(projectRoot, workflow);
|
|
578
|
+
|
|
579
|
+
return {
|
|
580
|
+
workflow,
|
|
581
|
+
resetOutputs,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
510
585
|
/**
|
|
511
586
|
* Get step info by number
|
|
512
587
|
* @param {number} stepNumber
|
package/package.json
CHANGED
package/templates/AGENTS.md
CHANGED
|
@@ -141,7 +141,7 @@ incspec archive [--yes] [--keep] # 步骤7
|
|
|
141
141
|
# 验证与同步
|
|
142
142
|
incspec validate / v [--strict] # 验证完整性
|
|
143
143
|
incspec sync [--cursor|--claude|--all] [--global|--project] # IDE集成
|
|
144
|
-
incspec reset [--
|
|
144
|
+
incspec reset [--to=<step>] # 重置工作流(可选回退到指定步骤 1-6)
|
|
145
145
|
```
|
|
146
146
|
|
|
147
147
|
## 文件格式示例
|
|
@@ -101,6 +101,7 @@ description: 设计优先的前端功能增量编码工作流。适用于实现
|
|
|
101
101
|
- `incspec status` - 查看工作流状态
|
|
102
102
|
- `incspec list` - 列出规范文件
|
|
103
103
|
- `incspec validate` - 验证项目健康状态
|
|
104
|
+
- `incspec reset [--to=<step>]` - 重置工作流(可选回退到指定步骤 1-6)
|
|
104
105
|
|
|
105
106
|
## 步骤详情
|
|
106
107
|
|
|
@@ -223,9 +224,20 @@ incspec archive <file> --keep # 复制而非移动
|
|
|
223
224
|
- 步骤5后: 验证所有文件已正确创建/修改
|
|
224
225
|
- 步骤7后: 确认工作区已清理
|
|
225
226
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
**人工确认点:**
|
|
228
|
+
|
|
229
|
+
| 步骤 | 确认级别 | 快速模式 | 说明 |
|
|
230
|
+
|------|----------|----------|------|
|
|
231
|
+
| 1. 代码流程分析 | 自动 | 适用 | 技术性分析,错误会在后续暴露 |
|
|
232
|
+
| 2. 需求收集 | **必须确认** | 适用 | 需求理解错误会导致全部返工 |
|
|
233
|
+
| 3. 依赖收集 | 自动 | 跳过 | 遗漏会在编译阶段暴露 |
|
|
234
|
+
| 4. 增量设计 | **必须确认** | 跳过 | 架构方案需审批后才能编码 |
|
|
235
|
+
| 5. 代码应用 | 自动 | 适用 | 按蓝图执行,完成后报告结果 |
|
|
236
|
+
| 6. 基线合并 | 自动 | 适用 | 技术性操作,无决策 |
|
|
237
|
+
| 7. 归档 | 建议确认 | 适用 | 涉及文件移动,用户应知晓范围 |
|
|
238
|
+
|
|
239
|
+
- **完整模式**: 步骤2 + 步骤4必须确认
|
|
240
|
+
- **快速模式**: 仅步骤2必须确认(步骤3、4被跳过)
|
|
229
241
|
|
|
230
242
|
## 常见陷阱
|
|
231
243
|
|