@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 +1 -1
- package/package.json +1 -1
- package/src/commands/collect-req.mjs +10 -0
- package/src/commands/init.mjs +1 -1
- package/src/commands/merge.mjs +11 -0
- package/src/commands/update.mjs +7 -7
- package/src/lib/config.mjs +2 -1
- package/src/lib/ide-sync.mjs +2 -2
- package/src/lib/workflow.mjs +147 -141
- package/src/templates/AGENTS.md +3 -1
- package/src/templates/commands/inc-archive.md +2 -3
- package/src/templates/commands/inc-upgrade.md +5 -5
- package/src/templates/workflow.json +59 -0
- package/src/templates/WORKFLOW.md +0 -31
package/README.md
CHANGED
|
@@ -511,7 +511,7 @@ your-project/
|
|
|
511
511
|
│ └── templates/ # Markdown 模板文件
|
|
512
512
|
├── incspec/
|
|
513
513
|
│ ├── project.md # 项目配置
|
|
514
|
-
│ ├──
|
|
514
|
+
│ ├── workflow.json # 当前工作流状态
|
|
515
515
|
│ ├── AGENTS.md # incspec 使用指南
|
|
516
516
|
│ ├── baselines/ # 基线快照
|
|
517
517
|
│ │ └── home-baseline-v1.md
|
package/package.json
CHANGED
|
@@ -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(', ')} 后再继续。`);
|
package/src/commands/init.mjs
CHANGED
|
@@ -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(` ├──
|
|
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));
|
package/src/commands/merge.mjs
CHANGED
|
@@ -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) {
|
package/src/commands/update.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* update command - Update template files to latest version
|
|
3
|
-
* Updates 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/
|
|
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(), '
|
|
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
|
|
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/
|
|
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/
|
|
131
|
+
// Update incspec/workflow.json
|
|
132
132
|
const workflowResult = updateIncspecWorkflow(projectRoot);
|
|
133
133
|
results.push({
|
|
134
|
-
file: 'incspec/
|
|
134
|
+
file: 'incspec/workflow.json',
|
|
135
135
|
...workflowResult,
|
|
136
136
|
});
|
|
137
137
|
|
package/src/lib/config.mjs
CHANGED
package/src/lib/ide-sync.mjs
CHANGED
|
@@ -136,7 +136,7 @@ incspec status
|
|
|
136
136
|
或直接读取状态文件:
|
|
137
137
|
|
|
138
138
|
\`\`\`bash
|
|
139
|
-
cat ${INCSPEC_DIR}/
|
|
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
|
-
├──
|
|
190
|
+
├── workflow.json # 工作流状态
|
|
191
191
|
├── baselines/ # 基线快照
|
|
192
192
|
├── requirements/ # 需求文档
|
|
193
193
|
├── increments/ # 增量设计
|
package/src/lib/workflow.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workflow state management for incspec
|
|
3
|
-
* - Read/write
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
231
|
-
return parseWorkflow(normalized.content);
|
|
262
|
+
return parseWorkflow(content);
|
|
232
263
|
}
|
|
233
264
|
|
|
234
265
|
/**
|
|
235
|
-
* Generate
|
|
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
|
-
|
|
244
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
package/src/templates/AGENTS.md
CHANGED
|
@@ -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
|
-
├──
|
|
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/
|
|
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. **检查历史**: 归档后检查
|
|
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
|
-
或直接读取
|
|
70
|
+
或直接读取 workflow.json:
|
|
71
71
|
|
|
72
72
|
```bash
|
|
73
|
-
cat incspec/
|
|
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
|
-
读取
|
|
115
|
+
读取 workflow.json 检查哪些步骤已完成:
|
|
116
116
|
|
|
117
117
|
```bash
|
|
118
|
-
cat incspec/
|
|
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. 更新
|
|
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
|
-
|--------|------|---------|---------|
|