@localsummer/incspec 0.3.1 → 0.3.3

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.3",
4
4
  "description": "面向 AI 编程助手的增量规范驱动开发工具",
5
5
  "bin": {
6
6
  "incspec": "src/index.mjs"
package/src/CLAUDE.md ADDED
@@ -0,0 +1,7 @@
1
+ <claude-mem-context>
2
+ # Recent Activity
3
+
4
+ <!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
5
+
6
+ *No recent activity*
7
+ </claude-mem-context>
@@ -0,0 +1,14 @@
1
+ <claude-mem-context>
2
+ # Recent Activity
3
+
4
+ <!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
5
+
6
+ ### Jan 8, 2026
7
+
8
+ | ID | Time | T | Title | Read |
9
+ |----|------|---|-------|------|
10
+ | #118 | 3:14 PM | 🔵 | 纠正对 IncSpec 工作流模式参数的错误理解 | ~113 |
11
+ | #101 | 2:43 PM | 🔵 | 完成了 IncSpec 极简模式的深度分析 | ~95 |
12
+ | #100 | " | 🔵 | 发现 IncSpec 三种工作流模式的完整实现 | ~110 |
13
+ | #99 | " | 🔵 | 发现 IncSpec 极简模式的完整实现 | ~97 |
14
+ </claude-mem-context>
@@ -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(', ')} 后再继续。`);
@@ -13,6 +13,12 @@ import {
13
13
  } from '../lib/config.mjs';
14
14
  import { initWorkflow } from '../lib/workflow.mjs';
15
15
  import { updateProjectAgentsFile } from '../lib/agents.mjs';
16
+ import {
17
+ syncToProject,
18
+ syncToGlobal,
19
+ getSupportedIDEs,
20
+ getIDEConfig,
21
+ } from '../lib/ide-sync.mjs';
16
22
  import {
17
23
  colors,
18
24
  colorize,
@@ -22,8 +28,82 @@ import {
22
28
  printInfo,
23
29
  prompt,
24
30
  confirm,
31
+ select,
25
32
  } from '../lib/terminal.mjs';
26
33
 
34
+ /**
35
+ * Sync IDE commands helper
36
+ * @param {string} cwd - Current working directory
37
+ */
38
+ async function syncIDECommands(cwd) {
39
+ print('');
40
+ print(colorize('IDE 命令同步', colors.bold, colors.cyan));
41
+ print(colorize('─────────────', colors.dim));
42
+ print('');
43
+
44
+ // Ask which IDE to sync
45
+ const ideChoice = await select({
46
+ message: '选择要同步的 IDE',
47
+ choices: [
48
+ { name: 'Cursor', value: 'cursor' },
49
+ { name: 'Claude Code', value: 'claude' },
50
+ { name: '全部 (Cursor + Claude Code)', value: 'all' },
51
+ ]
52
+ });
53
+
54
+ // Ask for sync scope
55
+ const scopeChoice = await select({
56
+ message: '选择同步范围',
57
+ choices: [
58
+ { name: '项目级 (仅当前项目)', value: 'project' },
59
+ { name: '全局级 (所有项目)', value: 'global' },
60
+ ]
61
+ });
62
+
63
+ const isGlobal = scopeChoice === 'global';
64
+
65
+ // Determine which IDEs to sync
66
+ const idesToSync = ideChoice === 'all' ? getSupportedIDEs() : [ideChoice];
67
+ let syncCount = 0;
68
+
69
+ for (const ide of idesToSync) {
70
+ const config = getIDEConfig(ide);
71
+ try {
72
+ let count;
73
+ if (isGlobal) {
74
+ count = syncToGlobal(ide);
75
+ } else {
76
+ count = syncToProject(ide, cwd);
77
+ }
78
+
79
+ syncCount += count;
80
+ const targetDir = isGlobal ? config.globalDir : path.join(cwd, config.projectDir);
81
+ printInfo(`已同步 ${count} 个命令到 ${config.name}: ${targetDir}`);
82
+ } catch (error) {
83
+ printWarning(`同步 ${config.name} 命令失败: ${error.message}`);
84
+ }
85
+ }
86
+
87
+ print('');
88
+ printSuccess(`IDE 命令同步完成! 共同步 ${syncCount} 个命令文件`);
89
+ print('');
90
+ print(colorize('可用的 IDE 命令:', colors.bold));
91
+ print(colorize(` /incspec/inc-analyze - 步骤1: 分析代码流程`, colors.dim));
92
+ print(colorize(` /incspec/inc-collect-req - 步骤2: 收集结构化需求`, colors.dim));
93
+ print(colorize(` /incspec/inc-collect-dep - 步骤3: 采集UI依赖`, colors.dim));
94
+ print(colorize(` /incspec/inc-design - 步骤4: 增量设计`, colors.dim));
95
+ print(colorize(` /incspec/inc-apply - 步骤5: 应用代码变更`, colors.dim));
96
+ print(colorize(` /incspec/inc-merge - 步骤6: 合并到基线`, colors.dim));
97
+ print(colorize(` /incspec/inc-archive - 步骤7: 归档产出`, colors.dim));
98
+ print(colorize(` /incspec/inc-status - 查看工作流状态`, colors.dim));
99
+ print(colorize(` /incspec/inc-help - 显示帮助信息`, colors.dim));
100
+ print('');
101
+ print(colorize('下一步:', colors.bold));
102
+ print(colorize(` 1. 运行 'incspec status' 查看工作流状态`, colors.dim));
103
+ print(colorize(` 2. 使用 /incspec/inc-analyze 开始第一个工作流`, colors.dim));
104
+ print('');
105
+ }
106
+
27
107
  /**
28
108
  * Execute init command
29
109
  * @param {Object} ctx - Command context
@@ -109,7 +189,7 @@ export async function initCommand(ctx) {
109
189
  print(colorize('创建的目录结构:', colors.bold));
110
190
  print(colorize(` ${INCSPEC_DIR}/`, colors.cyan));
111
191
  print(colorize(` ├── project.md`, colors.dim));
112
- print(colorize(` ├── WORKFLOW.md`, colors.dim));
192
+ print(colorize(` ├── workflow.json`, colors.dim));
113
193
  print(colorize(` ├── AGENTS.md`, colors.dim));
114
194
  print(colorize(` ├── baselines/`, colors.dim));
115
195
  print(colorize(` ├── requirements/`, colors.dim));
@@ -119,9 +199,18 @@ export async function initCommand(ctx) {
119
199
  print(colorize(` AGENTS.md (项目根目录)`, colors.cyan));
120
200
  print(colorize(` └── incspec 指令块已添加`, colors.dim));
121
201
  print('');
122
- print(colorize('下一步:', colors.bold));
123
- print(colorize(` 1. 运行 'incspec status' 查看工作流状态`, colors.dim));
124
- print(colorize(` 2. 运行 'incspec sync' 同步 Cursor 命令`, colors.dim));
125
- print(colorize(` 3. 使用 /incspec/inc-analyze 开始第一个工作流`, colors.dim));
126
- print('');
202
+
203
+ // Ask user if they want to sync IDE commands
204
+ const shouldSync = await confirm('是否立即同步 IDE 命令?');
205
+
206
+ if (shouldSync) {
207
+ await syncIDECommands(cwd);
208
+ } else {
209
+ print('');
210
+ print(colorize('下一步:', colors.bold));
211
+ print(colorize(` 1. 运行 'incspec status' 查看工作流状态`, colors.dim));
212
+ print(colorize(` 2. 运行 'incspec sync' 同步 IDE 命令`, colors.dim));
213
+ print(colorize(` 3. 使用 /incspec/inc-analyze 开始第一个工作流`, colors.dim));
214
+ print('');
215
+ }
127
216
  }
@@ -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
 
@@ -0,0 +1,7 @@
1
+ <claude-mem-context>
2
+ # Recent Activity
3
+
4
+ <!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
5
+
6
+ *No recent activity*
7
+ </claude-mem-context>
@@ -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
 
@@ -9,6 +9,7 @@ import * as path from 'path';
9
9
  import * as os from 'os';
10
10
  import { fileURLToPath } from 'url';
11
11
  import { INCSPEC_DIR } from './config.mjs';
12
+ import { MODE_CONFIG, MODE_UPGRADE_ORDER } from './mode-utils.mjs';
12
13
 
13
14
  /** Built-in templates directory */
14
15
  const TEMPLATES_DIR = fileURLToPath(new URL('../templates/commands', import.meta.url));
@@ -114,35 +115,59 @@ function getSourcePath(fileName, fallbackDirs = []) {
114
115
  }
115
116
 
116
117
  /**
117
- * Generate utility commands content
118
- * @returns {Array<{name: string, content: string}>}
118
+ * Generate mode sections for help content
119
+ * Shows all 7 workflow steps with their mode indicators
120
+ * @returns {string}
119
121
  */
120
- function generateUtilityCommands() {
121
- return [
122
- {
123
- name: 'inc-status.md',
124
- content: `---
125
- description: [incspec] 查看当前工作流状态
126
- ---
122
+ function generateModeSections() {
123
+ const lines = [];
124
+
125
+ // Step info: command, description, and which modes support it
126
+ const steps = [
127
+ { cmd: 'inc-analyze', desc: '分析代码流程,生成基线快照', modes: ['full', 'quick', 'minimal'] },
128
+ { cmd: 'inc-collect-req', desc: '收集结构化需求', modes: ['full', 'quick'] },
129
+ { cmd: 'inc-collect-dep', desc: '采集UI依赖', modes: ['full'] },
130
+ { cmd: 'inc-design', desc: '生成增量设计蓝图', modes: ['full'] },
131
+ { cmd: 'inc-apply', desc: '应用代码变更', modes: ['full', 'quick', 'minimal'] },
132
+ { cmd: 'inc-merge', desc: '合并到新基线', modes: ['full', 'quick'] },
133
+ { cmd: 'inc-archive', desc: '归档工作流产出', modes: ['full', 'quick', 'minimal'] }
134
+ ];
127
135
 
128
- # 查看工作流状态
136
+ // Generate all 7 steps with mode indicators
137
+ for (let i = 0; i < steps.length; i++) {
138
+ const step = steps[i];
139
+ const stepNum = i + 1;
129
140
 
130
- 请运行以下命令查看当前工作流状态:
141
+ let cmdLine;
142
+ if (step.modes.length === 3) {
143
+ cmdLine = `\`/incspec/${step.cmd}\``;
144
+ } else if (step.modes.includes('full') && step.modes.includes('quick')) {
145
+ cmdLine = `\`/incspec/${step.cmd}\` (完整/快速)`;
146
+ } else {
147
+ cmdLine = `\`/incspec/${step.cmd}\` (完整)`;
148
+ }
131
149
 
132
- \`\`\`bash
133
- incspec status
134
- \`\`\`
150
+ lines.push(`${stepNum}. ${cmdLine} - ${step.desc}`);
151
+ }
135
152
 
136
- 或直接读取状态文件:
153
+ return lines.join('\n');
154
+ }
137
155
 
138
- \`\`\`bash
139
- cat ${INCSPEC_DIR}/WORKFLOW.md
140
- \`\`\`
141
- `,
142
- },
143
- {
144
- name: 'inc-help.md',
145
- content: `---
156
+ /**
157
+ * Generate utility commands content
158
+ * @returns {Array<{name: string, content: string}>}
159
+ */
160
+ function generateUtilityCommands() {
161
+ // Generate help content from template
162
+ const helpTemplatePath = path.join(TEMPLATES_DIR, 'inc-help.md');
163
+ let helpContent;
164
+
165
+ if (fs.existsSync(helpTemplatePath)) {
166
+ const template = fs.readFileSync(helpTemplatePath, 'utf-8');
167
+ helpContent = template.replace('<!-- MODE_SECTIONS -->', generateModeSections());
168
+ } else {
169
+ // Fallback: generate complete content inline
170
+ helpContent = `---
146
171
  description: [incspec] 显示帮助信息
147
172
  ---
148
173
 
@@ -150,22 +175,7 @@ description: [incspec] 显示帮助信息
150
175
 
151
176
  ## 工作流步骤
152
177
 
153
- **完整模式 (7步):**
154
- 1. \`/incspec/inc-analyze\` - 分析代码流程,生成基线快照
155
- 2. \`/incspec/inc-collect-req\` - 收集结构化需求
156
- 3. \`/incspec/inc-collect-dep\` - 采集UI依赖
157
- 4. \`/incspec/inc-design\` - 生成增量设计蓝图
158
- 5. \`/incspec/inc-apply\` - 应用代码变更
159
- 6. \`/incspec/inc-merge\` - 合并到新基线
160
- 7. \`/incspec/inc-archive\` - 归档工作流产出
161
-
162
- **快速模式 (5步):**
163
- 1. \`/incspec/inc-analyze --quick\` - 分析代码流程 (快速模式)
164
- 2. \`/incspec/inc-collect-req\` - 收集结构化需求
165
- 5. \`/incspec/inc-apply\` - 应用代码变更
166
- 6. \`/incspec/inc-merge\` - 合并到新基线
167
- 7. \`/incspec/inc-archive\` - 归档工作流产出
168
-
178
+ ${generateModeSections()}
169
179
  ## 辅助命令
170
180
 
171
181
  - \`/incspec/inc-status\` - 查看当前工作流状态
@@ -187,14 +197,41 @@ incspec help # 显示帮助
187
197
  \`\`\`
188
198
  ${INCSPEC_DIR}/
189
199
  ├── project.md # 项目配置
190
- ├── WORKFLOW.md # 工作流状态
200
+ ├── workflow.json # 工作流状态
191
201
  ├── baselines/ # 基线快照
192
202
  ├── requirements/ # 需求文档
193
203
  ├── increments/ # 增量设计
194
204
  └── archives/ # 历史归档 (YYYY-MM/{module}/)
195
205
  \`\`\`
206
+ `;
207
+ }
208
+
209
+ return [
210
+ {
211
+ name: 'inc-status.md',
212
+ content: `---
213
+ description: [incspec] 查看当前工作流状态
214
+ ---
215
+
216
+ # 查看工作流状态
217
+
218
+ 请运行以下命令查看当前工作流状态:
219
+
220
+ \`\`\`bash
221
+ incspec status
222
+ \`\`\`
223
+
224
+ 或直接读取状态文件:
225
+
226
+ \`\`\`bash
227
+ cat ${INCSPEC_DIR}/workflow.json
228
+ \`\`\`
196
229
  `,
197
230
  },
231
+ {
232
+ name: 'inc-help.md',
233
+ content: helpContent,
234
+ },
198
235
  ];
199
236
  }
