@localsummer/incspec 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/apply.mjs +28 -0
- package/commands/collect-dep.mjs +14 -0
- package/commands/collect-req.mjs +8 -0
- package/commands/design.mjs +14 -0
- package/commands/help.mjs +15 -2
- package/commands/list.mjs +5 -1
- package/commands/merge.mjs +29 -0
- package/commands/validate.mjs +14 -4
- package/lib/cursor.mjs +0 -33
- package/lib/spec.mjs +13 -4
- package/lib/terminal.mjs +12 -0
- package/lib/workflow.mjs +48 -3
- package/package.json +1 -1
- package/templates/AGENTS.md +16 -11
- package/templates/inc-spec-skill/SKILL.md +3 -0
package/commands/apply.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
updateStep,
|
|
16
16
|
STATUS,
|
|
17
17
|
isQuickMode,
|
|
18
|
+
getMissingPrereqs,
|
|
18
19
|
} from '../lib/workflow.mjs';
|
|
19
20
|
import { listSpecs } from '../lib/spec.mjs';
|
|
20
21
|
import {
|
|
@@ -29,6 +30,20 @@ import {
|
|
|
29
30
|
|
|
30
31
|
const STEP_NUMBER = 5;
|
|
31
32
|
|
|
33
|
+
function resolveIncrementPath(projectRoot, candidate) {
|
|
34
|
+
if (!candidate || typeof candidate !== 'string') {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const possiblePaths = [
|
|
39
|
+
candidate,
|
|
40
|
+
path.join(projectRoot, candidate),
|
|
41
|
+
path.join(projectRoot, INCSPEC_DIR, DIRS.increments, candidate),
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
return possiblePaths.find(p => fs.existsSync(p)) || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
/**
|
|
33
48
|
* Execute apply command
|
|
34
49
|
* @param {Object} ctx - Command context
|
|
@@ -48,6 +63,12 @@ export async function applyCommand(ctx) {
|
|
|
48
63
|
}
|
|
49
64
|
|
|
50
65
|
const quickMode = isQuickMode(workflow);
|
|
66
|
+
const missingSteps = getMissingPrereqs(workflow, STEP_NUMBER);
|
|
67
|
+
if (missingSteps && missingSteps.length > 0 && !options.force) {
|
|
68
|
+
printWarning(`请先完成步骤 ${missingSteps.join(', ')} 后再继续。`);
|
|
69
|
+
printInfo('如需强制执行,请添加 --force。');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
51
72
|
|
|
52
73
|
// Get source directory
|
|
53
74
|
const config = readProjectConfig(projectRoot);
|
|
@@ -86,6 +107,13 @@ export async function applyCommand(ctx) {
|
|
|
86
107
|
printWarning('未找到增量设计文件。请先运行步骤 4 (design)。');
|
|
87
108
|
return;
|
|
88
109
|
}
|
|
110
|
+
} else {
|
|
111
|
+
const resolved = resolveIncrementPath(projectRoot, incrementPath);
|
|
112
|
+
if (!resolved) {
|
|
113
|
+
printWarning(`增量设计文件不存在: ${incrementPath}`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
incrementPath = resolved;
|
|
89
117
|
}
|
|
90
118
|
inputPath = incrementPath;
|
|
91
119
|
inputType = 'increment';
|
package/commands/collect-dep.mjs
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
readWorkflow,
|
|
13
13
|
updateStep,
|
|
14
14
|
STATUS,
|
|
15
|
+
isStepAllowed,
|
|
16
|
+
getMissingPrereqs,
|
|
15
17
|
} from '../lib/workflow.mjs';
|
|
16
18
|
import {
|
|
17
19
|
colors,
|
|
@@ -43,6 +45,18 @@ export async function collectDepCommand(ctx) {
|
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
if (!isStepAllowed(STEP_NUMBER, workflow.mode)) {
|
|
49
|
+
printWarning('当前工作流为快速模式,步骤 3 已跳过。');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const missingSteps = getMissingPrereqs(workflow, STEP_NUMBER);
|
|
54
|
+
if (missingSteps && missingSteps.length > 0 && !options.force) {
|
|
55
|
+
printWarning(`请先完成步骤 ${missingSteps.join(', ')} 后再继续。`);
|
|
56
|
+
printInfo('如需强制执行,请添加 --force。');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
46
60
|
const outputPath = path.join(INCSPEC_DIR, DIRS.requirements, OUTPUT_FILE);
|
|
47
61
|
|
|
48
62
|
print('');
|
package/commands/collect-req.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
updateStep,
|
|
14
14
|
STATUS,
|
|
15
15
|
isQuickMode,
|
|
16
|
+
getMissingPrereqs,
|
|
16
17
|
} from '../lib/workflow.mjs';
|
|
17
18
|
import {
|
|
18
19
|
colors,
|
|
@@ -44,6 +45,13 @@ export async function collectReqCommand(ctx) {
|
|
|
44
45
|
return;
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
const missingSteps = getMissingPrereqs(workflow, STEP_NUMBER);
|
|
49
|
+
if (missingSteps && missingSteps.length > 0 && !options.force) {
|
|
50
|
+
printWarning(`请先完成步骤 ${missingSteps.join(', ')} 后再继续。`);
|
|
51
|
+
printInfo('如需强制执行,请添加 --force。');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
47
55
|
const outputPath = path.join(INCSPEC_DIR, DIRS.requirements, OUTPUT_FILE);
|
|
48
56
|
|
|
49
57
|
print('');
|
package/commands/design.mjs
CHANGED
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
readWorkflow,
|
|
14
14
|
updateStep,
|
|
15
15
|
STATUS,
|
|
16
|
+
isStepAllowed,
|
|
17
|
+
getMissingPrereqs,
|
|
16
18
|
} from '../lib/workflow.mjs';
|
|
17
19
|
import { getNextVersion, listSpecs } from '../lib/spec.mjs';
|
|
18
20
|
import {
|
|
@@ -45,6 +47,18 @@ export async function designCommand(ctx) {
|
|
|
45
47
|
return;
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
if (!isStepAllowed(STEP_NUMBER, workflow.mode)) {
|
|
51
|
+
printWarning('当前工作流为快速模式,步骤 4 已跳过。');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const missingSteps = getMissingPrereqs(workflow, STEP_NUMBER);
|
|
56
|
+
if (missingSteps && missingSteps.length > 0 && !options.force) {
|
|
57
|
+
printWarning(`请先完成步骤 ${missingSteps.join(', ')} 后再继续。`);
|
|
58
|
+
printInfo('如需强制执行,请添加 --force。');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
48
62
|
// Get feature name
|
|
49
63
|
let featureName = typeof options.feature === 'string' ? options.feature : '';
|
|
50
64
|
if (!featureName) {
|
package/commands/help.mjs
CHANGED
|
@@ -52,6 +52,7 @@ const COMMANDS = {
|
|
|
52
52
|
description: '步骤2: 收集结构化需求',
|
|
53
53
|
options: [
|
|
54
54
|
['--complete', '标记步骤完成'],
|
|
55
|
+
['--force', '忽略前置步骤检查'],
|
|
55
56
|
],
|
|
56
57
|
},
|
|
57
58
|
'collect-dep': {
|
|
@@ -60,6 +61,7 @@ const COMMANDS = {
|
|
|
60
61
|
description: '步骤3: 采集 UI 依赖',
|
|
61
62
|
options: [
|
|
62
63
|
['--complete', '标记步骤完成'],
|
|
64
|
+
['--force', '忽略前置步骤检查'],
|
|
63
65
|
],
|
|
64
66
|
},
|
|
65
67
|
design: {
|
|
@@ -70,6 +72,7 @@ const COMMANDS = {
|
|
|
70
72
|
['-f, --feature=<name>', '指定功能名称'],
|
|
71
73
|
['--complete', '标记步骤完成'],
|
|
72
74
|
['-o, --output=<file>', '完成时指定输出文件'],
|
|
75
|
+
['--force', '忽略前置步骤检查'],
|
|
73
76
|
],
|
|
74
77
|
},
|
|
75
78
|
apply: {
|
|
@@ -79,6 +82,7 @@ const COMMANDS = {
|
|
|
79
82
|
options: [
|
|
80
83
|
['-s, --source-dir=<path>', '指定源代码目录'],
|
|
81
84
|
['--complete', '标记步骤完成'],
|
|
85
|
+
['--force', '忽略前置步骤检查'],
|
|
82
86
|
],
|
|
83
87
|
},
|
|
84
88
|
merge: {
|
|
@@ -88,6 +92,7 @@ const COMMANDS = {
|
|
|
88
92
|
options: [
|
|
89
93
|
['--complete', '标记步骤完成'],
|
|
90
94
|
['-o, --output=<file>', '完成时指定输出文件'],
|
|
95
|
+
['--force', '忽略前置步骤检查'],
|
|
91
96
|
],
|
|
92
97
|
},
|
|
93
98
|
list: {
|
|
@@ -145,18 +150,26 @@ const COMMANDS = {
|
|
|
145
150
|
},
|
|
146
151
|
};
|
|
147
152
|
|
|
153
|
+
const ALIAS_MAP = Object.entries(COMMANDS).reduce((map, [name, def]) => {
|
|
154
|
+
(def.aliases || []).forEach((alias) => {
|
|
155
|
+
map[alias] = name;
|
|
156
|
+
});
|
|
157
|
+
return map;
|
|
158
|
+
}, {});
|
|
159
|
+
|
|
148
160
|
/**
|
|
149
161
|
* Execute help command
|
|
150
162
|
* @param {Object} ctx - Command context
|
|
151
163
|
*/
|
|
152
164
|
export async function helpCommand(ctx = {}) {
|
|
153
165
|
const { command } = ctx;
|
|
166
|
+
const resolvedCommand = command && !COMMANDS[command] ? ALIAS_MAP[command] : command;
|
|
154
167
|
|
|
155
168
|
print('');
|
|
156
169
|
|
|
157
|
-
if (
|
|
170
|
+
if (resolvedCommand && COMMANDS[resolvedCommand]) {
|
|
158
171
|
// Show specific command help
|
|
159
|
-
showCommandHelp(
|
|
172
|
+
showCommandHelp(resolvedCommand);
|
|
160
173
|
} else if (command) {
|
|
161
174
|
print(colorize(`未知命令: ${command}`, colors.red));
|
|
162
175
|
print('');
|
package/commands/list.mjs
CHANGED
|
@@ -39,6 +39,9 @@ export async function listCommand(ctx) {
|
|
|
39
39
|
const types = type ? [type] : ['baselines', 'requirements', 'increments'];
|
|
40
40
|
|
|
41
41
|
for (const t of types) {
|
|
42
|
+
if (t === 'archives') {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
42
45
|
const specs = listSpecs(projectRoot, t);
|
|
43
46
|
|
|
44
47
|
print(colorize(`${DIRS[t] || t}/`, colors.bold, colors.yellow));
|
|
@@ -62,7 +65,8 @@ export async function listCommand(ctx) {
|
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
// Show archives if requested
|
|
65
|
-
|
|
68
|
+
const shouldShowArchives = type === 'archives' || options.all || options.a;
|
|
69
|
+
if (shouldShowArchives) {
|
|
66
70
|
const archivePath = path.join(projectRoot, INCSPEC_DIR, DIRS.archives);
|
|
67
71
|
print(colorize(`${DIRS.archives}/`, colors.bold, colors.yellow));
|
|
68
72
|
|
package/commands/merge.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* merge command - Step 6: Merge to baseline
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import * as fs from 'fs';
|
|
5
6
|
import * as path from 'path';
|
|
6
7
|
import {
|
|
7
8
|
ensureInitialized,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
updateStep,
|
|
14
15
|
STATUS,
|
|
15
16
|
isQuickMode,
|
|
17
|
+
getMissingPrereqs,
|
|
16
18
|
} from '../lib/workflow.mjs';
|
|
17
19
|
import { listSpecs, getNextVersion } from '../lib/spec.mjs';
|
|
18
20
|
import {
|
|
@@ -26,6 +28,20 @@ import {
|
|
|
26
28
|
|
|
27
29
|
const STEP_NUMBER = 6;
|
|
28
30
|
|
|
31
|
+
function resolveIncrementPath(projectRoot, candidate) {
|
|
32
|
+
if (!candidate || typeof candidate !== 'string') {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const possiblePaths = [
|
|
37
|
+
candidate,
|
|
38
|
+
path.join(projectRoot, candidate),
|
|
39
|
+
path.join(projectRoot, INCSPEC_DIR, DIRS.increments, candidate),
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
return possiblePaths.find(p => fs.existsSync(p)) || null;
|
|
43
|
+
}
|
|
44
|
+
|
|
29
45
|
/**
|
|
30
46
|
* Execute merge command
|
|
31
47
|
* @param {Object} ctx - Command context
|
|
@@ -45,6 +61,12 @@ export async function mergeCommand(ctx) {
|
|
|
45
61
|
}
|
|
46
62
|
|
|
47
63
|
const quickMode = isQuickMode(workflow);
|
|
64
|
+
const missingSteps = getMissingPrereqs(workflow, STEP_NUMBER);
|
|
65
|
+
if (missingSteps && missingSteps.length > 0 && !options.force) {
|
|
66
|
+
printWarning(`请先完成步骤 ${missingSteps.join(', ')} 后再继续。`);
|
|
67
|
+
printInfo('如需强制执行,请添加 --force。');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
48
70
|
|
|
49
71
|
// Calculate output file
|
|
50
72
|
const moduleName = workflow.currentWorkflow.replace(/^analyze-/, '');
|
|
@@ -73,6 +95,13 @@ export async function mergeCommand(ctx) {
|
|
|
73
95
|
printWarning('未找到增量设计文件。请先运行步骤 4 (design)。');
|
|
74
96
|
return;
|
|
75
97
|
}
|
|
98
|
+
} else {
|
|
99
|
+
const resolved = resolveIncrementPath(projectRoot, incrementPath);
|
|
100
|
+
if (!resolved) {
|
|
101
|
+
printWarning(`增量设计文件不存在: ${incrementPath}`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
incrementPath = resolved;
|
|
76
105
|
}
|
|
77
106
|
}
|
|
78
107
|
|
package/commands/validate.mjs
CHANGED
|
@@ -98,15 +98,25 @@ export async function validateCommand(ctx) {
|
|
|
98
98
|
if (!body.includes('## 1.') && !body.includes('# ')) {
|
|
99
99
|
warnings.push(`${spec.name}: 可能缺少标准章节结构`);
|
|
100
100
|
}
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
const hasSequenceDiagram = /sequenceDiagram/.test(body);
|
|
102
|
+
const hasGraph = /\bgraph\s+/.test(body);
|
|
103
|
+
if (!hasSequenceDiagram) {
|
|
104
|
+
warnings.push(`${spec.name}: 未检测到 Mermaid sequenceDiagram`);
|
|
105
|
+
}
|
|
106
|
+
if (!hasGraph) {
|
|
107
|
+
warnings.push(`${spec.name}: 未检测到 Mermaid graph`);
|
|
103
108
|
}
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
// Check for required sections in increments
|
|
107
112
|
if (type === 'increments') {
|
|
108
|
-
const requiredSections =
|
|
109
|
-
|
|
113
|
+
const requiredSections = Array.from({ length: 7 }, (_, i) => {
|
|
114
|
+
const num = i + 1;
|
|
115
|
+
return new RegExp(`^##\\s*(?:模块\\s*)?${num}(?:[.::\\s]|$)`, 'm');
|
|
116
|
+
});
|
|
117
|
+
const missingSections = requiredSections
|
|
118
|
+
.map((pattern, index) => (pattern.test(body) ? null : index + 1))
|
|
119
|
+
.filter(Boolean);
|
|
110
120
|
if (missingSections.length > 0) {
|
|
111
121
|
warnings.push(`${spec.name}: 可能缺少模块 ${missingSections.join(', ')}`);
|
|
112
122
|
}
|
package/lib/cursor.mjs
CHANGED
|
@@ -227,39 +227,6 @@ ${INCSPEC_DIR}/
|
|
|
227
227
|
`,
|
|
228
228
|
});
|
|
229
229
|
|
|
230
|
-
// Add archive command from template
|
|
231
|
-
const archiveSourcePath = getSourcePath('inc-archive.md');
|
|
232
|
-
if (archiveSourcePath) {
|
|
233
|
-
const archiveContent = fs.readFileSync(archiveSourcePath, 'utf-8');
|
|
234
|
-
commands.push({
|
|
235
|
-
name: 'inc-archive.md',
|
|
236
|
-
content: archiveContent,
|
|
237
|
-
});
|
|
238
|
-
} else {
|
|
239
|
-
commands.push({
|
|
240
|
-
name: 'inc-archive.md',
|
|
241
|
-
content: `---
|
|
242
|
-
description: [incspec] 归档规范文件到 archives 目录
|
|
243
|
-
---
|
|
244
|
-
|
|
245
|
-
# 归档规范文件
|
|
246
|
-
|
|
247
|
-
请运行以下命令归档规范文件:
|
|
248
|
-
|
|
249
|
-
\`\`\`bash
|
|
250
|
-
# 归档当前工作流全部产出文件(默认模式)
|
|
251
|
-
incspec archive --yes
|
|
252
|
-
|
|
253
|
-
# 归档指定文件(默认移动模式,删除原文件)
|
|
254
|
-
incspec archive <file-path> --yes
|
|
255
|
-
|
|
256
|
-
# 归档并保留原文件(复制模式)
|
|
257
|
-
incspec archive <file-path> --keep --yes
|
|
258
|
-
\`\`\`
|
|
259
|
-
`,
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
|
|
263
230
|
return commands;
|
|
264
231
|
}
|
|
265
232
|
|
package/lib/spec.mjs
CHANGED
|
@@ -13,6 +13,17 @@ function escapeRegExp(value) {
|
|
|
13
13
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function getVersionPattern(type, prefix) {
|
|
17
|
+
const safePrefix = escapeRegExp(prefix);
|
|
18
|
+
if (type === 'baselines') {
|
|
19
|
+
return new RegExp(`^${safePrefix}-baseline-v(\\d+)\\.md$`);
|
|
20
|
+
}
|
|
21
|
+
if (type === 'increments') {
|
|
22
|
+
return new RegExp(`^${safePrefix}-increment-v(\\d+)\\.md$`);
|
|
23
|
+
}
|
|
24
|
+
return new RegExp(`^${safePrefix}.*-v(\\d+)\\.md$`);
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
function ensureUniqueArchivePath(filePath) {
|
|
17
28
|
if (!fs.existsSync(filePath)) {
|
|
18
29
|
return filePath;
|
|
@@ -69,8 +80,7 @@ export function listSpecs(projectRoot, type) {
|
|
|
69
80
|
*/
|
|
70
81
|
export function getNextVersion(projectRoot, type, prefix) {
|
|
71
82
|
const specs = listSpecs(projectRoot, type);
|
|
72
|
-
const
|
|
73
|
-
const pattern = new RegExp(`^${safePrefix}.*-v(\\d+)\\.md$`);
|
|
83
|
+
const pattern = getVersionPattern(type, prefix);
|
|
74
84
|
|
|
75
85
|
const versions = specs
|
|
76
86
|
.map(s => {
|
|
@@ -91,8 +101,7 @@ export function getNextVersion(projectRoot, type, prefix) {
|
|
|
91
101
|
*/
|
|
92
102
|
export function getLatestSpec(projectRoot, type, prefix) {
|
|
93
103
|
const specs = listSpecs(projectRoot, type);
|
|
94
|
-
const
|
|
95
|
-
const pattern = new RegExp(`^${safePrefix}.*-v(\\d+)\\.md$`);
|
|
104
|
+
const pattern = getVersionPattern(type, prefix);
|
|
96
105
|
|
|
97
106
|
const versioned = specs
|
|
98
107
|
.map(s => {
|
package/lib/terminal.mjs
CHANGED
|
@@ -174,6 +174,12 @@ export async function prompt(message, defaultValue = '') {
|
|
|
174
174
|
* @returns {Promise<any>}
|
|
175
175
|
*/
|
|
176
176
|
export async function select({ message, choices }) {
|
|
177
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
178
|
+
const fallback = choices[0]?.value ?? null;
|
|
179
|
+
printWarning('当前环境不支持交互选择,已使用默认选项。');
|
|
180
|
+
return fallback;
|
|
181
|
+
}
|
|
182
|
+
|
|
177
183
|
return new Promise((resolve) => {
|
|
178
184
|
let cursor = 0;
|
|
179
185
|
|
|
@@ -249,6 +255,12 @@ export async function select({ message, choices }) {
|
|
|
249
255
|
* @returns {Promise<any[]>}
|
|
250
256
|
*/
|
|
251
257
|
export async function checkbox({ message, choices }) {
|
|
258
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
259
|
+
const selected = choices.filter(c => c.checked).map(c => c.value);
|
|
260
|
+
printWarning('当前环境不支持交互多选,已使用默认选项。');
|
|
261
|
+
return selected;
|
|
262
|
+
}
|
|
263
|
+
|
|
252
264
|
return new Promise((resolve) => {
|
|
253
265
|
let cursor = 0;
|
|
254
266
|
const selected = choices.map(c => c.checked || false);
|
package/lib/workflow.mjs
CHANGED
|
@@ -222,9 +222,6 @@ export function readWorkflow(projectRoot) {
|
|
|
222
222
|
|
|
223
223
|
const content = fs.readFileSync(workflowPath, 'utf-8');
|
|
224
224
|
const normalized = normalizeWorkflowContent(content);
|
|
225
|
-
if (normalized.updated) {
|
|
226
|
-
fs.writeFileSync(workflowPath, normalized.content, 'utf-8');
|
|
227
|
-
}
|
|
228
225
|
return parseWorkflow(normalized.content);
|
|
229
226
|
}
|
|
230
227
|
|
|
@@ -730,3 +727,51 @@ export function getNextStep(currentStep, mode) {
|
|
|
730
727
|
export function shouldSkipStep(stepNumber, mode) {
|
|
731
728
|
return mode === MODE.QUICK && QUICK_MODE_SKIPPED.includes(stepNumber);
|
|
732
729
|
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Check if a step is allowed in the current mode
|
|
733
|
+
* @param {number} stepNumber - Step number (1-based)
|
|
734
|
+
* @param {string} mode - Workflow mode
|
|
735
|
+
* @returns {boolean}
|
|
736
|
+
*/
|
|
737
|
+
export function isStepAllowed(stepNumber, mode = MODE.FULL) {
|
|
738
|
+
return !shouldSkipStep(stepNumber, mode);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Get prerequisite steps for a given step
|
|
743
|
+
* @param {number} stepNumber - Step number (1-based)
|
|
744
|
+
* @param {string} mode - Workflow mode
|
|
745
|
+
* @returns {number[]|null} Null if step is not allowed in the mode
|
|
746
|
+
*/
|
|
747
|
+
export function getPrerequisiteSteps(stepNumber, mode = MODE.FULL) {
|
|
748
|
+
if (stepNumber <= 1) {
|
|
749
|
+
return [];
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
if (mode === MODE.QUICK) {
|
|
753
|
+
if (!QUICK_MODE_STEPS.includes(stepNumber)) {
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
return QUICK_MODE_STEPS.filter(step => step < stepNumber);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return Array.from({ length: stepNumber - 1 }, (_, i) => i + 1);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Get missing prerequisite steps for a given step
|
|
764
|
+
* @param {Object} workflow
|
|
765
|
+
* @param {number} stepNumber - Step number (1-based)
|
|
766
|
+
* @returns {number[]|null} Null if step is not allowed in the mode
|
|
767
|
+
*/
|
|
768
|
+
export function getMissingPrereqs(workflow, stepNumber) {
|
|
769
|
+
const mode = workflow?.mode || MODE.FULL;
|
|
770
|
+
const requiredSteps = getPrerequisiteSteps(stepNumber, mode);
|
|
771
|
+
if (requiredSteps === null) {
|
|
772
|
+
return null;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const steps = workflow?.steps || [];
|
|
776
|
+
return requiredSteps.filter(step => steps[step - 1]?.status !== STATUS.COMPLETED);
|
|
777
|
+
}
|
package/package.json
CHANGED
package/templates/AGENTS.md
CHANGED
|
@@ -48,7 +48,7 @@ AI 编码助手使用 IncSpec 进行增量规格驱动开发的操作指南。
|
|
|
48
48
|
|
|
49
49
|
### 步骤 2: 收集结构化需求
|
|
50
50
|
|
|
51
|
-
**命令**: `incspec collect-req` (别名: `cr`)
|
|
51
|
+
**命令**: `incspec collect-req [--force]` (别名: `cr`)
|
|
52
52
|
|
|
53
53
|
**目的**: 交互式需求收集,转换为 5 列格式。
|
|
54
54
|
|
|
@@ -58,7 +58,7 @@ AI 编码助手使用 IncSpec 进行增量规格驱动开发的操作指南。
|
|
|
58
58
|
|
|
59
59
|
### 步骤 3: 收集 UI 依赖
|
|
60
60
|
|
|
61
|
-
**命令**: `incspec collect-dep` (别名: `cd`)
|
|
61
|
+
**命令**: `incspec collect-dep [--force]` (别名: `cd`)
|
|
62
62
|
|
|
63
63
|
**目的**: 映射新增/修改 UI 组件的所有上下文依赖。
|
|
64
64
|
|
|
@@ -68,7 +68,7 @@ AI 编码助手使用 IncSpec 进行增量规格驱动开发的操作指南。
|
|
|
68
68
|
|
|
69
69
|
### 步骤 4: 设计增量
|
|
70
70
|
|
|
71
|
-
**命令**: `incspec design [--feature=name]` (别名: `d`)
|
|
71
|
+
**命令**: `incspec design [--feature=name] [--force]` (别名: `d`)
|
|
72
72
|
|
|
73
73
|
**目的**: 创建全面的增量设计蓝图。
|
|
74
74
|
|
|
@@ -87,7 +87,7 @@ AI 编码助手使用 IncSpec 进行增量规格驱动开发的操作指南。
|
|
|
87
87
|
|
|
88
88
|
### 步骤 5: 应用代码变更
|
|
89
89
|
|
|
90
|
-
**命令**: `incspec apply [increment-path]` (别名: `ap`)
|
|
90
|
+
**命令**: `incspec apply [increment-path] [--force]` (别名: `ap`)
|
|
91
91
|
|
|
92
92
|
**目的**: 根据增量蓝图执行代码生成和修改。
|
|
93
93
|
|
|
@@ -97,7 +97,7 @@ AI 编码助手使用 IncSpec 进行增量规格驱动开发的操作指南。
|
|
|
97
97
|
|
|
98
98
|
### 步骤 6: 合并到基线
|
|
99
99
|
|
|
100
|
-
**命令**: `incspec merge [increment-path]` (别名: `m`)
|
|
100
|
+
**命令**: `incspec merge [increment-path] [--force]` (别名: `m`)
|
|
101
101
|
|
|
102
102
|
**目的**: 将增量整合到新的基线快照中。
|
|
103
103
|
|
|
@@ -145,11 +145,11 @@ incspec list / ls [-l] [-a] # 列出规格文件
|
|
|
145
145
|
|
|
146
146
|
# 7步工作流
|
|
147
147
|
incspec analyze <path> [--quick] [--module=name] [--baseline=file] # 步骤1
|
|
148
|
-
incspec collect-req / cr
|
|
149
|
-
incspec collect-dep / cd
|
|
150
|
-
incspec design / d [--feature=name] # 步骤4 (快速模式跳过)
|
|
151
|
-
incspec apply / ap [path]
|
|
152
|
-
incspec merge / m [path]
|
|
148
|
+
incspec collect-req / cr [--force] # 步骤2 (--force 跳过前置检查)
|
|
149
|
+
incspec collect-dep / cd [--force] # 步骤3 (快速模式跳过)
|
|
150
|
+
incspec design / d [--feature=name] [--force] # 步骤4 (快速模式跳过)
|
|
151
|
+
incspec apply / ap [path] [--force] # 步骤5
|
|
152
|
+
incspec merge / m [path] [--force] # 步骤6
|
|
153
153
|
incspec archive [--yes] [--keep] # 步骤7
|
|
154
154
|
|
|
155
155
|
# 验证与同步
|
|
@@ -223,10 +223,15 @@ baseline: home-baseline-v1
|
|
|
223
223
|
|------|----------|
|
|
224
224
|
| Workflow not initialized | 运行 `incspec init` |
|
|
225
225
|
| No baseline found | 先完成步骤 1 (analyze) |
|
|
226
|
-
| Previous step not completed | 运行 `incspec status`
|
|
226
|
+
| Previous step not completed | 运行 `incspec status` 检查进度,或添加 `--force` 跳过前置检查 |
|
|
227
227
|
| Validation failed | 检查文件格式和编号序列 |
|
|
228
228
|
| 工作流卡住或状态异常 | `incspec reset` 完全重置,或 `incspec reset --to=N` 回退到步骤N |
|
|
229
229
|
|
|
230
|
+
**前置步骤检查**:
|
|
231
|
+
- 步骤 2-6 执行前会自动检查前置步骤是否完成
|
|
232
|
+
- 若前置步骤未完成,命令会提示并阻止执行
|
|
233
|
+
- 添加 `--force` 可跳过此检查,强制执行当前步骤
|
|
234
|
+
|
|
230
235
|
**工作流重置**:
|
|
231
236
|
- 完全重置: `incspec reset` - 归档所有产出,回到初始状态
|
|
232
237
|
- 部分回退: `incspec reset --to=N` - 保留步骤1-N,重置N+1至7(示例:`--to=3` 保留1-3,重置4-7)
|
|
@@ -104,6 +104,8 @@ description: 设计优先的前端功能增量编码工作流。适用于实现
|
|
|
104
104
|
| 合并 | 完整/快速 | `incspec merge <report>` | `--complete --output=<file>` |
|
|
105
105
|
| 归档 | 完整/快速 | `incspec archive --yes` | - |
|
|
106
106
|
|
|
107
|
+
**前置步骤检查**: 步骤 2-6 执行前会自动检查前置步骤是否完成。若前置步骤未完成,命令会提示并阻止执行。添加 `--force` 可跳过此检查。
|
|
108
|
+
|
|
107
109
|
管理命令:
|
|
108
110
|
- `incspec status` - 查看工作流状态
|
|
109
111
|
- `incspec list` - 列出规范文件
|
|
@@ -273,6 +275,7 @@ incspec archive <file> --keep # 复制而非移动
|
|
|
273
275
|
| 跳过基线合并 | 对当前系统状态产生混淆 | 每次增量后必须执行步骤6 |
|
|
274
276
|
| 忽略归档 | 工作区杂乱,历史丢失 | 工作流完成后立即执行步骤7 |
|
|
275
277
|
| 工作流状态不一致 | CLI命令执行失败 | 使用 `incspec status` 检查状态,必要时使用 `incspec reset` 修复 |
|
|
278
|
+
| 前置步骤未完成 | 命令被阻止执行 | 按顺序完成前置步骤,或添加 `--force` 强制执行 |
|
|
276
279
|
|
|
277
280
|
## 参考资源
|
|
278
281
|
|