@localsummer/incspec 0.2.6 → 0.3.1

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
@@ -142,9 +142,36 @@ AI 编程助手在处理复杂前端代码库时常常力不从心,因为 API
142
142
  └────────────────────┘
143
143
  ```
144
144
 
145
+ ### 极简模式 (3步)
146
+
147
+ 适用于紧急修复、单文件小改动、不需要新基线的变更。
148
+
149
+ ```
150
+ ┌────────────────────┐
151
+ │ 1. 分析 │ 分析现有代码流程
152
+ │ 代码流程 │ 生成基线快照
153
+ └────────┬───────────┘
154
+
155
+
156
+ ┌────────────────────┐
157
+ │ 5. 应用 │ 基于口头需求
158
+ │ 变更 │ 直接实现代码变更
159
+ └────────┬───────────┘
160
+
161
+
162
+ ┌────────────────────┐
163
+ │ 7. 归档 │ 归档本轮产出
164
+ │ 产出 │ 清空工作区
165
+ └────────────────────┘
166
+ ```
167
+
145
168
  **模式选择建议:**
146
169
  - **完整模式**: 复杂 UI 功能、多组件交互、需要详细设计审查
147
170
  - **快速模式**: Bug 修复、简单功能、不涉及 UI 依赖变更
171
+ - **极简模式**: 紧急修复、单文件小改动、无需生成新基线
172
+
173
+ **模式升级:**
174
+ 支持从宽松模式升级到严格模式(`minimal → quick → full`),使用 `incspec upgrade <mode>` 补齐缺失步骤。
148
175
 
149
176
  > **提示**: 如果已有基准报告,可使用 `incspec analyze --baseline=<file>` 直接跳过分析步骤,快速进入后续工作流。
150
177
 
@@ -218,7 +245,7 @@ incspec init
218
245
 
219
246
  同步到 Cursor 或 Claude Code:
220
247
  ```bash
221
- incspec sync # 交互式选择
248
+ incspec sync # 交互式选择(默认不预选)
222
249
  incspec sync --cursor # 仅 Cursor
223
250
  incspec sync --claude # 仅 Claude Code
224
251
  incspec sync --all # 全部
@@ -260,6 +287,14 @@ $ incspec analyze src/views/Home --quick # 启动快速模式工作流
260
287
  ```
261
288
  快速模式跳过步骤 3 (UI依赖采集) 和步骤 4 (增量设计),直接从需求进入代码应用。
262
289
 
290
+ **启动极简模式** (3步流程):
291
+
292
+ 适用于紧急修复或单文件小改动:
293
+ ```bash
294
+ $ incspec analyze src/views/Home --minimal # 启动极简模式工作流
295
+ ```
296
+ 极简模式跳过步骤 2、3、4、6,仅保留分析 → 应用 → 归档;如需补齐步骤可使用 `incspec upgrade <mode>`。
297
+
263
298
  **使用现有基准报告** (跳过分析):
264
299
 
265
300
  如果已有基准报告文件,可直接使用,跳过分析步骤:
@@ -462,7 +497,7 @@ incspec reset -t 3 # 短选项形式
462
497
 
463
498
  **回退行为:** 保留目标步骤及之前状态,重置后续步骤为 pending,被重置步骤的产出自动归档。
464
499
 
465
- **限制:** 目标步骤必须已完成(1-6),快速模式下不能回退到被跳过的步骤(3、4)。
500
+ **限制:** 目标步骤必须已完成(1-6),快速模式下不能回退到被跳过的步骤(3、4),极简模式下不能回退到被跳过的步骤(2、3、4、6)。
466
501
 
467
502
  ## 目录结构
468
503
 
@@ -520,9 +555,10 @@ incspec help <command> # 显示特定命令帮助
520
555
 
521
556
  ```bash
522
557
  # 步骤 1:分析代码流程
523
- incspec analyze <source-path> [--module=name] [--quick] # 别名:a
558
+ incspec analyze <source-path> [--module=name] [--quick|--minimal] # 别名:a
524
559
  incspec a src/views/Home --module=home
525
560
  incspec analyze src/views/Home --quick # 启动快速模式
561
+ incspec analyze src/views/Home --minimal # 启动极简模式
526
562
  incspec analyze src/views/Home --complete -o baselines/home-baseline-v1.md