200
237
 
@@ -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/ # 需求与依赖
@@ -0,0 +1,12 @@
1
+ <claude-mem-context>
2
+ # Recent Activity
3
+
4
+ <!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
5
+
6
+ ### Jan 8, 2026
7
+
8
+ | ID | Time | T | Title | Read |
9
+ |----|------|---|-------|------|
10
+ | #105 | 2:48 PM | 🔵 | 发现模板文件目录结构 | ~96 |
11
+ | #94 | 2:40 PM | 🔵 | 发现 IncSpec 模板文件结构 | ~62 |
12
+ </claude-mem-context>
@@ -0,0 +1,11 @@
1
+ <claude-mem-context>
2
+ # Recent Activity
3
+
4
+ <!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
5
+
6
+ ### Jan 8, 2026
7
+
8
+ | ID | Time | T | Title | Read |
9
+ |----|------|---|-------|------|
10
+ | #106 | 3:07 PM | 🔵 | 完成对 inc-help.md 写入机制的深度分析 | ~130 |
11
+ </claude-mem-context>
@@ -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
 
@@ -0,0 +1,37 @@
1
+ ---
2
+ description: [incspec] 显示帮助信息
3
+ ---
4
+
5
+ # incspec 帮助
6
+
7
+ ## 工作流步骤
8
+
9
+ <!-- MODE_SECTIONS -->
10
+
11
+ ## 辅助命令
12
+
13
+ - `/incspec/inc-status` - 查看当前工作流状态
14
+ - `/incspec/inc-help` - 显示帮助信息
15
+
16
+ ## CLI 命令
17
+
18
+ ```bash
19
+ incspec init # 初始化项目
20
+ incspec status # 查看工作流状态
21
+ incspec list # 列出规范文件
22
+ incspec validate # 验证规范完整性
23
+ incspec sync # 同步 IDE 命令
24
+ incspec help # 显示帮助
25
+ ```
26
+
27
+ ## 目录结构
28
+
29
+ ```
30
+ incspec/
31
+ ├── project.md # 项目配置
32
+ ├── workflow.json # 工作流状态
33
+ ├── baselines/ # 基线快照
34
+ ├── requirements/ # 需求文档
35
+ ├── increments/ # 增量设计
36
+ └── archives/ # 历史归档 (YYYY-MM/{module}/)
37
+ ```
@@ -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
- |--------|------|---------|---------|