@localsummer/incspec 0.0.6 → 0.0.8
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 +76 -15
- package/commands/analyze.mjs +28 -12
- package/commands/apply.mjs +78 -33
- package/commands/archive.mjs +25 -3
- package/commands/collect-dep.mjs +2 -2
- package/commands/collect-req.mjs +10 -2
- package/commands/design.mjs +2 -2
- package/commands/help.mjs +20 -11
- package/commands/list.mjs +2 -1
- package/commands/merge.mjs +64 -33
- package/commands/reset.mjs +166 -0
- package/commands/status.mjs +30 -7
- package/commands/sync.mjs +210 -0
- package/commands/update.mjs +2 -1
- package/index.mjs +13 -6
- package/lib/agents.mjs +1 -1
- package/lib/claude.mjs +144 -0
- package/lib/config.mjs +13 -10
- package/lib/cursor.mjs +20 -5
- package/lib/terminal.mjs +108 -0
- package/lib/workflow.mjs +123 -29
- package/package.json +1 -1
- package/templates/AGENTS.md +89 -36
- package/templates/INCSPEC_BLOCK.md +1 -1
- package/templates/WORKFLOW.md +1 -0
- package/templates/cursor-commands/analyze-codeflow.md +12 -1
- package/templates/cursor-commands/apply-increment-code.md +129 -1
- package/templates/cursor-commands/merge-to-baseline.md +87 -1
- package/templates/cursor-commands/structured-requirements-collection.md +6 -0
- package/templates/inc-spec-skill/SKILL.md +286 -0
- package/templates/inc-spec-skill/references/analyze-codeflow.md +368 -0
- package/templates/inc-spec-skill/references/analyze-increment-codeflow.md +246 -0
- package/templates/inc-spec-skill/references/apply-increment-code.md +520 -0
- package/templates/inc-spec-skill/references/inc-archive.md +278 -0
- package/templates/inc-spec-skill/references/merge-to-baseline.md +415 -0
- package/templates/inc-spec-skill/references/structured-requirements-collection.md +129 -0
- package/templates/inc-spec-skill/references/ui-dependency-collection.md +143 -0
- package/commands/cursor-sync.mjs +0 -116
package/commands/list.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
print,
|
|
16
16
|
printTable,
|
|
17
17
|
printWarning,
|
|
18
|
+
formatLocalDateTime,
|
|
18
19
|
} from '../lib/terminal.mjs';
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -47,7 +48,7 @@ export async function listCommand(ctx) {
|
|
|
47
48
|
} else {
|
|
48
49
|
specs.forEach(spec => {
|
|
49
50
|
const info = getSpecInfo(spec.path);
|
|
50
|
-
const mtime = spec.mtime
|
|
51
|
+
const mtime = formatLocalDateTime(spec.mtime);
|
|
51
52
|
const versionStr = info.version ? colorize(`v${info.version}`, colors.cyan) : '';
|
|
52
53
|
|
|
53
54
|
print(` ${colorize(spec.name, colors.white)} ${versionStr}`);
|
package/commands/merge.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
readWorkflow,
|
|
13
13
|
updateStep,
|
|
14
14
|
STATUS,
|
|
15
|
+
isQuickMode,
|
|
15
16
|
} from '../lib/workflow.mjs';
|
|
16
17
|
import { listSpecs, getNextVersion } from '../lib/spec.mjs';
|
|
17
18
|
import {
|
|
@@ -43,24 +44,7 @@ export async function mergeCommand(ctx) {
|
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
let incrementPath = args[0];
|
|
48
|
-
if (!incrementPath) {
|
|
49
|
-
const increments = listSpecs(projectRoot, 'increments');
|
|
50
|
-
if (increments.length > 0) {
|
|
51
|
-
const featureName = workflow.currentWorkflow.replace(/^analyze-/, '');
|
|
52
|
-
const matched = increments.find(spec => spec.name.startsWith(`${featureName}-increment-`));
|
|
53
|
-
if (matched) {
|
|
54
|
-
incrementPath = matched.path;
|
|
55
|
-
} else {
|
|
56
|
-
incrementPath = increments[0].path;
|
|
57
|
-
printWarning(`未找到与当前工作流匹配的增量文件,已使用最近文件: ${increments[0].name}`);
|
|
58
|
-
}
|
|
59
|
-
} else {
|
|
60
|
-
printWarning('未找到增量设计文件。请先运行步骤 4 (design)。');
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
47
|
+
const quickMode = isQuickMode(workflow);
|
|
64
48
|
|
|
65
49
|
// Calculate output file
|
|
66
50
|
const moduleName = workflow.currentWorkflow.replace(/^analyze-/, '');
|
|
@@ -70,12 +54,39 @@ export async function mergeCommand(ctx) {
|
|
|
70
54
|
const outputFile = outputOverride || defaultOutputFile;
|
|
71
55
|
const outputPath = path.join(INCSPEC_DIR, DIRS.baselines, outputFile);
|
|
72
56
|
|
|
57
|
+
// Get increment file (only needed for full mode)
|
|
58
|
+
let incrementPath = null;
|
|
59
|
+
if (!quickMode) {
|
|
60
|
+
incrementPath = args[0];
|
|
61
|
+
if (!incrementPath) {
|
|
62
|
+
const increments = listSpecs(projectRoot, 'increments');
|
|
63
|
+
if (increments.length > 0) {
|
|
64
|
+
const featureName = workflow.currentWorkflow.replace(/^analyze-/, '');
|
|
65
|
+
const matched = increments.find(spec => spec.name.startsWith(`${featureName}-increment-`));
|
|
66
|
+
if (matched) {
|
|
67
|
+
incrementPath = matched.path;
|
|
68
|
+
} else {
|
|
69
|
+
incrementPath = increments[0].path;
|
|
70
|
+
printWarning(`未找到与当前工作流匹配的增量文件,已使用最近文件: ${increments[0].name}`);
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
printWarning('未找到增量设计文件。请先运行步骤 4 (design)。');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
73
79
|
print('');
|
|
74
80
|
print(colorize('步骤 6: 合并到基线', colors.bold, colors.cyan));
|
|
81
|
+
if (quickMode) {
|
|
82
|
+
print(colorize('(快速模式 - 重新分析生成新基线)', colors.yellow));
|
|
83
|
+
}
|
|
75
84
|
print(colorize('─────────────────', colors.dim));
|
|
76
85
|
print('');
|
|
77
86
|
print(colorize(`当前工作流: ${workflow.currentWorkflow}`, colors.dim));
|
|
78
|
-
|
|
87
|
+
if (!quickMode) {
|
|
88
|
+
print(colorize(`增量设计文件: ${incrementPath}`, colors.dim));
|
|
89
|
+
}
|
|
79
90
|
print(colorize(`输出基线文件: ${outputPath}`, colors.dim));
|
|
80
91
|
print('');
|
|
81
92
|
|
|
@@ -84,20 +95,40 @@ export async function mergeCommand(ctx) {
|
|
|
84
95
|
|
|
85
96
|
print(colorize('使用说明:', colors.bold));
|
|
86
97
|
print('');
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
|
|
99
|
+
if (quickMode) {
|
|
100
|
+
// Quick mode instructions
|
|
101
|
+
print(colorize('快速模式下,将重新分析当前代码生成新基线:', colors.cyan));
|
|
102
|
+
print('');
|
|
103
|
+
print(colorize('在 Cursor 中:', colors.dim));
|
|
104
|
+
print(colorize(` /incspec/inc-merge --output=${outputFile}`, colors.bold, colors.white));
|
|
105
|
+
print('');
|
|
106
|
+
print(colorize('在 Claude Code 中:', colors.dim));
|
|
107
|
+
print(colorize(` 请分析当前代码状态,生成新的基线报告到 ${outputPath}`, colors.dim));
|
|
108
|
+
print('');
|
|
109
|
+
print(colorize('该命令将:', colors.dim));
|
|
110
|
+
print(colorize(' 1. 分析当前代码的完整流程', colors.dim));
|
|
111
|
+
print(colorize(' 2. 生成 API 调用时序图', colors.dim));
|
|
112
|
+
print(colorize(' 3. 生成依赖关系图', colors.dim));
|
|
113
|
+
print(colorize(' 4. 保存为新版本基线快照', colors.dim));
|
|
114
|
+
} else {
|
|
115
|
+
// Full mode instructions
|
|
116
|
+
print(colorize('请在 Cursor 中运行以下命令:', colors.cyan));
|
|
117
|
+
print('');
|
|
118
|
+
print(colorize(` /incspec/inc-merge ${incrementPath}`, colors.bold, colors.white));
|
|
119
|
+
print('');
|
|
120
|
+
print(colorize('或在 Claude Code 中使用 inc-spec-skill 技能:', colors.cyan));
|
|
121
|
+
print('');
|
|
122
|
+
const outDir = path.join(projectRoot, INCSPEC_DIR, DIRS.baselines);
|
|
123
|
+
print(colorize(` 请将 ${incrementPath} 的增量合并到基线 ${outDir}`, colors.dim));
|
|
124
|
+
print('');
|
|
125
|
+
print(colorize('该命令将:', colors.dim));
|
|
126
|
+
print(colorize(' 1. 解析增量设计文件中的时序图和依赖图', colors.dim));
|
|
127
|
+
print(colorize(' 2. 清理增量标记', colors.dim));
|
|
128
|
+
print(colorize(' 3. 重新编号为 S1-Sxx, D1-Dxx', colors.dim));
|
|
129
|
+
print(colorize(' 4. 生成新的基线快照', colors.dim));
|
|
130
|
+
}
|
|
131
|
+
|
|
101
132
|
print('');
|
|
102
133
|
print(colorize('新基线将作为下一轮增量开发的起点。', colors.dim));
|
|
103
134
|
print('');
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* reset command - Reset current workflow
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import {
|
|
8
|
+
ensureInitialized,
|
|
9
|
+
INCSPEC_DIR,
|
|
10
|
+
DIRS,
|
|
11
|
+
} from '../lib/config.mjs';
|
|
12
|
+
import {
|
|
13
|
+
readWorkflow,
|
|
14
|
+
archiveWorkflow,
|
|
15
|
+
STATUS,
|
|
16
|
+
} from '../lib/workflow.mjs';
|
|
17
|
+
import { archiveSpec } from '../lib/spec.mjs';
|
|
18
|
+
import {
|
|
19
|
+
colors,
|
|
20
|
+
colorize,
|
|
21
|
+
print,
|
|
22
|
+
printSuccess,
|
|
23
|
+
printInfo,
|
|
24
|
+
printWarning,
|
|
25
|
+
} from '../lib/terminal.mjs';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extract module name from workflow name
|
|
29
|
+
* e.g., "analyze-incspec-cli" -> "incspec-cli"
|
|
30
|
+
* @param {string} workflowName
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
function extractModuleName(workflowName) {
|
|
34
|
+
if (!workflowName) {
|
|
35
|
+
return 'unknown';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Remove common prefixes like "analyze-"
|
|
39
|
+
const prefixes = ['analyze-', 'feature-', 'fix-', 'refactor-'];
|
|
40
|
+
let moduleName = workflowName;
|
|
41
|
+
|
|
42
|
+
for (const prefix of prefixes) {
|
|
43
|
+
if (moduleName.startsWith(prefix)) {
|
|
44
|
+
moduleName = moduleName.slice(prefix.length);
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return moduleName || workflowName;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get output directory for a step index
|
|
54
|
+
* @param {number} stepIndex - 0-based step index
|
|
55
|
+
* @returns {string|null}
|
|
56
|
+
*/
|
|
57
|
+
function getOutputDirForStepIndex(stepIndex) {
|
|
58
|
+
// Step 1 (index 0) and Step 6 (index 5) -> baselines
|
|
59
|
+
if (stepIndex === 0 || stepIndex === 5) {
|
|
60
|
+
return DIRS.baselines;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Step 2-3 (index 1-2) -> requirements
|
|
64
|
+
if (stepIndex === 1 || stepIndex === 2) {
|
|
65
|
+
return DIRS.requirements;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Step 4 (index 3) -> increments
|
|
69
|
+
if (stepIndex === 3) {
|
|
70
|
+
return DIRS.increments;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Collect output files from workflow steps
|
|
78
|
+
* @param {string} projectRoot
|
|
79
|
+
* @param {Object} workflow
|
|
80
|
+
* @returns {Array<{path: string, name: string}>}
|
|
81
|
+
*/
|
|
82
|
+
function collectWorkflowOutputs(projectRoot, workflow) {
|
|
83
|
+
const outputs = [];
|
|
84
|
+
|
|
85
|
+
if (!workflow?.steps) {
|
|
86
|
+
return outputs;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
workflow.steps.forEach((step, index) => {
|
|
90
|
+
// Only collect completed steps with output
|
|
91
|
+
if (step.status !== STATUS.COMPLETED || !step.output || step.output === '-') {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const dir = getOutputDirForStepIndex(index);
|
|
96
|
+
if (!dir) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const outputPath = path.join(projectRoot, INCSPEC_DIR, dir, step.output);
|
|
101
|
+
if (fs.existsSync(outputPath)) {
|
|
102
|
+
outputs.push({
|
|
103
|
+
path: outputPath,
|
|
104
|
+
name: step.output,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return outputs;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Execute reset command
|
|
114
|
+
* @param {Object} ctx - Command context
|
|
115
|
+
*/
|
|
116
|
+
export async function resetCommand(ctx) {
|
|
117
|
+
const { cwd } = ctx;
|
|
118
|
+
|
|
119
|
+
// Ensure initialized
|
|
120
|
+
const projectRoot = ensureInitialized(cwd);
|
|
121
|
+
|
|
122
|
+
// Read current workflow
|
|
123
|
+
const workflow = readWorkflow(projectRoot);
|
|
124
|
+
|
|
125
|
+
if (!workflow?.currentWorkflow) {
|
|
126
|
+
print('');
|
|
127
|
+
printInfo('当前无活跃工作流,无需重置');
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const workflowName = workflow.currentWorkflow;
|
|
132
|
+
const moduleName = extractModuleName(workflowName);
|
|
133
|
+
|
|
134
|
+
print('');
|
|
135
|
+
print(colorize(' incspec 工作流重置', colors.bold, colors.cyan));
|
|
136
|
+
print(colorize(' ─────────────────', colors.dim));
|
|
137
|
+
print('');
|
|
138
|
+
print(colorize(`工作流: ${workflowName}`, colors.dim));
|
|
139
|
+
print('');
|
|
140
|
+
|
|
141
|
+
// Collect output files
|
|
142
|
+
const outputs = collectWorkflowOutputs(projectRoot, workflow);
|
|
143
|
+
|
|
144
|
+
// Archive output files
|
|
145
|
+
if (outputs.length > 0) {
|
|
146
|
+
print(colorize('归档产出文件:', colors.dim));
|
|
147
|
+
|
|
148
|
+
for (const output of outputs) {
|
|
149
|
+
try {
|
|
150
|
+
const archivePath = archiveSpec(projectRoot, output.path, true, moduleName);
|
|
151
|
+
print(colorize(` - ${output.name}`, colors.dim));
|
|
152
|
+
} catch (e) {
|
|
153
|
+
printWarning(`归档失败: ${output.name} - ${e.message}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
print('');
|
|
158
|
+
printSuccess(`已归档 ${outputs.length} 个产出文件到 archives/${new Date().toISOString().slice(0, 7)}/${moduleName}/`);
|
|
159
|
+
} else {
|
|
160
|
+
printInfo('无产出文件需要归档');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Reset workflow state
|
|
164
|
+
archiveWorkflow(projectRoot);
|
|
165
|
+
printSuccess('工作流已重置');
|
|
166
|
+
}
|
package/commands/status.mjs
CHANGED
|
@@ -10,6 +10,9 @@ import {
|
|
|
10
10
|
readWorkflow,
|
|
11
11
|
STEPS,
|
|
12
12
|
STATUS,
|
|
13
|
+
MODE,
|
|
14
|
+
QUICK_MODE_SKIPPED,
|
|
15
|
+
isQuickMode,
|
|
13
16
|
} from '../lib/workflow.mjs';
|
|
14
17
|
import {
|
|
15
18
|
colors,
|
|
@@ -49,7 +52,11 @@ export async function statusCommand(ctx) {
|
|
|
49
52
|
|
|
50
53
|
// Current workflow
|
|
51
54
|
if (workflow?.currentWorkflow) {
|
|
55
|
+
const quickMode = isQuickMode(workflow);
|
|
56
|
+
const modeLabel = quickMode ? '快速模式 (5步)' : '完整模式 (7步)';
|
|
57
|
+
|
|
52
58
|
print(colorize(`当前工作流: `, colors.bold) + colorize(workflow.currentWorkflow, colors.cyan));
|
|
59
|
+
print(colorize(`工作流模式: ${modeLabel}`, colors.dim));
|
|
53
60
|
print(colorize(`开始时间: ${workflow.startTime || '-'}`, colors.dim));
|
|
54
61
|
print(colorize(`最后更新: ${workflow.lastUpdate || '-'}`, colors.dim));
|
|
55
62
|
print('');
|
|
@@ -60,8 +67,14 @@ export async function statusCommand(ctx) {
|
|
|
60
67
|
|
|
61
68
|
STEPS.forEach((step, index) => {
|
|
62
69
|
const stepData = workflow.steps[index] || {};
|
|
63
|
-
|
|
70
|
+
let status = stepData.status || STATUS.PENDING;
|
|
64
71
|
const isCurrent = workflow.currentStep === step.id;
|
|
72
|
+
const isSkipped = quickMode && QUICK_MODE_SKIPPED.includes(step.id);
|
|
73
|
+
|
|
74
|
+
// Quick mode: mark skipped steps
|
|
75
|
+
if (isSkipped && status !== STATUS.SKIPPED) {
|
|
76
|
+
status = STATUS.SKIPPED;
|
|
77
|
+
}
|
|
65
78
|
|
|
66
79
|
// Determine display status
|
|
67
80
|
let displayStatus = status;
|
|
@@ -69,7 +82,12 @@ export async function statusCommand(ctx) {
|
|
|
69
82
|
displayStatus = STATUS.IN_PROGRESS;
|
|
70
83
|
}
|
|
71
84
|
|
|
72
|
-
|
|
85
|
+
// Use dimmed style for skipped steps
|
|
86
|
+
if (status === STATUS.SKIPPED) {
|
|
87
|
+
print(colorize(` [-] ${step.id}. ${step.label}`, colors.dim) + colorize(' (已跳过)', colors.dim));
|
|
88
|
+
} else {
|
|
89
|
+
printStep(step.id, step.label, displayStatus);
|
|
90
|
+
}
|
|
73
91
|
|
|
74
92
|
// Show output file if completed
|
|
75
93
|
if (stepData.output && status === STATUS.COMPLETED) {
|
|
@@ -80,9 +98,14 @@ export async function statusCommand(ctx) {
|
|
|
80
98
|
print('');
|
|
81
99
|
|
|
82
100
|
// Next step hint
|
|
83
|
-
const nextStepIndex = workflow.steps.findIndex(
|
|
84
|
-
|
|
85
|
-
|
|
101
|
+
const nextStepIndex = workflow.steps.findIndex((step, index) => {
|
|
102
|
+
const stepNumber = index + 1;
|
|
103
|
+
// Skip excluded steps in quick mode
|
|
104
|
+
if (quickMode && QUICK_MODE_SKIPPED.includes(stepNumber)) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return (step?.status || STATUS.PENDING) !== STATUS.COMPLETED;
|
|
108
|
+
});
|
|
86
109
|
if (nextStepIndex >= 0) {
|
|
87
110
|
const nextStep = STEPS[nextStepIndex];
|
|
88
111
|
print(colorize('下一步:', colors.bold));
|
|
@@ -107,8 +130,8 @@ export async function statusCommand(ctx) {
|
|
|
107
130
|
|
|
108
131
|
const recentHistory = workflow.history.slice(0, 5);
|
|
109
132
|
recentHistory.forEach(item => {
|
|
110
|
-
const statusIcon = item.status === 'completed'
|
|
111
|
-
? colorize('✓', colors.green)
|
|
133
|
+
const statusIcon = item.status === 'completed'
|
|
134
|
+
? colorize('✓', colors.green)
|
|
112
135
|
: colorize('○', colors.dim);
|
|
113
136
|
print(` ${statusIcon} ${item.name} (${item.startTime || '-'})`);
|
|
114
137
|
});
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sync command - Sync incspec integrations to IDEs/AI tools
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
syncToProject as syncCursorToProject,
|
|
7
|
+
syncToGlobal as syncCursorToGlobal,
|
|
8
|
+
} from '../lib/cursor.mjs';
|
|
9
|
+
import {
|
|
10
|
+
syncToProjectClaude,
|
|
11
|
+
syncToGlobalClaude,
|
|
12
|
+
} from '../lib/claude.mjs';
|
|
13
|
+
import {
|
|
14
|
+
colors,
|
|
15
|
+
colorize,
|
|
16
|
+
print,
|
|
17
|
+
printSuccess,
|
|
18
|
+
printWarning,
|
|
19
|
+
printInfo,
|
|
20
|
+
select,
|
|
21
|
+
checkbox,
|
|
22
|
+
} from '../lib/terminal.mjs';
|
|
23
|
+
|
|
24
|
+
/** Sync target definitions */
|
|
25
|
+
const SYNC_TARGETS = {
|
|
26
|
+
cursor: {
|
|
27
|
+
name: 'Cursor',
|
|
28
|
+
value: 'cursor',
|
|
29
|
+
},
|
|
30
|
+
claude: {
|
|
31
|
+
name: 'Claude Code',
|
|
32
|
+
value: 'claude',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Execute sync command
|
|
38
|
+
* @param {Object} ctx - Command context
|
|
39
|
+
*/
|
|
40
|
+
export async function syncCommand(ctx) {
|
|
41
|
+
const { cwd, options } = ctx;
|
|
42
|
+
|
|
43
|
+
print('');
|
|
44
|
+
print(colorize(' IncSpec 集成同步', colors.bold, colors.cyan));
|
|
45
|
+
print(colorize(' ────────────────', colors.dim));
|
|
46
|
+
print('');
|
|
47
|
+
|
|
48
|
+
// Determine sync targets
|
|
49
|
+
let targets = [];
|
|
50
|
+
|
|
51
|
+
// Command line args take priority
|
|
52
|
+
if (options.cursor) {
|
|
53
|
+
targets.push('cursor');
|
|
54
|
+
}
|
|
55
|
+
if (options.claude) {
|
|
56
|
+
targets.push('claude');
|
|
57
|
+
}
|
|
58
|
+
if (options.all) {
|
|
59
|
+
targets = ['cursor', 'claude'];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// If no args specified, use interactive checkbox
|
|
63
|
+
if (targets.length === 0) {
|
|
64
|
+
targets = await checkbox({
|
|
65
|
+
message: '选择要同步的目标:',
|
|
66
|
+
choices: [
|
|
67
|
+
{ ...SYNC_TARGETS.cursor, checked: true },
|
|
68
|
+
{ ...SYNC_TARGETS.claude, checked: false },
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (targets.length === 0) {
|
|
73
|
+
printWarning('未选择任何同步目标。');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
print(colorize(`已选择: ${targets.map(t => SYNC_TARGETS[t].name).join(', ')}`, colors.dim));
|
|
79
|
+
print('');
|
|
80
|
+
|
|
81
|
+
// Execute sync for each target
|
|
82
|
+
for (const target of targets) {
|
|
83
|
+
if (target === 'cursor') {
|
|
84
|
+
await syncCursor(ctx);
|
|
85
|
+
} else if (target === 'claude') {
|
|
86
|
+
await syncClaude(ctx);
|
|
87
|
+
}
|
|
88
|
+
print('');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
printInfo('同步完成。');
|
|
92
|
+
print('');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Sync Cursor commands
|
|
97
|
+
* @param {Object} ctx
|
|
98
|
+
*/
|
|
99
|
+
async function syncCursor(ctx) {
|
|
100
|
+
const { cwd, options } = ctx;
|
|
101
|
+
|
|
102
|
+
print(colorize('=== Cursor 命令同步 ===', colors.bold));
|
|
103
|
+
print('');
|
|
104
|
+
|
|
105
|
+
// Determine sync target
|
|
106
|
+
let syncTarget = null;
|
|
107
|
+
|
|
108
|
+
if (options.project) {
|
|
109
|
+
syncTarget = 'project';
|
|
110
|
+
} else if (options.global) {
|
|
111
|
+
syncTarget = 'global';
|
|
112
|
+
} else {
|
|
113
|
+
const choices = [
|
|
114
|
+
{
|
|
115
|
+
name: `当前目录 (${cwd}/.cursor/commands/incspec/)`,
|
|
116
|
+
value: 'project',
|
|
117
|
+
description: '仅对当前目录生效',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: '全局目录 (~/.cursor/commands/incspec/)',
|
|
121
|
+
value: 'global',
|
|
122
|
+
description: '对所有项目生效',
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
syncTarget = await select({
|
|
127
|
+
message: 'Cursor - 选择同步目标:',
|
|
128
|
+
choices,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Execute sync
|
|
133
|
+
if (syncTarget === 'project') {
|
|
134
|
+
const count = syncCursorToProject(cwd);
|
|
135
|
+
printSuccess(`Cursor: 已同步 ${count} 个命令到 .cursor/commands/incspec/`);
|
|
136
|
+
printCursorCommands();
|
|
137
|
+
} else if (syncTarget === 'global') {
|
|
138
|
+
const count = syncCursorToGlobal();
|
|
139
|
+
printSuccess(`Cursor: 已同步 ${count} 个命令到 ~/.cursor/commands/incspec/`);
|
|
140
|
+
printCursorCommands();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Print Cursor commands list
|
|
146
|
+
*/
|
|
147
|
+
function printCursorCommands() {
|
|
148
|
+
print('');
|
|
149
|
+
print(colorize('已创建的命令:', colors.bold));
|
|
150
|
+
print(colorize(' /incspec/inc-analyze 步骤1: 分析代码流程', colors.dim));
|
|
151
|
+
print(colorize(' /incspec/inc-collect-req 步骤2: 收集结构化需求', colors.dim));
|
|
152
|
+
print(colorize(' /incspec/inc-collect-dep 步骤3: UI依赖采集', colors.dim));
|
|
153
|
+
print(colorize(' /incspec/inc-design 步骤4: 增量设计', colors.dim));
|
|
154
|
+
print(colorize(' /incspec/inc-apply 步骤5: 应用代码变更', colors.dim));
|
|
155
|
+
print(colorize(' /incspec/inc-merge 步骤6: 合并到基线', colors.dim));
|
|
156
|
+
print(colorize(' /incspec/inc-archive 归档规范文件', colors.dim));
|
|
157
|
+
print(colorize(' /incspec/inc-status 查看工作流状态', colors.dim));
|
|
158
|
+
print(colorize(' /incspec/inc-help 显示帮助', colors.dim));
|
|
159
|
+
print('');
|
|
160
|
+
printInfo('请重启 Cursor 以加载新命令。');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Sync Claude Code skill
|
|
165
|
+
* @param {Object} ctx
|
|
166
|
+
*/
|
|
167
|
+
async function syncClaude(ctx) {
|
|
168
|
+
const { cwd, options } = ctx;
|
|
169
|
+
|
|
170
|
+
print(colorize('=== Claude Code Skill 同步 ===', colors.bold));
|
|
171
|
+
print('');
|
|
172
|
+
|
|
173
|
+
// Determine sync target
|
|
174
|
+
let syncTarget = null;
|
|
175
|
+
|
|
176
|
+
if (options.project) {
|
|
177
|
+
syncTarget = 'project';
|
|
178
|
+
} else if (options.global) {
|
|
179
|
+
syncTarget = 'global';
|
|
180
|
+
} else {
|
|
181
|
+
const choices = [
|
|
182
|
+
{
|
|
183
|
+
name: `当前目录 (${cwd}/.claude/skills/inc-spec-skill/)`,
|
|
184
|
+
value: 'project',
|
|
185
|
+
description: '仅对当前目录生效',
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: '全局目录 (~/.claude/skills/inc-spec-skill/)',
|
|
189
|
+
value: 'global',
|
|
190
|
+
description: '对所有项目生效(推荐)',
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
syncTarget = await select({
|
|
195
|
+
message: 'Claude Code - 选择同步目标:',
|
|
196
|
+
choices,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Execute sync
|
|
201
|
+
if (syncTarget === 'project') {
|
|
202
|
+
const { count } = syncToProjectClaude(cwd);
|
|
203
|
+
printSuccess(`Claude Code: 已同步 ${count} 个文件到 .claude/skills/inc-spec-skill/`);
|
|
204
|
+
print(colorize(' 包含: SKILL.md + references/', colors.dim));
|
|
205
|
+
} else if (syncTarget === 'global') {
|
|
206
|
+
const { count } = syncToGlobalClaude();
|
|
207
|
+
printSuccess(`Claude Code: 已同步 ${count} 个文件到 ~/.claude/skills/inc-spec-skill/`);
|
|
208
|
+
print(colorize(' 包含: SKILL.md + references/', colors.dim));
|
|
209
|
+
}
|
|
210
|
+
}
|
package/commands/update.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
printWarning,
|
|
21
21
|
printInfo,
|
|
22
22
|
confirm,
|
|
23
|
+
formatLocalDateTime,
|
|
23
24
|
} from '../lib/terminal.mjs';
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -71,7 +72,7 @@ function updateIncspecWorkflow(projectRoot) {
|
|
|
71
72
|
return { updated: false, path: targetPath, reason: '保留用户工作流数据' };
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
const now = new Date()
|
|
75
|
+
const now = formatLocalDateTime(new Date());
|
|
75
76
|
let templateContent = fs.readFileSync(templatePath, 'utf-8');
|
|
76
77
|
templateContent = templateContent.replace(/\{\{last_update\}\}/g, now);
|
|
77
78
|
|
package/index.mjs
CHANGED
|
@@ -17,7 +17,8 @@ import { mergeCommand } from './commands/merge.mjs';
|
|
|
17
17
|
import { listCommand } from './commands/list.mjs';
|
|
18
18
|
import { validateCommand } from './commands/validate.mjs';
|
|
19
19
|
import { archiveCommand } from './commands/archive.mjs';
|
|
20
|
-
import {
|
|
20
|
+
import { resetCommand } from './commands/reset.mjs';
|
|
21
|
+
import { syncCommand } from './commands/sync.mjs';
|
|
21
22
|
import { helpCommand } from './commands/help.mjs';
|
|
22
23
|
import { colors, colorize } from './lib/terminal.mjs';
|
|
23
24
|
|
|
@@ -156,7 +157,7 @@ async function main() {
|
|
|
156
157
|
await statusCommand(commandContext);
|
|
157
158
|
break;
|
|
158
159
|
|
|
159
|
-
// Workflow commands (Step 1-6)
|
|
160
|
+
// Workflow commands (Step 1-6, Step 7 archive is separate)
|
|
160
161
|
case 'analyze':
|
|
161
162
|
case 'a':
|
|
162
163
|
await analyzeCommand(commandContext);
|
|
@@ -203,10 +204,16 @@ async function main() {
|
|
|
203
204
|
await archiveCommand(commandContext);
|
|
204
205
|
break;
|
|
205
206
|
|
|
206
|
-
//
|
|
207
|
-
case '
|
|
208
|
-
case '
|
|
209
|
-
await
|
|
207
|
+
// Reset workflow
|
|
208
|
+
case 'reset':
|
|
209
|
+
case 'rs':
|
|
210
|
+
await resetCommand(commandContext);
|
|
211
|
+
break;
|
|
212
|
+
|
|
213
|
+
// Sync integrations
|
|
214
|
+
case 'sync':
|
|
215
|
+
case 's':
|
|
216
|
+
await syncCommand(commandContext);
|
|
210
217
|
break;
|
|
211
218
|
|
|
212
219
|
// Help
|