527
563
  incspec analyze --baseline=home-baseline-v1.md # 使用现有基准(自动从归档恢复)
528
564
 
@@ -530,23 +566,24 @@ incspec analyze --baseline=home-baseline-v1.md # 使用现有基准(自动从
530
566
  incspec collect-req # 别名:cr
531
567
  incspec cr --complete
532
568
 
533
- # 步骤 3:收集 UI 依赖(快速模式跳过)
569
+ # 步骤 3:收集 UI 依赖(快速/极简模式跳过)
534
570
  incspec collect-dep # 别名:cd
535
571
  incspec cd --complete
536
572
 
537
- # 步骤 4:生成增量设计(快速模式跳过)
573
+ # 步骤 4:生成增量设计(快速/极简模式跳过)
538
574
  incspec design [--feature=name] # 别名:d
539
575
  incspec d --feature=user-auth --complete -o increments/auth-increment-v1.md
540
576
 
541
577
  # 步骤 5:应用代码变更
542
578
  incspec apply [increment-path] # 别名:ap
543
579
  incspec ap --source-dir=src/ --complete
544
- # 快速模式下自动使用 requirements/structured-requirements.md 作为输入
580
+ # 快速/极简模式下自动使用 requirements/structured-requirements.md 作为输入
545
581
 
546
582
  # 步骤 6:合并到基线
547
583
  incspec merge [increment-path] # 别名:m
548
584
  incspec m --complete -o baselines/home-baseline-v2.md
549
585
  # 快速模式下重新分析代码生成新基线
586
+ # 极简模式默认跳过步骤 6(需升级模式后执行)
550
587
  ```
551
588
 
552
589
  </details>
@@ -572,6 +609,10 @@ incspec archive -y # 跳过确认提示
572
609
  incspec reset # 重置当前工作流(别名:rs)
573
610
  incspec reset --to=3 # 回退到步骤 3,保留步骤 1-3 的状态
574
611
  incspec reset -t 3 # 同上,短选项形式
612
+
613
+ incspec upgrade <mode> # 升级工作流模式(别名:ug)
614
+ incspec upgrade quick # minimal → quick
615
+ incspec upgrade full # quick/minimal → full
575
616
  ```
576
617
 
577
618
  </details>
@@ -594,6 +635,7 @@ incspec reset -t 3 # 同上,短选项形式
594
635
  | `sync` | `s` | 同步 |
595
636
  | `update` | `up` | 更新 |
596
637
  | `reset` | `rs` | 重置/回退 |
638
+ | `upgrade` | `ug` | 模式升级 |
597
639
  | `help` | `h` | 帮助 |
598
640
 
599
641
  </details>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@localsummer/incspec",
3
- "version": "0.2.6",
3
+ "version": "0.3.1",
4
4
  "description": "面向 AI 编程助手的增量规范驱动开发工具",
