@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.
@@ -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;