@localsummer/incspec 0.3.1 → 0.3.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.
package/README.md CHANGED
@@ -511,7 +511,7 @@ your-project/
511
511
  │ └── templates/ # Markdown 模板文件
512
512
  ├── incspec/
513
513
  │ ├── project.md # 项目配置
514
- │ ├── WORKFLOW.md # 当前工作流状态
514
+ │ ├── workflow.json # 当前工作流状态
515
515
  │ ├── AGENTS.md # incspec 使用指南
516
516
  │ ├── baselines/ # 基线快照
517
517
  │ │ └── home-baseline-v1.md
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@localsummer/incspec",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "面向 AI 编程助手的增量规范驱动开发工具",
5
5
  "bin": {
6
6
  "incspec": "src/index.mjs"
@@ -13,6 +13,7 @@ import {
13
13
  updateStep,
14
14
  STATUS,
15
15
  isQuickMode,
16
+ isStepAllowed,
16
17
  getMissingPrereqs,
17
18
  } from '../lib/workflow.mjs';
18
19
  import {
@@ -46,6 +47,10 @@ export async function collectReqCommand(ctx) {
46
47
  printWarning('没有活跃的工作流,无法标记完成。');
47
48
  return;
48
49
  }
50
+ if (!isStepAllowed(STEP_NUMBER, workflow.mode)) {
51
+ printWarning('当前工作流模式不包含此步骤,无需标记完成。');
52
+ return;
53
+ }
49
54
  updateStep(projectRoot, STEP_NUMBER, STATUS.COMPLETED, OUTPUT_FILE);
50
55
  printSuccess(`步骤 ${STEP_NUMBER} 已标记为完成: ${OUTPUT_FILE}`);
51
56
 
@@ -63,6 +68,11 @@ export async function collectReqCommand(ctx) {
63
68
  return;
64
69
  }
65
70
 
71
+ if (!isStepAllowed(STEP_NUMBER, workflow.mode)) {
72
+ printWarning('当前工作流模式不包含此步骤(极简模式跳过需求收集)。');
73
+ return;
74
+ }
75
+
66
76
  const missingSteps = getMissingPrereqs(workflow, STEP_NUMBER);
67
77
  if (missingSteps && missingSteps.length > 0 && !options.force) {
68
78
  printWarning(`请先完成步骤 ${missingSteps.join(', ')} 后再继续。`);
@@ -109,7 +109,7 @@ export async function initCommand(ctx) {
109
109
  print(colorize('创建的目录结构:', colors.bold));
110
110
  print(colorize(` ${INCSPEC_DIR}/`, colors.cyan));
111
111
  print(colorize(` ├── project.md`, colors.dim));
112
- print(colorize(` ├── WORKFLOW.md`, colors.dim));
112
+ print(colorize(` ├── workflow.json`, colors.dim));
113
113
  print(colorize(` ├── AGENTS.md`, colors.dim));
114
114
  print(colorize(` ├── baselines/`, colors.dim));
115
115
  print(colorize(` ├── requirements/`, colors.dim));
@@ -14,6 +14,7 @@ import {
14
14
  updateStep,
15
15
  STATUS,
16
16
  isQuickMode,
17
+ isStepAllowed,
17
18
  getMissingPrereqs,
18
19
  } from '../lib/workflow.mjs';
19
20
  import { listSpecs, getNextVersion } from '../lib/spec.mjs';
@@ -61,6 +62,10 @@ export async function mergeCommand(ctx) {
61
62
  printWarning('没有活跃的工作流,无法标记完成。');
62
63
  return;
63
64
  }
65
+ if (!isStepAllowed(STEP_NUMBER, workflow.mode)) {
66
+ printWarning('当前工作流模式不包含此步骤,无需标记完成。');
67
+ return;
68
+ }
64
69
  const output = typeof options.output === 'string' ? options.output : null;
65
70
  if (!output) {
66
71
  printWarning('请通过 --output 指定输出文件名。');
@@ -76,6 +81,12 @@ export async function mergeCommand(ctx) {
76
81
  return;
77
82
  }
78
83
 
84
+ if (!isStepAllowed(STEP_NUMBER, workflow.mode)) {
85
+ printWarning('当前工作流模式不包含此步骤(极简模式跳过合并基线)。');
86
+ printInfo('极简模式下可在归档前使用 incspec upgrade quick 升级模式后执行合并。');
87
+ return;
88
+ }
89
+
79
90
  const quickMode = isQuickMode(workflow);
80
91
  const missingSteps = getMissingPrereqs(workflow, STEP_NUMBER);
81
92
  if (missingSteps && missingSteps.length > 0 && !options.force) {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * update command - Update template files to latest version
3
- * Updates AGENTS.md, WORKFLOW.md in incspec/ and managed block in project AGENTS.md
3
+ * Updates AGENTS.md, workflow.json in incspec/ and managed block in project AGENTS.md
4
4
  */
5
5
 
6
6
  import * as fs from 'fs';
@@ -53,19 +53,19 @@ function updateIncspecAgents(projectRoot) {
53
53
  }
54
54
 
55
55
  /**
56
- * Update incspec/WORKFLOW.md template structure while preserving user data
56
+ * Update incspec/workflow.json template structure while preserving user data
57
57
  * @param {string} projectRoot
58
58
  * @returns {{updated: boolean, path: string}}
59
59
  */
60
60
  function updateIncspecWorkflow(projectRoot) {
61
61
  const targetPath = path.join(getIncspecDir(projectRoot), FILES.workflow);
62
- const templatePath = path.join(getTemplatesDir(), 'WORKFLOW.md');
62
+ const templatePath = path.join(getTemplatesDir(), 'workflow.json');
63
63
 
64
64
  if (!fs.existsSync(templatePath)) {
65
65
  return { updated: false, path: targetPath, error: '模板文件不存在' };
66
66
  }
67
67
 
68
- // For WORKFLOW.md, we only update if file doesn't exist
68
+ // For workflow.json, we only update if file doesn't exist
69
69
  // Because it contains dynamic user data that we don't want to overwrite
70
70
  // The template is only used for initial structure
71
71
  if (fs.existsSync(targetPath)) {
@@ -104,7 +104,7 @@ export async function updateCommand(ctx) {
104
104
  // List what will be updated
105
105
  print(colorize('将更新以下模板文件:', colors.bold));
106
106
  print(colorize(' - incspec/AGENTS.md (incspec 使用指南)', colors.dim));
107
- print(colorize(' - incspec/WORKFLOW.md (工作流模板,保留用户数据)', colors.dim));
107
+ print(colorize(' - incspec/workflow.json (工作流状态,保留用户数据)', colors.dim));
108
108
  print(colorize(' - AGENTS.md (项目根目录 incspec 指令块)', colors.dim));
109
109
  print('');
110
110
 
@@ -128,10 +128,10 @@ export async function updateCommand(ctx) {
128
128
  ...agentsResult,
129
129
  });
130
130
 
131
- // Update incspec/WORKFLOW.md
131
+ // Update incspec/workflow.json
132
132
  const workflowResult = updateIncspecWorkflow(projectRoot);
133
133
  results.push({
134
- file: 'incspec/WORKFLOW.md',
134
+ file: 'incspec/workflow.json',
135
135
  ...workflowResult,
136
136
  });
137
137
 
@@ -24,7 +24,8 @@ export const DIRS = {
24
24
  /** Core files */
25
25
  export const FILES = {
26
26
  project: 'project.md',
27
- workflow: 'WORKFLOW.md',
27
+ workflow: 'workflow.json',
28
+ workflowLegacy: 'WORKFLOW.md',
28
29
  agents: 'AGENTS.md',
29
30
  };
30
31
 
@@ -136,7 +136,7 @@ incspec status
136
136
  或直接读取状态文件:
137
137
 
138
138
  \`\`\`bash
139
- cat ${INCSPEC_DIR}/WORKFLOW.md
139
+ cat ${INCSPEC_DIR}/workflow.json
140
140
  \`\`\`
141
141
  `,
142
142
  },
@@ -187,7 +187,7 @@ incspec help # 显示帮助
187
187
  \`\`\`
188
188
  ${INCSPEC_DIR}/
189
189
  ├── project.md # 项目配置
190
- ├── WORKFLOW.md # 工作流状态
190
+ ├── workflow.json # 工作流状态
191
191
  ├── baselines/ # 基线快照
192
192
  ├── requirements/ # 需求文档
193
193
  ├── increments/ # 增量设计
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Workflow state management for incspec
3
- * - Read/write WORKFLOW.md
3
+ * - Read/write workflow.json
4
4
  * - Track current step
5
5
  * - Manage workflow history
6
6
  */
@@ -54,67 +54,8 @@ function normalizeOutputName(outputFile) {
54
54
  return parts[parts.length - 1] || outputFile;
55
55
  }
56
56
 
57
- function ensureTableSeparator(content, sectionTitle, headerLine, separatorLine) {
58
- const sectionIndex = content.indexOf(sectionTitle);
59
- if (sectionIndex === -1) {
60
- return { content, updated: false };
61
- }
62
-
63
- const headerIndex = content.indexOf(headerLine, sectionIndex);
64
- if (headerIndex === -1) {
65
- return { content, updated: false };
66
- }
67
-
68
- const headerLineEnd = content.indexOf('\n', headerIndex);
69
- if (headerLineEnd === -1) {
70
- return { content, updated: false };
71
- }
72
-
73
- const nextLineStart = headerLineEnd + 1;
74
- const nextLineEnd = content.indexOf('\n', nextLineStart);
75
- const nextLine = content
76
- .slice(nextLineStart, nextLineEnd === -1 ? content.length : nextLineEnd)
77
- .replace(/\r$/, '');
78
-
79
- if (nextLine.trim() === separatorLine.trim()) {
80
- return { content, updated: false };
81
- }
82
-
83
- const updatedContent =
84
- content.slice(0, nextLineStart) +
85
- `${separatorLine}\n` +
86
- content.slice(nextLineStart);
87
-
88
- return { content: updatedContent, updated: true };
89
- }
90
-
91
- function normalizeWorkflowContent(content) {
92
- let updated = false;
93
- let normalized = content;
94
-
95
- const stepsResult = ensureTableSeparator(
96
- normalized,
97
- '## 步骤进度',
98
- '| 步骤 | 状态 | 输出文件 | 完成时间 |',
99
- '|------|------|---------|---------|'
100
- );
101
- normalized = stepsResult.content;
102
- updated = updated || stepsResult.updated;
103
-
104
- const historyResult = ensureTableSeparator(
105
- normalized,
106
- '## 工作流历史',
107
- '| 工作流 | 状态 | 开始时间 | 完成时间 |',
108
- '|--------|------|---------|---------|'
109
- );
110
- normalized = historyResult.content;
111
- updated = updated || historyResult.updated;
112
-
113
- return { content: normalized, updated };
114
- }
115
-
116
57
  /**
117
- * Get workflow file path
58
+ * Get workflow file path (JSON format)
118
59
  * @param {string} projectRoot
119
60
  * @returns {string}
120
61
  */
@@ -123,11 +64,20 @@ export function getWorkflowPath(projectRoot) {
123
64
  }
124
65
 
125
66
  /**
126
- * Parse WORKFLOW.md content
67
+ * Get legacy workflow file path (Markdown format)
68
+ * @param {string} projectRoot
69
+ * @returns {string}
70
+ */
71
+ function getLegacyWorkflowPath(projectRoot) {
72
+ return path.join(projectRoot, INCSPEC_DIR, FILES.workflowLegacy);
73
+ }
74
+
75
+ /**
76
+ * Parse legacy WORKFLOW.md content (for migration)
127
77
  * @param {string} content
128
78
  * @returns {Object}
129
79
  */
130
- export function parseWorkflow(content) {
80
+ function parseMarkdownWorkflow(content) {
131
81
  const workflow = {
132
82
  currentWorkflow: null,
133
83
  currentStep: null,
@@ -167,7 +117,8 @@ export function parseWorkflow(content) {
167
117
 
168
118
  const startMatch = content.match(/\*\*开始时间\*\*:\s*(.+)/);
169
119
  if (startMatch) {
170
- workflow.startTime = startMatch[1].trim();
120
+ const value = startMatch[1].trim();
121
+ workflow.startTime = value === '-' ? null : value;
171
122
  }
172
123
 
173
124
  const updateMatch = content.match(/\*\*最后更新\*\*:\s*(.+)/);
@@ -179,11 +130,12 @@ export function parseWorkflow(content) {
179
130
  const stepsTableMatch = content.match(/## 步骤进度\n\n\|[^\n]+\n\|[^\n]+\n([\s\S]*?)(?=\n##|\n*$)/);
180
131
  if (stepsTableMatch) {
181
132
  const rows = stepsTableMatch[1].trim().split('\n').filter(r => r.trim());
182
- workflow.steps = rows.map(row => {
133
+ workflow.steps = rows.map((row, index) => {
183
134
  const cells = row.split('|').map(c => c.trim()).filter(c => c);
184
135
  if (cells.length >= 4) {
185
136
  return {
186
- step: cells[0],
137
+ id: index + 1,
138
+ name: STEPS[index]?.name || `step-${index + 1}`,
187
139
  status: cells[1],
188
140
  output: cells[2] === '-' ? null : cells[2],
189
141
  completedAt: cells[3] === '-' ? null : cells[3],
@@ -193,6 +145,18 @@ export function parseWorkflow(content) {
193
145
  }).filter(Boolean);
194
146
  }
195
147
 
148
+ // Ensure we have all 7 steps
149
+ while (workflow.steps.length < STEPS.length) {
150
+ const index = workflow.steps.length;
151
+ workflow.steps.push({
152
+ id: index + 1,
153
+ name: STEPS[index].name,
154
+ status: STATUS.PENDING,
155
+ output: null,
156
+ completedAt: null,
157
+ });
158
+ }
159
+
196
160
  // Parse history table
197
161
  const historyMatch = content.match(/## 工作流历史\n\n\|[^\n]+\n\|[^\n]+\n([\s\S]*?)(?=\n##|\n*$)/);
198
162
  if (historyMatch) {
@@ -214,12 +178,80 @@ export function parseWorkflow(content) {
214
178
  return workflow;
215
179
  }
216
180
 
181
+ /**
182
+ * Migrate from WORKFLOW.md to workflow.json
183
+ * @param {string} projectRoot
184
+ * @returns {Object|null} Migrated workflow or null if no migration needed
185
+ */
186
+ function migrateFromMarkdown(projectRoot) {
187
+ const legacyPath = getLegacyWorkflowPath(projectRoot);
188
+ const jsonPath = getWorkflowPath(projectRoot);
189
+
190
+ // Check if legacy file exists and JSON doesn't
191
+ if (!fs.existsSync(legacyPath)) {
192
+ return null;
193
+ }
194
+
195
+ if (fs.existsSync(jsonPath)) {
196
+ // JSON already exists, no migration needed
197
+ return null;
198
+ }
199
+
200
+ // Read and parse legacy file
201
+ const content = fs.readFileSync(legacyPath, 'utf-8');
202
+ const workflow = parseMarkdownWorkflow(content);
203
+
204
+ // Update lastUpdate
205
+ workflow.lastUpdate = formatLocalDateTime(new Date());
206
+
207
+ // Write new JSON file
208
+ const jsonContent = JSON.stringify(workflow, null, 2);
209
+ fs.writeFileSync(jsonPath, jsonContent, 'utf-8');
210
+
211
+ // Delete legacy file
212
+ fs.unlinkSync(legacyPath);
213
+
214
+ console.log(`已将 WORKFLOW.md 迁移到 workflow.json`);
215
+
216
+ return workflow;
217
+ }
218
+
219
+ /**
220
+ * Parse workflow.json content
221
+ * @param {string} content - JSON string
222
+ * @returns {Object}
223
+ */
224
+ export function parseWorkflow(content) {
225
+ try {
226
+ const workflow = JSON.parse(content);
227
+
228
+ // Ensure all required fields exist with defaults
229
+ return {
230
+ currentWorkflow: workflow.currentWorkflow ?? null,
231
+ currentStep: workflow.currentStep ?? null,
232
+ mode: workflow.mode ?? MODE.FULL,
233
+ startTime: workflow.startTime ?? null,
234
+ lastUpdate: workflow.lastUpdate ?? null,
235
+ steps: workflow.steps ?? [],
236
+ history: workflow.history ?? [],
237
+ };
238
+ } catch (e) {
239
+ throw new Error(`解析 workflow.json 失败: ${e.message}`);
240
+ }
241
+ }
242
+
217
243
  /**
218
244
  * Read workflow state
219
245
  * @param {string} projectRoot
220
246
  * @returns {Object|null}
221
247
  */
222
248
  export function readWorkflow(projectRoot) {
249
+ // Try migration first
250
+ const migrated = migrateFromMarkdown(projectRoot);
251
+ if (migrated) {
252
+ return migrated;
253
+ }
254
+
223
255
  const workflowPath = getWorkflowPath(projectRoot);
224
256
 
225
257
  if (!fs.existsSync(workflowPath)) {
@@ -227,12 +259,11 @@ export function readWorkflow(projectRoot) {
227
259
  }
228
260
 
229
261
  const content = fs.readFileSync(workflowPath, 'utf-8');
230
- const normalized = normalizeWorkflowContent(content);
231
- return parseWorkflow(normalized.content);
262
+ return parseWorkflow(content);
232
263
  }
233
264
 
234
265
  /**
235
- * Generate WORKFLOW.md content
266
+ * Generate workflow.json content
236
267
  * @param {Object} workflow
237
268
  * @returns {string}
238
269
  */
@@ -240,44 +271,29 @@ export function generateWorkflowContent(workflow) {
240
271
  const now = formatLocalDateTime(new Date());
241
272
  const mode = workflow.mode || MODE.FULL;
242
273
 
243
- const lines = [
244
- '# Workflow Status',
245
- '',
246
- `**当前工作流**: ${workflow.currentWorkflow || '-'}`,
247
- `**当前步骤**: ${workflow.currentStep || '-'}`,
248
- `**工作流模式**: ${mode}`,
249
- `**开始时间**: ${workflow.startTime || '-'}`,
250
- `**最后更新**: ${now}`,
251
- '',
252
- '## 步骤进度',
253
- '',
254
- '| 步骤 | 状态 | 输出文件 | 完成时间 |',
255
- '|------|------|---------|---------|',
256
- ];
257
-
258
- // Generate steps
259
- STEPS.forEach((step, index) => {
274
+ // Ensure steps have proper structure
275
+ const steps = STEPS.map((step, index) => {
260
276
  const stepData = workflow.steps[index] || {};
261
- const status = stepData.status || STATUS.PENDING;
262
- const output = stepData.output || '-';
263
- const completedAt = stepData.completedAt || '-';
264
- lines.push(`| ${step.id}. ${step.name} | ${status} | ${output} | ${completedAt} |`);
277
+ return {
278
+ id: step.id,
279
+ name: step.name,
280
+ status: stepData.status || STATUS.PENDING,
281
+ output: stepData.output || null,
282
+ completedAt: stepData.completedAt || null,
283
+ };
265
284
  });
266
285
 
267
- lines.push('');
268
- lines.push('## 工作流历史');
269
- lines.push('');
270
- lines.push('| 工作流 | 状态 | 开始时间 | 完成时间 |');
271
- lines.push('|--------|------|---------|---------|');
272
-
273
- // Generate history
274
- if (workflow.history && workflow.history.length > 0) {
275
- workflow.history.forEach(item => {
276
- lines.push(`| ${item.name} | ${item.status} | ${item.startTime || '-'} | ${item.endTime || '-'} |`);
277
- });
278
- }
286
+ const data = {
287
+ currentWorkflow: workflow.currentWorkflow || null,
288
+ currentStep: workflow.currentStep || null,
289
+ mode: mode,
290
+ startTime: workflow.startTime || null,
291
+ lastUpdate: now,
292
+ steps: steps,
293
+ history: workflow.history || [],
294
+ };
279
295
 
280
- return lines.join('\n');
296
+ return JSON.stringify(data, null, 2);
281
297
  }
282
298
 
283
299
  /**
@@ -297,45 +313,17 @@ export function writeWorkflow(projectRoot, workflow) {
297
313
  */
298
314
  export function initWorkflow(projectRoot) {
299
315
  const workflowPath = getWorkflowPath(projectRoot);
300
- const content = generateInitialWorkflowContent();
301
- fs.writeFileSync(workflowPath, content, 'utf-8');
302
-
303
- return {
304
- currentWorkflow: null,
305
- currentStep: null,
306
- mode: MODE.FULL,
307
- startTime: null,
308
- lastUpdate: null,
309
- steps: STEPS.map(() => ({
310
- status: STATUS.PENDING,
311
- output: null,
312
- completedAt: null,
313
- })),
314
- history: [],
315
- };
316
- }
317
-
318
- /**
319
- * Generate initial WORKFLOW.md content from template
320
- * @returns {string}
321
- */
322
- function generateInitialWorkflowContent() {
323
- const templatePath = path.join(getTemplatesDir(), 'WORKFLOW.md');
324
316
  const now = formatLocalDateTime(new Date());
325
317
 
326
- if (fs.existsSync(templatePath)) {
327
- let content = fs.readFileSync(templatePath, 'utf-8');
328
- content = content.replace(/\{\{last_update\}\}/g, now);
329
- return content;
330
- }
331
-
332
- // Fallback to generated content
333
318
  const workflow = {
334
319
  currentWorkflow: null,
335
320
  currentStep: null,
321
+ mode: MODE.FULL,
336
322
  startTime: null,
337
- lastUpdate: null,
338
- steps: STEPS.map(() => ({
323
+ lastUpdate: now,
324
+ steps: STEPS.map((step) => ({
325
+ id: step.id,
326
+ name: step.name,
339
327
  status: STATUS.PENDING,
340
328
  output: null,
341
329
  completedAt: null,
@@ -343,7 +331,10 @@ function generateInitialWorkflowContent() {
343
331
  history: [],
344
332
  };
345
333
 
346
- return generateWorkflowContent(workflow);
334
+ const content = JSON.stringify(workflow, null, 2);
335
+ fs.writeFileSync(workflowPath, content, 'utf-8');
336
+
337
+ return workflow;
347
338
  }
348
339
 
349
340
  /**
@@ -391,12 +382,16 @@ export function startWorkflow(projectRoot, workflowName, options = {}) {
391
382
  workflow.steps = STEPS.map((step) => {
392
383
  if (skippedSteps.includes(step.id)) {
393
384
  return {
385
+ id: step.id,
386
+ name: step.name,
394
387
  status: STATUS.SKIPPED,
395
388
  output: null,
396
389
  completedAt: now,
397
390
  };
398
391
  }
399
392
  return {
393
+ id: step.id,
394
+ name: step.name,
400
395
  status: STATUS.PENDING,
401
396
  output: null,
402
397
  completedAt: null,
@@ -430,6 +425,8 @@ export function updateStep(projectRoot, stepNumber, status, outputFile = null) {
430
425
  const mode = workflow.mode || MODE.FULL;
431
426
 
432
427
  workflow.steps[index] = {
428
+ id: stepNumber,
429
+ name: STEPS[index].name,
433
430
  status,
434
431
  output: normalizedOutput,
435
432
  completedAt: status === STATUS.COMPLETED ? now : null,
@@ -494,14 +491,16 @@ export function archiveWorkflow(projectRoot) {
494
491
  workflow.history.unshift({
495
492
  name: workflow.currentWorkflow,
496
493
  status: 'archived',
497
- startTime: workflow.startTime || '-',
494
+ startTime: workflow.startTime || null,
498
495
  endTime: now,
499
496
  });
500
497
 
501
498
  workflow.currentWorkflow = null;
502
499
  workflow.currentStep = null;
503
500
  workflow.startTime = null;
504
- workflow.steps = STEPS.map(() => ({
501
+ workflow.steps = STEPS.map((step) => ({
502
+ id: step.id,
503
+ name: step.name,
505
504
  status: STATUS.PENDING,
506
505
  output: null,
507
506
  completedAt: null,
@@ -563,12 +562,16 @@ export function resetToStep(projectRoot, targetStep) {
563
562
  // Keep skipped steps as skipped based on mode
564
563
  if (skippedSteps.includes(stepNumber)) {
565
564
  workflow.steps[i] = {
565
+ id: stepNumber,
566
+ name: STEPS[i].name,
566
567
  status: STATUS.SKIPPED,
567
568
  output: null,
568
569
  completedAt: workflow.steps[i]?.completedAt || now,
569
570
  };
570
571
  } else {
571
572
  workflow.steps[i] = {
573
+ id: stepNumber,
574
+ name: STEPS[i].name,
572
575
  status: STATUS.PENDING,
573
576
  output: null,
574
577
  completedAt: null,
@@ -676,9 +679,12 @@ export function addToHistory(projectRoot, entry) {
676
679
  workflow = {
677
680
  currentWorkflow: null,
678
681
  currentStep: null,
682
+ mode: MODE.FULL,
679
683
  startTime: null,
680
684
  lastUpdate: null,
681
- steps: STEPS.map(() => ({
685
+ steps: STEPS.map((step) => ({
686
+ id: step.id,
687
+ name: step.name,
682
688
  status: STATUS.PENDING,
683
689
  output: null,
684
690
  completedAt: null,
@@ -692,7 +698,7 @@ export function addToHistory(projectRoot, entry) {
692
698
  workflow.history.unshift({
693
699
  name: entry.name,
694
700
  status: entry.status,
695
- startTime: entry.startTime || '-',
701
+ startTime: entry.startTime || null,
696
702
  endTime: entry.endTime || now,
697
703
  });
698
704
 
@@ -1,5 +1,7 @@
1
1
  # IncSpec 使用指南
2
2
 
3
+ > **核心定位**: CLI 工具仅用于工作流状态的跟踪记录和流程串联。**每个步骤的具体执行内容**(如代码分析、需求收集、UI依赖收集、增量设计、应用代码等)**应由用户指定的 Agent(Cursor、Claude Code) 根据对应指令完成**。
4
+
3
5
  AI 编码助手使用 IncSpec 进行增量规格驱动开发的操作指南。
4
6
 
5
7
  ## 快速检查清单
@@ -154,7 +156,7 @@ AI 编码助手使用 IncSpec 进行增量规格驱动开发的操作指南。
154
156
  ```
155
157
  incspec/
156
158
  ├── project.md # 项目配置
157
- ├── WORKFLOW.md # 工作流状态
159
+ ├── workflow.json # 工作流状态
158
160
  ├── AGENTS.md # 本指南
159
161
  ├── baselines/ # 基线快照 (版本控制)
160
162
  ├── requirements/ # 需求与依赖
@@ -145,11 +145,10 @@ ls -la incspec/archives/2025-12/ # 当月目录
145
145
  ### 4.2 检查工作流历史
146
146
 
147
147
  ```bash
148
- cat incspec/WORKFLOW.md
148
+ cat incspec/workflow.json
149
149
  ```
150
150
 
151
151
  确认工作流历史已更新,包含工作流级别的归档记录(不是文件级别)。
152
- 如果仅归档部分文件且本工作流尚未全部归档,工作流历史不应新增记录。
153
152
 
154
153
  ### 4.3 运行验证
155
154
 
@@ -265,7 +264,7 @@ incspec archive --workflow --yes
265
264
  1. **完成后归档**: 在完成工作流后及时归档,保持工作目录整洁
266
265
  2. **默认移动模式**: 默认会删除原文件,保持工作流干净;如需保留原文件使用 `--keep`
267
266
  3. **批量归档**: 工作流完成后,一次性归档所有相关文件
268
- 4. **检查历史**: 归档后检查 WORKFLOW.md 确认记录正确
267
+ 4. **检查历史**: 归档后检查 workflow.json 确认记录正确
269
268
 
270
269
  # 错误处理
271
270
 
@@ -67,10 +67,10 @@ incspec upgrade <target-mode>
67
67
  incspec status
68
68
  ```
69
69
 
70
- 或直接读取 WORKFLOW.md:
70
+ 或直接读取 workflow.json:
71
71
 
72
72
  ```bash
73
- cat incspec/WORKFLOW.md
73
+ cat incspec/workflow.json
74
74
  ```
75
75
 
76
76
  检查:
@@ -112,10 +112,10 @@ cat incspec/WORKFLOW.md
112
112
 
113
113
  ### 3.1 检查当前工作流进度
114
114
 
115
- 读取 WORKFLOW.md 检查哪些步骤已完成:
115
+ 读取 workflow.json 检查哪些步骤已完成:
116
116
 
117
117
  ```bash
118
- cat incspec/WORKFLOW.md | grep -A 20 "## 当前工作流"
118
+ cat incspec/workflow.json
119
119
  ```
120
120
 
121
121
  ### 3.2 按顺序引导补充步骤
@@ -214,7 +214,7 @@ incspec upgrade <target-mode>
214
214
 
215
215
  CLI 会:
216
216
  1. 验证所有必需步骤已完成
217
- 2. 更新 WORKFLOW.md 中的模式标记
217
+ 2. 更新 workflow.json 中的模式标记
218
218
  3. 输出升级成功确认
219
219
 
220
220
  ## 步骤 5: 验证结果
@@ -0,0 +1,59 @@
1
+ {
2
+ "currentWorkflow": null,
3
+ "currentStep": null,
4
+ "mode": "full",
5
+ "startTime": null,
6
+ "lastUpdate": "{{last_update}}",
7
+ "steps": [
8
+ {
9
+ "id": 1,
10
+ "name": "analyze-codeflow",
11
+ "status": "pending",
12
+ "output": null,
13
+ "completedAt": null
14
+ },
15
+ {
16
+ "id": 2,
17
+ "name": "collect-requirements",
18
+ "status": "pending",
19
+ "output": null,
20
+ "completedAt": null
21
+ },
22
+ {
23
+ "id": 3,
24
+ "name": "collect-dependencies",
25
+ "status": "pending",
26
+ "output": null,
27
+ "completedAt": null
28
+ },
29
+ {
30
+ "id": 4,
31
+ "name": "design-increment",
32
+ "status": "pending",
33
+ "output": null,
34
+ "completedAt": null
35
+ },
36
+ {
37
+ "id": 5,
38
+ "name": "apply-code",
39
+ "status": "pending",
40
+ "output": null,
41
+ "completedAt": null
42
+ },
43
+ {
44
+ "id": 6,
45
+ "name": "merge-baseline",
46
+ "status": "pending",
47
+ "output": null,
48
+ "completedAt": null
49
+ },
50
+ {
51
+ "id": 7,
52
+ "name": "archive-workflow",
53
+ "status": "pending",
54
+ "output": null,
55
+ "completedAt": null
56
+ }
57
+ ],
58
+ "history": []
59
+ }
@@ -1,31 +0,0 @@
1
- # Workflow Status
2
-
3
- **当前工作流**: -
4
- **当前步骤**: -
5
- **工作流模式**: full
6
- **开始时间**: -
7
- **最后更新**: {{last_update}}
8
-
9
- <!--
10
- 工作流模式说明:
11
- - full: 完整模式 (7步) - 适合复杂功能开发
12
- - quick: 快速模式 (5步) - 跳过步骤3,4,适合Bug修复和简单功能
13
- - minimal: 极简模式 (3步) - 跳过步骤2,3,4,6,适合紧急修复或单文件小改动
14
- -->
15
-
16
- ## 步骤进度
17
-
18
- | 步骤 | 状态 | 输出文件 | 完成时间 |
19
- |------|------|---------|---------|
20
- | 1. analyze-codeflow | pending | - | - |
21
- | 2. collect-requirements | pending | - | - |
22
- | 3. collect-dependencies | pending | - | - |
23
- | 4. design-increment | pending | - | - |
24
- | 5. apply-code | pending | - | - |
25
- | 6. merge-baseline | pending | - | - |
26
- | 7. archive-workflow | pending | - | - |
27
-
28
- ## 工作流历史
29
-
30
- | 工作流 | 状态 | 开始时间 | 完成时间 |
31
- |--------|------|---------|---------|