5
5
  "bin": {
6
6
  "incspec": "src/index.mjs"
@@ -19,6 +19,7 @@ import {
19
19
  isWorkflowIncomplete,
20
20
  getWorkflowProgress,
21
21
  } from '../lib/workflow.mjs';
22
+ import { getModeLabel, formatModeInfo } from '../lib/mode-utils.mjs';
22
23
  import { getNextVersion } from '../lib/spec.mjs';
23
24
  import {
24
25
  colors,
@@ -141,11 +142,12 @@ export async function analyzeCommand(ctx) {
141
142
 
142
143
  // Handle workflow state
143
144
  let workflow = readWorkflow(projectRoot);
144
- const isQuick = options.quick || options.q;
145
+ const mode = options.minimal ? MODE.MINIMAL :
146
+ options.quick || options.q ? MODE.QUICK : MODE.FULL;
145
147
  if (!workflow?.currentWorkflow) {
146
148
  const workflowName = typeof options.workflow === 'string' ? options.workflow : `analyze-${moduleName}`;
147
- workflow = startWorkflow(projectRoot, workflowName, { mode: isQuick ? MODE.QUICK : MODE.FULL });
148
- const modeLabel = isQuick ? '(快速模式: 5步)' : '';
149
+ workflow = startWorkflow(projectRoot, workflowName, { mode });
150
+ const modeLabel = getModeLabel(mode);
149
151
  printSuccess(`已创建新工作流: ${workflowName} ${modeLabel}`);
150
152
  }
151
153
 
@@ -171,17 +173,18 @@ export async function analyzeCommand(ctx) {
171
173
 
172
174
  // Get workflow state
173
175
  let workflow = readWorkflow(projectRoot);
174
- const isQuick = options.quick || options.q;
176
+ const mode = options.minimal ? MODE.MINIMAL :
177
+ options.quick || options.q ? MODE.QUICK : MODE.FULL;
175
178
 
176
179
  // Check if starting new workflow
177
180
  if (!workflow?.currentWorkflow) {
178
181
  let workflowName = typeof options.workflow === 'string' ? options.workflow : '';
179
182
  if (!workflowName) {
180
- const modeHint = isQuick ? '(快速模式)' : '';
181
- workflowName = await prompt(`请输入工作流名称 ${modeHint}`, `analyze-${moduleName}`);
183
+ const modeLabel = getModeLabel(mode);
184
+ workflowName = await prompt(`请输入工作流名称 ${modeLabel}`, `analyze-${moduleName}`);
182
185
  }
183
- workflow = startWorkflow(projectRoot, workflowName, { mode: isQuick ? MODE.QUICK : MODE.FULL });
184
- const modeLabel = isQuick ? '(快速模式: 5步)' : '';
186
+ workflow = startWorkflow(projectRoot, workflowName, { mode });
187
+ const modeLabel = getModeLabel(mode);
185
188
  printSuccess(`已创建新工作流: ${workflowName} ${modeLabel}`);
186
189
  } else if (isWorkflowIncomplete(workflow)) {
187
190
  // Current workflow is incomplete, ask for confirmation
@@ -199,11 +202,11 @@ export async function analyzeCommand(ctx) {
199
202
  } else {
200
203
  let workflowName = typeof options.workflow === 'string' ? options.workflow : '';
201
204
  if (!workflowName) {
202
- const modeHint = isQuick ? '(快速模式)' : '';
203
- workflowName = await prompt(`请输入新工作流名称 ${modeHint}`, `analyze-${moduleName}`);
205
+ const modeLabel = getModeLabel(mode);
206
+ workflowName = await prompt(`请输入新工作流名称 ${modeLabel}`, `analyze-${moduleName}`);
204
207
  }
205
- workflow = startWorkflow(projectRoot, workflowName, { mode: isQuick ? MODE.QUICK : MODE.FULL });
206
- const modeLabel = isQuick ? '(快速模式: 5步)' : '';
208
+ workflow = startWorkflow(projectRoot, workflowName, { mode });
209
+ const modeLabel = getModeLabel(mode);
207
210
  printSuccess(`已归档旧工作流,创建新工作流: ${workflowName} ${modeLabel}`);
208
211
  }
209
212
  }
@@ -218,10 +221,9 @@ export async function analyzeCommand(ctx) {
218
221
  print(colorize('─────────────────────', colors.dim));
219
222
  print('');
220
223
 
221
- // Show quick mode info
222
- if (isQuick) {
223
- print(colorize('快速模式流程: 分析 -> 需求收集 -> 应用代码 -> 合并基线', colors.yellow));
224
- print(colorize('(跳过 UI依赖采集 和 增量设计)', colors.dim));
224
+ // Show mode info
225
+ if (mode !== MODE.FULL) {
226
+ print(colorize(formatModeInfo(mode), colors.yellow));
225
227
  print('');
226
228
  }
227
229
 
@@ -14,9 +14,15 @@ import {
14
14
  readWorkflow,
15
15
  updateStep,
16
16
  STATUS,
17
- isQuickMode,
17
+ MODE,
18
18
  getMissingPrereqs,
19
19
  } from '../lib/workflow.mjs';
20
+ import {
21
+ isMinimalMode,
22
+ isQuickMode,
23
+ isFullMode,
24
+ getApplyInputFile,
25
+ } from '../lib/mode-utils.mjs';
20
26
  import { listSpecs } from '../lib/spec.mjs';
21
27
  import {
22
28
  colors,
@@ -73,7 +79,7 @@ export async function applyCommand(ctx) {
73
79
  return;
74
80
  }
75
81
 
76
- const quickMode = isQuickMode(workflow);
82
+ const mode = workflow.mode || MODE.FULL;
77
83
  const missingSteps = getMissingPrereqs(workflow, STEP_NUMBER);
78
84
  if (missingSteps && missingSteps.length > 0 && !options.force) {
79
85
  printWarning(`请先完成步骤 ${missingSteps.join(', ')} 后再继续。`);
@@ -91,16 +97,7 @@ export async function applyCommand(ctx) {
91
97
  let inputPath;
92
98
  let inputType;
93
99
 
94
- if (quickMode) {
95
- // Quick mode: use requirements document
96
- const reqFile = path.join(projectRoot, INCSPEC_DIR, DIRS.requirements, 'structured-requirements.md');
97
- if (!fs.existsSync(reqFile)) {
98
- printWarning('未找到需求文件。请先运行步骤 2 (collect-req)。');
99
- return;
100
- }
101
- inputPath = path.join(INCSPEC_DIR, DIRS.requirements, 'structured-requirements.md');
102
- inputType = 'requirements';
103
- } else {
100
+ if (isFullMode(workflow)) {
104
101
  // Full mode: use increment design file
105
102
  let incrementPath = args[0];
106
103
  if (!incrementPath) {
@@ -128,12 +125,22 @@ export async function applyCommand(ctx) {
128
125
  }
129
126
  inputPath = incrementPath;
130
127
  inputType = 'increment';
128
+ } else {
129
+ // Quick/Minimal mode: use requirements document
130
+ const reqFile = path.join(projectRoot, INCSPEC_DIR, DIRS.requirements, 'structured-requirements.md');
131
+ if (!fs.existsSync(reqFile)) {
132
+ printWarning('未找到需求文件。请先运行步骤 2 (collect-req)。');
133
+ return;
134
+ }
135
+ inputPath = path.join(INCSPEC_DIR, DIRS.requirements, 'structured-requirements.md');
136
+ inputType = 'requirements';
131
137
  }
132
138
 
133
139
  print('');
134
140
  print(colorize('步骤 5: 应用代码变更', colors.bold, colors.cyan));
135
- if (quickMode) {
136
- print(colorize('(快速模式 - 基于需求文档)', colors.yellow));
141
+ if (mode !== MODE.FULL) {
142
+ const modeLabel = mode === MODE.MINIMAL ? '极简模式' : '快速模式';
143
+ print(colorize(`(${modeLabel} - 基于需求文档)`, colors.yellow));
137
144
  }
138
145
  print(colorize('────────────────────', colors.dim));
139
146
  print('');
@@ -149,9 +156,10 @@ export async function applyCommand(ctx) {
149
156
  print(colorize('使用说明:', colors.bold));
150
157
  print('');
151
158
 
152
- if (quickMode) {
153
- // Quick mode instructions
154
- print(colorize('快速模式下,请直接根据需求文档实现代码变更:', colors.cyan));
159
+ if (mode !== MODE.FULL) {
160
+ // Quick/Minimal mode instructions
161
+ const modeLabel = mode === MODE.MINIMAL ? '极简模式' : '快速模式';
162
+ print(colorize(`${modeLabel}下,请直接根据需求文档实现代码变更:`, colors.cyan));
155
163
  print('');
156
164
  print(colorize('在 Cursor 中:', colors.dim));
157
165
  print(colorize(` /incspec/inc-apply ${inputPath}`, colors.bold, colors.white));
@@ -10,7 +10,8 @@ import {
10
10
  DIRS,
11
11
  } from '../lib/config.mjs';
12
12
  import { archiveSpec, getSpecInfo } from '../lib/spec.mjs';
13
- import { archiveWorkflow, readWorkflow, updateStep, STATUS, isQuickMode, MODE } from '../lib/workflow.mjs';
13
+ import { archiveWorkflow, readWorkflow, updateStep, STATUS, MODE } from '../lib/workflow.mjs';
14
+ import { getArchivableStepsForMode } from '../lib/mode-utils.mjs';
14
15
  import {
15
16
  colors,
16
17
  colorize,
@@ -21,25 +22,6 @@ import {
21
22
  confirm,
22
23
  } from '../lib/terminal.mjs';
23
24
 
24
- // Full mode: archivable steps are 1, 2, 3, 4, 6 (0-based: 0, 1, 2, 3, 5)
25
- // Note: Step 5 (apply) has no file output, Step 7 (archive) is this command itself
26
- const FULL_MODE_ARCHIVABLE_INDEXES = [0, 1, 2, 3, 5];
27
- // Quick mode: archivable steps are 1, 2, 6 (0-based: 0, 1, 5)
28
- // Note: Steps 3, 4 are skipped in quick mode
29
- const QUICK_MODE_ARCHIVABLE_INDEXES = [0, 1, 5];
30
-
31
- /**
32
- * Get archivable step indexes based on workflow mode
33
- * @param {Object} workflow
34
- * @returns {number[]}
35
- */
36
- function getArchivableStepIndexes(workflow) {
37
- if (isQuickMode(workflow)) {
38
- return QUICK_MODE_ARCHIVABLE_INDEXES;
39
- }
40
- return FULL_MODE_ARCHIVABLE_INDEXES;
41
- }
42
-
43
25
  function escapeRegExp(value) {
44
26
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
45
27
  }
@@ -64,7 +46,7 @@ function collectArchivedFiles(projectRoot) {
64
46
  }
65
47
 
66
48
  const files = [];
67
-
49
+
68
50
  // Helper to collect .md files from a directory
69
51
  const collectMdFiles = (dir) => {
70
52
  const entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -75,7 +57,7 @@ function collectArchivedFiles(projectRoot) {
75
57
  }
76
58
  });
77
59
  };
78
-
60
+
79
61
  // Traverse: archives/ -> YYYY-MM/ -> [module/] -> files
80
62
  const topEntries = fs.readdirSync(archiveRoot, { withFileTypes: true });
81
63
  topEntries.forEach(entry => {
@@ -132,7 +114,8 @@ function getArchivableOutputs(workflow) {
132
114
  return null;
133
115
  }
134
116
 
135
- const archivableIndexes = getArchivableStepIndexes(workflow);
117
+ const mode = workflow.mode || MODE.FULL;
118
+ const archivableIndexes = getArchivableStepsForMode(mode);
136
119
  const outputs = [];
137
120
 
138
121
  for (const index of archivableIndexes) {
@@ -141,6 +124,11 @@ function getArchivableOutputs(workflow) {
141
124
  return null;
142
125
  }
143
126
 
127
+ // 步骤5 (apply) 的输出是"代码已应用"占位符,不是文件,跳过
128
+ if (index === 4 && step.output === '代码已应用') {
129
+ continue;
130
+ }
131
+
144
132
  const completedAt = parseWorkflowTime(step.completedAt);
145
133
  if (!completedAt) {
146
134
  return null;
@@ -254,6 +242,20 @@ export async function archiveCommand(ctx) {
254
242
  return;
255
243
  }
256
244
 
245
+ // 极简模式提醒
246
+ if (workflow.mode === MODE.MINIMAL && !skipConfirm) {
247
+ print(colorize('⚠️ 极简模式提醒:', colors.yellow, colors.bold));
248
+ print(colorize(' 极简模式跳过了步骤6(合并基线),当前归档的是初始基线和应用后的代码。', colors.dim));
249
+ print(colorize(' 如需生成新基线快照,请先运行: incspec merge', colors.dim));
250
+ print('');
251
+ const shouldContinue = await confirm('是否继续归档当前文件?');
252
+ if (!shouldContinue) {
253
+ print(colorize('已取消。', colors.dim));
254
+ return;
255
+ }
256
+ print('');
257
+ }
258
+
257
259
  if (targets.length > 0) {
258
260
  print(colorize('将归档以下文件:', colors.dim));
259
261
  targets.forEach(target => print(colorize(` - ${target.name}`, colors.dim)));
@@ -11,9 +11,12 @@ import {
11
11
  STEPS,
12
12
  STATUS,
13
13
  MODE,
14
- QUICK_MODE_SKIPPED,
15
14
  isQuickMode,
16
15
  } from '../lib/workflow.mjs';
16
+ import {
17
+ getModeLabel,
18
+ getSkippedStepsForMode,
19
+ } from '../lib/mode-utils.mjs';
17
20
  import {
18
21
  colors,
19
22
  colorize,
@@ -52,8 +55,9 @@ export async function statusCommand(ctx) {
52
55
 
53
56
  // Current workflow
54
57
  if (workflow?.currentWorkflow) {
55
- const quickMode = isQuickMode(workflow);
56
- const modeLabel = quickMode ? '快速模式 (5步)' : '完整模式 (7步)';
58
+ const mode = workflow.mode || MODE.FULL;
59
+ const modeLabel = getModeLabel(mode);
60
+ const skippedSteps = getSkippedStepsForMode(mode);
57
61
 
58
62
  print(colorize(`当前工作流: `, colors.bold) + colorize(workflow.currentWorkflow, colors.cyan));
59
63
  print(colorize(`工作流模式: ${modeLabel}`, colors.dim));
@@ -69,9 +73,9 @@ export async function statusCommand(ctx) {
69
73
  const stepData = workflow.steps[index] || {};
70
74
  let status = stepData.status || STATUS.PENDING;
71
75
  const isCurrent = workflow.currentStep === step.id;
72
- const isSkipped = quickMode && QUICK_MODE_SKIPPED.includes(step.id);
76
+ const isSkipped = skippedSteps.includes(step.id);
73
77
 
74
- // Quick mode: mark skipped steps
78
+ // Mark skipped steps
75
79
  if (isSkipped && status !== STATUS.SKIPPED) {
76
80
  status = STATUS.SKIPPED;
77
81
  }
@@ -100,8 +104,8 @@ export async function statusCommand(ctx) {
100
104
  // Next step hint
101
105
  const nextStepIndex = workflow.steps.findIndex((step, index) => {
102
106
  const stepNumber = index + 1;
103
- // Skip excluded steps in quick mode
104
- if (quickMode && QUICK_MODE_SKIPPED.includes(stepNumber)) {
107
+ // Skip excluded steps based on mode
108
+ if (skippedSteps.includes(stepNumber)) {
105
109
  return false;
106
110
  }
107
111
  return (step?.status || STATUS.PENDING) !== STATUS.COMPLETED;
@@ -61,8 +61,8 @@ export async function syncCommand(ctx) {
61
61
  targets = await checkbox({
62
62
  message: '选择要同步的目标:',
63
63
  choices: [
64
- { ...SYNC_TARGETS.cursor, checked: true },
65
- { ...SYNC_TARGETS.claude, checked: false },
64
+ { ...SYNC_TARGETS.cursor },
65
+ { ...SYNC_TARGETS.claude },
66
66
  ],
67
67
  });
68
68
 
@@ -0,0 +1,139 @@
1
+ /**
2
+ * upgrade command - Upgrade workflow mode
3
+ */
4
+
5
+ import {
6
+ ensureInitialized,
7
+ } from '../lib/config.mjs';
8
+ import {
9
+ readWorkflow,
10
+ writeWorkflow,
11
+ STATUS,
12
+ MODE,
13
+ } from '../lib/workflow.mjs';
14
+ import {
15
+ validateModeUpgrade,
16
+ getMissingStepsAfterUpgrade,
17
+ getModeLabel,
18
+ MODE_UPGRADE_ORDER,
19
+ } from '../lib/mode-utils.mjs';
20
+ import {
21
+ colors,
22
+ colorize,
23
+ print,
24
+ printSuccess,
25
+ printWarning,
26
+ printError,
27
+ printInfo,
28
+ confirm,
29
+ } from '../lib/terminal.mjs';
30
+
31
+ /**
32
+ * Execute upgrade command
33
+ * @param {Object} ctx - Command context
34
+ */
35
+ export async function upgradeCommand(ctx) {
36
+ const { cwd, args, options } = ctx;
37
+
38
+ const targetMode = args[0]; // 目标模式:quick 或 full
39
+
40
+ // Validate target mode
41
+ if (!targetMode) {
42
+ printError('请指定目标模式');
43
+ print('');
44
+ print(colorize('用法:', colors.bold));
45
+ print(colorize(' incspec upgrade <mode>', colors.dim));
46
+ print('');
47
+ print(colorize('可用模式:', colors.bold));
48
+ print(colorize(` ${MODE_UPGRADE_ORDER.join(' → ')}`, colors.cyan));
49
+ return;
50
+ }
51
+
52
+ if (!Object.values(MODE).includes(targetMode)) {
53
+ printError(`未知的目标模式: ${targetMode}`);
54
+ print('');
55
+ print(colorize('可用模式:', colors.bold));
56
+ print(colorize(` ${MODE_UPGRADE_ORDER.join(', ')}`, colors.dim));
57
+ return;
58
+ }
59
+
60
+ // Ensure initialized
61
+ const projectRoot = ensureInitialized(cwd);
62
+
63
+ // Get workflow state
64
+ const workflow = readWorkflow(projectRoot);
65
+
66
+ if (!workflow?.currentWorkflow) {
67
+ printError('当前没有活跃的工作流');
68
+ printInfo('请先运行 incspec analyze 开始新工作流');
69
+ return;
70
+ }
71
+
72
+ const currentMode = workflow.mode || MODE.FULL;
73
+
74
+ // Validate upgrade
75
+ const validation = validateModeUpgrade(currentMode, targetMode);
76
+ if (!validation.valid) {
77
+ printError(`无法升级: ${validation.reason}`);
78
+ print('');
79
+ print(colorize('提示:', colors.bold));
80
+ print(colorize(' 只能从宽松模式升级到严格模式', colors.dim));
81
+ print(colorize(` 升级路径: ${MODE_UPGRADE_ORDER.join(' → ')}`, colors.cyan));
82
+ return;
83
+ }
84
+
85
+ // Get missing steps
86
+ const missingSteps = getMissingStepsAfterUpgrade(currentMode, targetMode);
87
+
88
+ print('');
89
+ print(colorize('工作流模式升级', colors.bold, colors.cyan));
90
+ print(colorize('──────────────', colors.dim));
91
+ print('');
92
+ print(colorize(`当前模式: ${getModeLabel(currentMode)}`, colors.dim));
93
+ print(colorize(`目标模式: ${getModeLabel(targetMode)}`, colors.cyan));
94
+ print('');
95
+
96
+ if (missingSteps.length > 0) {
97
+ print(colorize('需要补充的步骤:', colors.bold));
98
+ missingSteps.forEach(stepId => {
99
+ print(colorize(` - 步骤 ${stepId}`, colors.yellow));
100
+ });
101
+ print('');
102
+ } else {
103
+ print(colorize('无需补充额外步骤', colors.dim));
104
+ print('');
105
+ }
106
+
107
+ const confirmed = await confirm('确认升级工作流模式?');
108
+ if (!confirmed) {
109
+ print('');
110
+ printInfo('已取消升级');
111
+ return;
112
+ }
113
+
114
+ // Update workflow mode
115
+ workflow.mode = targetMode;
116
+
117
+ // Update skipped steps to pending (the steps that were skipped but now are needed)
118
+ missingSteps.forEach(stepId => {
119
+ const index = stepId - 1;
120
+ if (workflow.steps[index]) {
121
+ workflow.steps[index].status = STATUS.PENDING;
122
+ workflow.steps[index].output = null;
123
+ workflow.steps[index].completedAt = null;
124
+ }
125
+ });
126
+
127
+ // Write updated workflow
128
+ writeWorkflow(projectRoot, workflow);
129
+
130
+ print('');
131
+ printSuccess(`已升级到 ${getModeLabel(targetMode)}`);
132
+
133
+ if (missingSteps.length > 0) {
134
+ print('');
135
+ printInfo(`请依次执行步骤: ${missingSteps.join(', ')}`);
136
+ print('');
137
+ print(colorize('提示: 运行 incspec status 查看详细进度', colors.dim));
138
+ }
139
+ }
package/src/index.mjs CHANGED
@@ -18,6 +18,7 @@ import { listCommand } from './commands/list.mjs';
18
18
  import { validateCommand } from './commands/validate.mjs';
19
19
  import { archiveCommand } from './commands/archive.mjs';
20
20
  import { resetCommand } from './commands/reset.mjs';
21
+ import { upgradeCommand } from './commands/upgrade.mjs';
21
22
  import { syncCommand } from './commands/sync.mjs';
22
23
  import { helpCommand } from './commands/help.mjs';
23
24
  import { colors, colorize } from './lib/terminal.mjs';
@@ -211,6 +212,12 @@ async function main() {
211
212
  await resetCommand(commandContext);
212
213
  break;
213
214
 
215
+ // Upgrade workflow mode
216
+ case 'upgrade':
217
+ case 'ug':
218
+ await upgradeCommand(commandContext);
219
+ break;
220
+
214
221
  // Sync integrations
215
222
  case 'sync':
216
223
  case 's':