@localsummer/incspec 0.0.1
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/LICENSE +15 -0
- package/README.md +540 -0
- package/commands/analyze.mjs +133 -0
- package/commands/apply.mjs +111 -0
- package/commands/archive.mjs +340 -0
- package/commands/collect-dep.mjs +85 -0
- package/commands/collect-req.mjs +80 -0
- package/commands/cursor-sync.mjs +116 -0
- package/commands/design.mjs +131 -0
- package/commands/help.mjs +235 -0
- package/commands/init.mjs +127 -0
- package/commands/list.mjs +111 -0
- package/commands/merge.mjs +112 -0
- package/commands/status.mjs +117 -0
- package/commands/update.mjs +189 -0
- package/commands/validate.mjs +181 -0
- package/index.mjs +236 -0
- package/lib/agents.mjs +163 -0
- package/lib/config.mjs +343 -0
- package/lib/cursor.mjs +307 -0
- package/lib/spec.mjs +300 -0
- package/lib/terminal.mjs +292 -0
- package/lib/workflow.mjs +563 -0
- package/package.json +40 -0
- package/templates/AGENTS.md +610 -0
- package/templates/INCSPEC_BLOCK.md +19 -0
- package/templates/WORKFLOW.md +22 -0
- package/templates/cursor-commands/analyze-codeflow.md +341 -0
- package/templates/cursor-commands/analyze-increment-codeflow.md +246 -0
- package/templates/cursor-commands/apply-increment-code.md +392 -0
- package/templates/cursor-commands/inc-archive.md +278 -0
- package/templates/cursor-commands/merge-to-baseline.md +329 -0
- package/templates/cursor-commands/structured-requirements-collection.md +123 -0
- package/templates/cursor-commands/ui-dependency-collection.md +143 -0
- package/templates/project.md +24 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* list command - List spec files
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import {
|
|
7
|
+
ensureInitialized,
|
|
8
|
+
INCSPEC_DIR,
|
|
9
|
+
DIRS,
|
|
10
|
+
} from '../lib/config.mjs';
|
|
11
|
+
import { listSpecs, getSpecInfo } from '../lib/spec.mjs';
|
|
12
|
+
import {
|
|
13
|
+
colors,
|
|
14
|
+
colorize,
|
|
15
|
+
print,
|
|
16
|
+
printTable,
|
|
17
|
+
printWarning,
|
|
18
|
+
} from '../lib/terminal.mjs';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Execute list command
|
|
22
|
+
* @param {Object} ctx - Command context
|
|
23
|
+
*/
|
|
24
|
+
export async function listCommand(ctx) {
|
|
25
|
+
const { cwd, args, options } = ctx;
|
|
26
|
+
|
|
27
|
+
// Ensure initialized
|
|
28
|
+
const projectRoot = ensureInitialized(cwd);
|
|
29
|
+
|
|
30
|
+
// Get type filter
|
|
31
|
+
const type = args[0]; // baselines | requirements | increments | archives
|
|
32
|
+
|
|
33
|
+
print('');
|
|
34
|
+
print(colorize(' incspec 规范文件列表', colors.bold, colors.cyan));
|
|
35
|
+
print(colorize(' ───────────────────', colors.dim));
|
|
36
|
+
print('');
|
|
37
|
+
|
|
38
|
+
const types = type ? [type] : ['baselines', 'requirements', 'increments'];
|
|
39
|
+
|
|
40
|
+
for (const t of types) {
|
|
41
|
+
const specs = listSpecs(projectRoot, t);
|
|
42
|
+
|
|
43
|
+
print(colorize(`${DIRS[t] || t}/`, colors.bold, colors.yellow));
|
|
44
|
+
|
|
45
|
+
if (specs.length === 0) {
|
|
46
|
+
print(colorize(' (空)', colors.dim));
|
|
47
|
+
} else {
|
|
48
|
+
specs.forEach(spec => {
|
|
49
|
+
const info = getSpecInfo(spec.path);
|
|
50
|
+
const mtime = spec.mtime.toISOString().replace('T', ' ').slice(0, 16);
|
|
51
|
+
const versionStr = info.version ? colorize(`v${info.version}`, colors.cyan) : '';
|
|
52
|
+
|
|
53
|
+
print(` ${colorize(spec.name, colors.white)} ${versionStr}`);
|
|
54
|
+
if (options.long || options.l) {
|
|
55
|
+
print(colorize(` 修改时间: ${mtime}`, colors.dim));
|
|
56
|
+
print(colorize(` 路径: ${spec.path}`, colors.dim));
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
print('');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Show archives if requested
|
|
64
|
+
if (type === 'archives' || options.all || options.a) {
|
|
65
|
+
const archivePath = path.join(projectRoot, INCSPEC_DIR, DIRS.archives);
|
|
66
|
+
print(colorize(`${DIRS.archives}/`, colors.bold, colors.yellow));
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const fs = await import('fs');
|
|
70
|
+
if (fs.existsSync(archivePath)) {
|
|
71
|
+
const months = fs.readdirSync(archivePath).filter(f =>
|
|
72
|
+
fs.statSync(path.join(archivePath, f)).isDirectory()
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (months.length === 0) {
|
|
76
|
+
print(colorize(' (空)', colors.dim));
|
|
77
|
+
} else {
|
|
78
|
+
months.sort().reverse().forEach(month => {
|
|
79
|
+
print(colorize(` ${month}/`, colors.cyan));
|
|
80
|
+
const monthPath = path.join(archivePath, month);
|
|
81
|
+
const entries = fs.readdirSync(monthPath, { withFileTypes: true });
|
|
82
|
+
|
|
83
|
+
// Collect direct .md files (legacy) and module subdirectories
|
|
84
|
+
const directFiles = entries.filter(e => e.isFile() && e.name.endsWith('.md'));
|
|
85
|
+
const moduleDirs = entries.filter(e => e.isDirectory());
|
|
86
|
+
|
|
87
|
+
// Show direct files (legacy structure)
|
|
88
|
+
directFiles.forEach(f => {
|
|
89
|
+
print(colorize(` ${f.name}`, colors.dim));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Show module subdirectories and their files
|
|
93
|
+
moduleDirs.forEach(moduleDir => {
|
|
94
|
+
print(colorize(` ${moduleDir.name}/`, colors.white));
|
|
95
|
+
const modulePath = path.join(monthPath, moduleDir.name);
|
|
96
|
+
const moduleFiles = fs.readdirSync(modulePath).filter(f => f.endsWith('.md'));
|
|
97
|
+
moduleFiles.forEach(f => {
|
|
98
|
+
print(colorize(` ${f}`, colors.dim));
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
print(colorize(' (空)', colors.dim));
|
|
105
|
+
}
|
|
106
|
+
} catch (e) {
|
|
107
|
+
print(colorize(' (空)', colors.dim));
|
|
108
|
+
}
|
|
109
|
+
print('');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* merge command - Step 6: Merge to baseline
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import {
|
|
7
|
+
ensureInitialized,
|
|
8
|
+
INCSPEC_DIR,
|
|
9
|
+
DIRS,
|
|
10
|
+
} from '../lib/config.mjs';
|
|
11
|
+
import {
|
|
12
|
+
readWorkflow,
|
|
13
|
+
updateStep,
|
|
14
|
+
STATUS,
|
|
15
|
+
} from '../lib/workflow.mjs';
|
|
16
|
+
import { listSpecs, getNextVersion } from '../lib/spec.mjs';
|
|
17
|
+
import {
|
|
18
|
+
colors,
|
|
19
|
+
colorize,
|
|
20
|
+
print,
|
|
21
|
+
printSuccess,
|
|
22
|
+
printWarning,
|
|
23
|
+
printInfo,
|
|
24
|
+
} from '../lib/terminal.mjs';
|
|
25
|
+
|
|
26
|
+
const STEP_NUMBER = 6;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Execute merge command
|
|
30
|
+
* @param {Object} ctx - Command context
|
|
31
|
+
*/
|
|
32
|
+
export async function mergeCommand(ctx) {
|
|
33
|
+
const { cwd, args, options } = ctx;
|
|
34
|
+
|
|
35
|
+
// Ensure initialized
|
|
36
|
+
const projectRoot = ensureInitialized(cwd);
|
|
37
|
+
|
|
38
|
+
// Get workflow state
|
|
39
|
+
const workflow = readWorkflow(projectRoot);
|
|
40
|
+
|
|
41
|
+
if (!workflow?.currentWorkflow) {
|
|
42
|
+
printWarning('没有活跃的工作流。请先运行 incspec analyze 开始新工作流。');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Get increment file
|
|
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
|
+
}
|
|
64
|
+
|
|
65
|
+
// Calculate output file
|
|
66
|
+
const moduleName = workflow.currentWorkflow.replace(/^analyze-/, '');
|
|
67
|
+
const version = getNextVersion(projectRoot, 'baselines', moduleName);
|
|
68
|
+
const defaultOutputFile = `${moduleName}-baseline-v${version}.md`;
|
|
69
|
+
const outputOverride = typeof options.output === 'string' ? options.output : '';
|
|
70
|
+
const outputFile = outputOverride || defaultOutputFile;
|
|
71
|
+
const outputPath = path.join(INCSPEC_DIR, DIRS.baselines, outputFile);
|
|
72
|
+
|
|
73
|
+
print('');
|
|
74
|
+
print(colorize('步骤 6: 合并到基线', colors.bold, colors.cyan));
|
|
75
|
+
print(colorize('─────────────────', colors.dim));
|
|
76
|
+
print('');
|
|
77
|
+
print(colorize(`当前工作流: ${workflow.currentWorkflow}`, colors.dim));
|
|
78
|
+
print(colorize(`增量设计文件: ${incrementPath}`, colors.dim));
|
|
79
|
+
print(colorize(`输出基线文件: ${outputPath}`, colors.dim));
|
|
80
|
+
print('');
|
|
81
|
+
|
|
82
|
+
// Update workflow status
|
|
83
|
+
updateStep(projectRoot, STEP_NUMBER, STATUS.IN_PROGRESS);
|
|
84
|
+
|
|
85
|
+
print(colorize('使用说明:', colors.bold));
|
|
86
|
+
print('');
|
|
87
|
+
print(colorize('请在 Cursor 中运行以下命令:', colors.cyan));
|
|
88
|
+
print('');
|
|
89
|
+
print(colorize(` /incspec/inc-merge ${incrementPath}`, colors.bold, colors.white));
|
|
90
|
+
print('');
|
|
91
|
+
print(colorize('或使用 Claude Code 命令:', colors.cyan));
|
|
92
|
+
print('');
|
|
93
|
+
const outDir = path.join(projectRoot, INCSPEC_DIR, DIRS.baselines);
|
|
94
|
+
print(colorize(` /ai-increment:merge-to-baseline ${incrementPath} ${outDir}`, colors.bold, colors.white));
|
|
95
|
+
print('');
|
|
96
|
+
print(colorize('该命令将:', colors.dim));
|
|
97
|
+
print(colorize(' 1. 解析增量设计文件中的时序图和依赖图', colors.dim));
|
|
98
|
+
print(colorize(' 2. 清理增量标记 (🆕/✏️/❌)', colors.dim));
|
|
99
|
+
print(colorize(' 3. 重新编号为 S1-Sxx, D1-Dxx', colors.dim));
|
|
100
|
+
print(colorize(' 4. 生成新的基线快照', colors.dim));
|
|
101
|
+
print('');
|
|
102
|
+
print(colorize('新基线将作为下一轮增量开发的起点。', colors.dim));
|
|
103
|
+
print('');
|
|
104
|
+
printInfo(`完成后运行 'incspec status' 查看进度`);
|
|
105
|
+
print('');
|
|
106
|
+
|
|
107
|
+
// Handle --complete flag
|
|
108
|
+
if (options.complete) {
|
|
109
|
+
updateStep(projectRoot, STEP_NUMBER, STATUS.COMPLETED, outputFile);
|
|
110
|
+
printSuccess(`步骤 6 已标记为完成: ${outputFile}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* status command - Display workflow status
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
ensureInitialized,
|
|
7
|
+
readProjectConfig,
|
|
8
|
+
} from '../lib/config.mjs';
|
|
9
|
+
import {
|
|
10
|
+
readWorkflow,
|
|
11
|
+
STEPS,
|
|
12
|
+
STATUS,
|
|
13
|
+
} from '../lib/workflow.mjs';
|
|
14
|
+
import {
|
|
15
|
+
colors,
|
|
16
|
+
colorize,
|
|
17
|
+
print,
|
|
18
|
+
printHeader,
|
|
19
|
+
printStep,
|
|
20
|
+
printWarning,
|
|
21
|
+
} from '../lib/terminal.mjs';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Execute status command
|
|
25
|
+
* @param {Object} ctx - Command context
|
|
26
|
+
*/
|
|
27
|
+
export async function statusCommand(ctx) {
|
|
28
|
+
const { cwd } = ctx;
|
|
29
|
+
|
|
30
|
+
// Ensure initialized
|
|
31
|
+
const projectRoot = ensureInitialized(cwd);
|
|
32
|
+
|
|
33
|
+
// Read config and workflow
|
|
34
|
+
const config = readProjectConfig(projectRoot);
|
|
35
|
+
const workflow = readWorkflow(projectRoot);
|
|
36
|
+
|
|
37
|
+
print('');
|
|
38
|
+
print(colorize(' incspec 工作流状态', colors.bold, colors.cyan));
|
|
39
|
+
print(colorize(' ─────────────────', colors.dim));
|
|
40
|
+
print('');
|
|
41
|
+
|
|
42
|
+
// Project info
|
|
43
|
+
const techStack = Array.isArray(config?.tech_stack)
|
|
44
|
+
? config.tech_stack
|
|
45
|
+
: (config?.tech_stack ? [config.tech_stack] : []);
|
|
46
|
+
print(colorize(`项目: ${config.name}`, colors.bold));
|
|
47
|
+
print(colorize(`技术栈: ${techStack.join(', ') || '-'}`, colors.dim));
|
|
48
|
+
print('');
|
|
49
|
+
|
|
50
|
+
// Current workflow
|
|
51
|
+
if (workflow?.currentWorkflow) {
|
|
52
|
+
print(colorize(`当前工作流: `, colors.bold) + colorize(workflow.currentWorkflow, colors.cyan));
|
|
53
|
+
print(colorize(`开始时间: ${workflow.startTime || '-'}`, colors.dim));
|
|
54
|
+
print(colorize(`最后更新: ${workflow.lastUpdate || '-'}`, colors.dim));
|
|
55
|
+
print('');
|
|
56
|
+
|
|
57
|
+
// Steps progress
|
|
58
|
+
print(colorize('步骤进度:', colors.bold));
|
|
59
|
+
print('');
|
|
60
|
+
|
|
61
|
+
STEPS.forEach((step, index) => {
|
|
62
|
+
const stepData = workflow.steps[index] || {};
|
|
63
|
+
const status = stepData.status || STATUS.PENDING;
|
|
64
|
+
const isCurrent = workflow.currentStep === step.id;
|
|
65
|
+
|
|
66
|
+
// Determine display status
|
|
67
|
+
let displayStatus = status;
|
|
68
|
+
if (isCurrent && status === STATUS.PENDING) {
|
|
69
|
+
displayStatus = STATUS.IN_PROGRESS;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
printStep(step.id, step.label, displayStatus);
|
|
73
|
+
|
|
74
|
+
// Show output file if completed
|
|
75
|
+
if (stepData.output && status === STATUS.COMPLETED) {
|
|
76
|
+
print(colorize(` → ${stepData.output}`, colors.dim));
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
print('');
|
|
81
|
+
|
|
82
|
+
// Next step hint
|
|
83
|
+
const nextStepIndex = workflow.steps.findIndex(
|
|
84
|
+
step => (step?.status || STATUS.PENDING) !== STATUS.COMPLETED
|
|
85
|
+
);
|
|
86
|
+
if (nextStepIndex >= 0) {
|
|
87
|
+
const nextStep = STEPS[nextStepIndex];
|
|
88
|
+
print(colorize('下一步:', colors.bold));
|
|
89
|
+
print(colorize(` 运行 'incspec ${nextStep.command}' 或使用 /incspec/inc-${nextStep.command}`, colors.cyan));
|
|
90
|
+
} else {
|
|
91
|
+
print(colorize('当前工作流步骤已全部完成。', colors.dim));
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
printWarning('当前没有活跃的工作流');
|
|
95
|
+
print('');
|
|
96
|
+
print(colorize('开始新工作流:', colors.bold));
|
|
97
|
+
print(colorize(` 1. 运行 'incspec analyze <source-path>' 分析代码`, colors.dim));
|
|
98
|
+
print(colorize(` 2. 或使用 Cursor 命令 /incspec/inc-analyze`, colors.dim));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
print('');
|
|
102
|
+
|
|
103
|
+
// Recent history
|
|
104
|
+
if (workflow?.history && workflow.history.length > 0) {
|
|
105
|
+
print(colorize('最近的工作流:', colors.bold));
|
|
106
|
+
print('');
|
|
107
|
+
|
|
108
|
+
const recentHistory = workflow.history.slice(0, 5);
|
|
109
|
+
recentHistory.forEach(item => {
|
|
110
|
+
const statusIcon = item.status === 'completed'
|
|
111
|
+
? colorize('✓', colors.green)
|
|
112
|
+
: colorize('○', colors.dim);
|
|
113
|
+
print(` ${statusIcon} ${item.name} (${item.startTime || '-'})`);
|
|
114
|
+
});
|
|
115
|
+
print('');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* update command - Update template files to latest version
|
|
3
|
+
* Updates AGENTS.md, WORKFLOW.md in incspec/ and managed block in project AGENTS.md
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import {
|
|
9
|
+
ensureInitialized,
|
|
10
|
+
getIncspecDir,
|
|
11
|
+
getTemplatesDir,
|
|
12
|
+
FILES,
|
|
13
|
+
} from '../lib/config.mjs';
|
|
14
|
+
import { updateProjectAgentsFile, getProjectAgentsFilePath } from '../lib/agents.mjs';
|
|
15
|
+
import {
|
|
16
|
+
colors,
|
|
17
|
+
colorize,
|
|
18
|
+
print,
|
|
19
|
+
printSuccess,
|
|
20
|
+
printWarning,
|
|
21
|
+
printInfo,
|
|
22
|
+
confirm,
|
|
23
|
+
} from '../lib/terminal.mjs';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Update incspec/AGENTS.md from template
|
|
27
|
+
* @param {string} projectRoot
|
|
28
|
+
* @returns {{updated: boolean, path: string}}
|
|
29
|
+
*/
|
|
30
|
+
function updateIncspecAgents(projectRoot) {
|
|
31
|
+
const targetPath = path.join(getIncspecDir(projectRoot), FILES.agents);
|
|
32
|
+
const templatePath = path.join(getTemplatesDir(), 'AGENTS.md');
|
|
33
|
+
|
|
34
|
+
if (!fs.existsSync(templatePath)) {
|
|
35
|
+
return { updated: false, path: targetPath, error: '模板文件不存在' };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const templateContent = fs.readFileSync(templatePath, 'utf-8');
|
|
39
|
+
|
|
40
|
+
// Check if content is different
|
|
41
|
+
let currentContent = '';
|
|
42
|
+
if (fs.existsSync(targetPath)) {
|
|
43
|
+
currentContent = fs.readFileSync(targetPath, 'utf-8');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (currentContent === templateContent) {
|
|
47
|
+
return { updated: false, path: targetPath, reason: '内容相同' };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fs.writeFileSync(targetPath, templateContent, 'utf-8');
|
|
51
|
+
return { updated: true, path: targetPath };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Update incspec/WORKFLOW.md template structure while preserving user data
|
|
56
|
+
* @param {string} projectRoot
|
|
57
|
+
* @returns {{updated: boolean, path: string}}
|
|
58
|
+
*/
|
|
59
|
+
function updateIncspecWorkflow(projectRoot) {
|
|
60
|
+
const targetPath = path.join(getIncspecDir(projectRoot), FILES.workflow);
|
|
61
|
+
const templatePath = path.join(getTemplatesDir(), 'WORKFLOW.md');
|
|
62
|
+
|
|
63
|
+
if (!fs.existsSync(templatePath)) {
|
|
64
|
+
return { updated: false, path: targetPath, error: '模板文件不存在' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// For WORKFLOW.md, we only update if file doesn't exist
|
|
68
|
+
// Because it contains dynamic user data that we don't want to overwrite
|
|
69
|
+
// The template is only used for initial structure
|
|
70
|
+
if (fs.existsSync(targetPath)) {
|
|
71
|
+
return { updated: false, path: targetPath, reason: '保留用户工作流数据' };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const now = new Date().toISOString().replace('T', ' ').slice(0, 16);
|
|
75
|
+
let templateContent = fs.readFileSync(templatePath, 'utf-8');
|
|
76
|
+
templateContent = templateContent.replace(/\{\{last_update\}\}/g, now);
|
|
77
|
+
|
|
78
|
+
fs.writeFileSync(targetPath, templateContent, 'utf-8');
|
|
79
|
+
return { updated: true, path: targetPath };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Execute update command
|
|
84
|
+
* @param {Object} ctx - Command context
|
|
85
|
+
*/
|
|
86
|
+
export async function updateCommand(ctx) {
|
|
87
|
+
const { cwd, options } = ctx;
|
|
88
|
+
|
|
89
|
+
// Ensure project is initialized
|
|
90
|
+
let projectRoot;
|
|
91
|
+
try {
|
|
92
|
+
projectRoot = ensureInitialized(cwd);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
printWarning(error.message);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
print('');
|
|
99
|
+
print(colorize(' incspec 模板更新', colors.bold, colors.cyan));
|
|
100
|
+
print(colorize(' ────────────────', colors.dim));
|
|
101
|
+
print('');
|
|
102
|
+
|
|
103
|
+
// List what will be updated
|
|
104
|
+
print(colorize('将更新以下模板文件:', colors.bold));
|
|
105
|
+
print(colorize(' - incspec/AGENTS.md (incspec 使用指南)', colors.dim));
|
|
106
|
+
print(colorize(' - incspec/WORKFLOW.md (工作流模板,保留用户数据)', colors.dim));
|
|
107
|
+
print(colorize(' - AGENTS.md (项目根目录 incspec 指令块)', colors.dim));
|
|
108
|
+
print('');
|
|
109
|
+
|
|
110
|
+
// Confirm unless --yes flag
|
|
111
|
+
if (!options.yes && !options.y) {
|
|
112
|
+
const shouldProceed = await confirm('确认更新模板文件?');
|
|
113
|
+
if (!shouldProceed) {
|
|
114
|
+
print(colorize('已取消更新。', colors.dim));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
print('');
|
|
120
|
+
|
|
121
|
+
const results = [];
|
|
122
|
+
|
|
123
|
+
// Update incspec/AGENTS.md
|
|
124
|
+
const agentsResult = updateIncspecAgents(projectRoot);
|
|
125
|
+
results.push({
|
|
126
|
+
file: 'incspec/AGENTS.md',
|
|
127
|
+
...agentsResult,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Update incspec/WORKFLOW.md
|
|
131
|
+
const workflowResult = updateIncspecWorkflow(projectRoot);
|
|
132
|
+
results.push({
|
|
133
|
+
file: 'incspec/WORKFLOW.md',
|
|
134
|
+
...workflowResult,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Update project AGENTS.md (managed block)
|
|
138
|
+
const projectAgentsResult = updateProjectAgentsFile(projectRoot);
|
|
139
|
+
results.push({
|
|
140
|
+
file: 'AGENTS.md',
|
|
141
|
+
path: getProjectAgentsFilePath(projectRoot),
|
|
142
|
+
updated: projectAgentsResult.created || projectAgentsResult.updated,
|
|
143
|
+
created: projectAgentsResult.created,
|
|
144
|
+
blockUpdated: projectAgentsResult.updated,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Print results
|
|
148
|
+
print(colorize('更新结果:', colors.bold));
|
|
149
|
+
print('');
|
|
150
|
+
|
|
151
|
+
let updatedCount = 0;
|
|
152
|
+
let skippedCount = 0;
|
|
153
|
+
|
|
154
|
+
for (const result of results) {
|
|
155
|
+
if (result.error) {
|
|
156
|
+
print(` ${colorize('x', colors.red)} ${result.file}`);
|
|
157
|
+
print(colorize(` 错误: ${result.error}`, colors.red));
|
|
158
|
+
} else if (result.updated) {
|
|
159
|
+
updatedCount++;
|
|
160
|
+
if (result.created) {
|
|
161
|
+
print(` ${colorize('+', colors.green)} ${result.file} ${colorize('(新建)', colors.green)}`);
|
|
162
|
+
} else if (result.blockUpdated) {
|
|
163
|
+
print(` ${colorize('~', colors.yellow)} ${result.file} ${colorize('(更新指令块)', colors.yellow)}`);
|
|
164
|
+
} else {
|
|
165
|
+
print(` ${colorize('~', colors.yellow)} ${result.file} ${colorize('(已更新)', colors.yellow)}`);
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
skippedCount++;
|
|
169
|
+
const reasonText = result.reason ? ` (${result.reason})` : '';
|
|
170
|
+
print(` ${colorize('-', colors.dim)} ${result.file} ${colorize(`跳过${reasonText}`, colors.dim)}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
print('');
|
|
175
|
+
|
|
176
|
+
if (updatedCount > 0) {
|
|
177
|
+
printSuccess(`已更新 ${updatedCount} 个文件。`);
|
|
178
|
+
} else {
|
|
179
|
+
printInfo('所有模板文件已是最新版本。');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (skippedCount > 0) {
|
|
183
|
+
printInfo(`跳过 ${skippedCount} 个文件。`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
print('');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export default updateCommand;
|