@localsummer/incspec 0.0.5 → 0.0.7
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 +10 -6
- package/commands/analyze.mjs +2 -2
- package/commands/apply.mjs +2 -2
- package/commands/collect-dep.mjs +2 -2
- package/commands/collect-req.mjs +2 -2
- package/commands/design.mjs +2 -2
- package/commands/help.mjs +12 -9
- package/commands/list.mjs +2 -1
- package/commands/merge.mjs +2 -2
- package/commands/sync.mjs +210 -0
- package/commands/update.mjs +2 -1
- package/index.mjs +5 -5
- package/lib/claude.mjs +144 -0
- package/lib/config.mjs +6 -3
- package/lib/terminal.mjs +108 -0
- package/lib/workflow.mjs +8 -7
- package/package.json +1 -1
- package/templates/AGENTS.md +8 -5
- package/templates/cursor-commands/analyze-codeflow.md +13 -13
- package/templates/cursor-commands/analyze-increment-codeflow.md +4 -4
- package/templates/cursor-commands/merge-to-baseline.md +4 -4
- package/templates/inc-spec-skill/.security-scan-passed +4 -0
- package/templates/inc-spec-skill/SKILL.md +231 -0
- package/templates/inc-spec-skill/references/analyze-codeflow.md +357 -0
- package/templates/inc-spec-skill/references/analyze-increment-codeflow.md +246 -0
- package/templates/inc-spec-skill/references/apply-increment-code.md +392 -0
- package/templates/inc-spec-skill/references/inc-archive.md +278 -0
- package/templates/inc-spec-skill/references/merge-to-baseline.md +329 -0
- package/templates/inc-spec-skill/references/structured-requirements-collection.md +123 -0
- package/templates/inc-spec-skill/references/ui-dependency-collection.md +143 -0
- package/commands/cursor-sync.mjs +0 -116
package/README.md
CHANGED
|
@@ -100,12 +100,12 @@ AI 编程助手在处理复杂前端代码库时常常力不从心,因为 API
|
|
|
100
100
|
<details>
|
|
101
101
|
<summary><strong>原生斜杠命令</strong>(点击展开)</summary>
|
|
102
102
|
|
|
103
|
-
运行 `incspec
|
|
103
|
+
运行 `incspec sync` 后,这些工具可使用内置的 incspec 命令。
|
|
104
104
|
|
|
105
105
|
| 工具 | 命令 |
|
|
106
106
|
|------|------|
|
|
107
107
|
| **Cursor** | `/incspec/inc-analyze`、`/incspec/inc-collect-req`、`/incspec/inc-collect-dep`、`/incspec/inc-design`、`/incspec/inc-apply`、`/incspec/inc-merge`、`/incspec/inc-archive` |
|
|
108
|
-
| **Claude Code** | 使用
|
|
108
|
+
| **Claude Code** | 使用 `inc-spec-skill` Skill |
|
|
109
109
|
|
|
110
110
|
</details>
|
|
111
111
|
|
|
@@ -159,16 +159,20 @@ incspec init
|
|
|
159
159
|
- 生成包含工作流指令的 `AGENTS.md`
|
|
160
160
|
- 在 `incspec/project.md` 中设置项目配置
|
|
161
161
|
|
|
162
|
-
#### 步骤 3:同步
|
|
162
|
+
#### 步骤 3:同步 IDE 集成(可选)
|
|
163
163
|
|
|
164
|
-
|
|
164
|
+
同步到 Cursor 或 Claude Code:
|
|
165
165
|
```bash
|
|
166
|
-
incspec
|
|
166
|
+
incspec sync # 交互式选择
|
|
167
|
+
incspec sync --cursor # 仅 Cursor
|
|
168
|
+
incspec sync --claude # 仅 Claude Code
|
|
169
|
+
incspec sync --all # 全部
|
|
167
170
|
```
|
|
168
171
|
|
|
169
172
|
**设置完成后:**
|
|
170
173
|
- 运行 `incspec status` 验证设置
|
|
171
174
|
- Cursor 用户可直接触发 `/incspec/inc-*` 命令
|
|
175
|
+
- Claude Code 用户可使用 `inc-spec-skill` Skill
|
|
172
176
|
|
|
173
177
|
### 创建你的第一个增量
|
|
174
178
|
|
|
@@ -501,7 +505,7 @@ incspec archive -y # 跳过确认提示
|
|
|
501
505
|
| `status` | `st` | 状态 |
|
|
502
506
|
| `list` | `ls` | 列表 |
|
|
503
507
|
| `validate` | `v` | 验证 |
|
|
504
|
-
| `
|
|
508
|
+
| `sync` | `s` | 同步 |
|
|
505
509
|
| `update` | `up` | 更新 |
|
|
506
510
|
| `help` | `h` | 帮助 |
|
|
507
511
|
|
package/commands/analyze.mjs
CHANGED
|
@@ -207,9 +207,9 @@ export async function analyzeCommand(ctx) {
|
|
|
207
207
|
print('');
|
|
208
208
|
print(colorize(` /incspec/inc-analyze ${sourcePath} --module=${moduleName}`, colors.bold, colors.white));
|
|
209
209
|
print('');
|
|
210
|
-
print(colorize('
|
|
210
|
+
print(colorize('或在 Claude Code 中使用 inc-spec-skill 技能:', colors.cyan));
|
|
211
211
|
print('');
|
|
212
|
-
print(colorize(`
|
|
212
|
+
print(colorize(` 请分析 ${sourcePath} 的代码流程,生成基线报告到 ${path.join(projectRoot, INCSPEC_DIR, DIRS.baselines)}`, colors.dim));
|
|
213
213
|
print('');
|
|
214
214
|
printInfo(`完成后运行 'incspec status' 查看进度`);
|
|
215
215
|
print('');
|
package/commands/apply.mjs
CHANGED
|
@@ -88,9 +88,9 @@ export async function applyCommand(ctx) {
|
|
|
88
88
|
print('');
|
|
89
89
|
print(colorize(` /incspec/inc-apply ${incrementPath}`, colors.bold, colors.white));
|
|
90
90
|
print('');
|
|
91
|
-
print(colorize('
|
|
91
|
+
print(colorize('或在 Claude Code 中使用 inc-spec-skill 技能:', colors.cyan));
|
|
92
92
|
print('');
|
|
93
|
-
print(colorize(`
|
|
93
|
+
print(colorize(` 请按照 ${incrementPath} 的增量设计,应用代码变更到 ${path.join(projectRoot, sourceDir)}`, colors.dim));
|
|
94
94
|
print('');
|
|
95
95
|
print(colorize('该命令将:', colors.dim));
|
|
96
96
|
print(colorize(' 1. 解析增量设计文件中的变更计划', colors.dim));
|
package/commands/collect-dep.mjs
CHANGED
|
@@ -62,9 +62,9 @@ export async function collectDepCommand(ctx) {
|
|
|
62
62
|
print('');
|
|
63
63
|
print(colorize(` /incspec/inc-collect-dep`, colors.bold, colors.white));
|
|
64
64
|
print('');
|
|
65
|
-
print(colorize('
|
|
65
|
+
print(colorize('或在 Claude Code 中使用 inc-spec-skill 技能:', colors.cyan));
|
|
66
66
|
print('');
|
|
67
|
-
print(colorize(`
|
|
67
|
+
print(colorize(` 请收集 UI 依赖,生成依赖报告到 ${path.join(projectRoot, INCSPEC_DIR, DIRS.requirements)}`, colors.dim));
|
|
68
68
|
print('');
|
|
69
69
|
print(colorize('该命令将交互式采集 6 维度 UI 依赖:', colors.dim));
|
|
70
70
|
print(colorize(' - UI组件库 (Arco/Antd)', colors.dim));
|
package/commands/collect-req.mjs
CHANGED
|
@@ -62,9 +62,9 @@ export async function collectReqCommand(ctx) {
|
|
|
62
62
|
print('');
|
|
63
63
|
print(colorize(` /incspec/inc-collect-req`, colors.bold, colors.white));
|
|
64
64
|
print('');
|
|
65
|
-
print(colorize('
|
|
65
|
+
print(colorize('或在 Claude Code 中使用 inc-spec-skill 技能:', colors.cyan));
|
|
66
66
|
print('');
|
|
67
|
-
print(colorize(`
|
|
67
|
+
print(colorize(` 请收集需求,生成结构化需求表格到 ${path.join(projectRoot, INCSPEC_DIR, DIRS.requirements)}`, colors.dim));
|
|
68
68
|
print('');
|
|
69
69
|
print(colorize('该命令将交互式收集需求,生成 5 列结构化表格:', colors.dim));
|
|
70
70
|
print(colorize(' | 新增/修改功能 | 涉及UI组件 | 触发条件 | 影响的核心状态 | 预期数据流向 |', colors.dim));
|
package/commands/design.mjs
CHANGED
|
@@ -102,13 +102,13 @@ export async function designCommand(ctx) {
|
|
|
102
102
|
print('');
|
|
103
103
|
print(colorize(` /incspec/inc-design --feature=${featureName}`, colors.bold, colors.white));
|
|
104
104
|
print('');
|
|
105
|
-
print(colorize('
|
|
105
|
+
print(colorize('或在 Claude Code 中使用 inc-spec-skill 技能:', colors.cyan));
|
|
106
106
|
print('');
|
|
107
107
|
const baselinePath = path.join(projectRoot, INCSPEC_DIR, DIRS.baselines, latestBaseline);
|
|
108
108
|
const reqPath = path.join(projectRoot, INCSPEC_DIR, DIRS.requirements, 'structured-requirements.md');
|
|
109
109
|
const depPath = path.join(projectRoot, INCSPEC_DIR, DIRS.requirements, 'ui-dependencies.md');
|
|
110
110
|
const outDir = path.join(projectRoot, INCSPEC_DIR, DIRS.increments);
|
|
111
|
-
print(colorize(`
|
|
111
|
+
print(colorize(` 请基于基线 ${latestBaseline} 和需求/依赖文件,设计增量方案到 ${outDir}`, colors.dim));
|
|
112
112
|
print('');
|
|
113
113
|
print(colorize('该命令将生成包含 7 大模块的增量设计蓝图:', colors.dim));
|
|
114
114
|
print(colorize(' 1. 一句话摘要', colors.dim));
|
package/commands/help.mjs
CHANGED
|
@@ -117,12 +117,15 @@ const COMMANDS = {
|
|
|
117
117
|
['-y, --yes', '跳过确认提示'],
|
|
118
118
|
],
|
|
119
119
|
},
|
|
120
|
-
|
|
121
|
-
usage: 'incspec
|
|
122
|
-
aliases: ['
|
|
123
|
-
description: '
|
|
120
|
+
sync: {
|
|
121
|
+
usage: 'incspec sync [--cursor] [--claude] [--all] [--project|--global]',
|
|
122
|
+
aliases: ['s'],
|
|
123
|
+
description: '同步集成到 IDE/AI 工具 (Cursor, Claude Code)',
|
|
124
124
|
options: [
|
|
125
|
-
['--
|
|
125
|
+
['--cursor', '仅同步 Cursor 命令'],
|
|
126
|
+
['--claude', '仅同步 Claude Code Skill'],
|
|
127
|
+
['--all', '同步所有目标'],
|
|
128
|
+
['--project', '同步到当前目录'],
|
|
126
129
|
['--global', '同步到全局目录'],
|
|
127
130
|
],
|
|
128
131
|
},
|
|
@@ -158,7 +161,7 @@ export async function helpCommand(ctx = {}) {
|
|
|
158
161
|
* Show general help
|
|
159
162
|
*/
|
|
160
163
|
function showGeneralHelp() {
|
|
161
|
-
print(colorize('
|
|
164
|
+
print(colorize(' IncSpec - 增量规范驱动开发工具', colors.bold, colors.cyan));
|
|
162
165
|
print(colorize(' ────────────────────────────', colors.dim));
|
|
163
166
|
print('');
|
|
164
167
|
print(colorize('用法:', colors.bold));
|
|
@@ -167,7 +170,7 @@ function showGeneralHelp() {
|
|
|
167
170
|
|
|
168
171
|
print(colorize('工作流命令:', colors.bold));
|
|
169
172
|
print('');
|
|
170
|
-
|
|
173
|
+
|
|
171
174
|
const workflowCommands = ['analyze', 'collect-req', 'collect-dep', 'design', 'apply', 'merge'];
|
|
172
175
|
workflowCommands.forEach((cmd, index) => {
|
|
173
176
|
const def = COMMANDS[cmd];
|
|
@@ -180,7 +183,7 @@ function showGeneralHelp() {
|
|
|
180
183
|
print(colorize('管理命令:', colors.bold));
|
|
181
184
|
print('');
|
|
182
185
|
|
|
183
|
-
const mgmtCommands = ['init', 'update', 'status', 'list', 'validate', 'archive', '
|
|
186
|
+
const mgmtCommands = ['init', 'update', 'status', 'list', 'validate', 'archive', 'sync', 'help'];
|
|
184
187
|
mgmtCommands.forEach(cmd => {
|
|
185
188
|
const def = COMMANDS[cmd];
|
|
186
189
|
const aliases = def.aliases ? colorize(` (${def.aliases.join(', ')})`, colors.dim) : '';
|
|
@@ -198,7 +201,7 @@ function showGeneralHelp() {
|
|
|
198
201
|
print(colorize(' incspec update # 更新模板文件', colors.dim));
|
|
199
202
|
print(colorize(' incspec analyze src/views/Home # 分析代码流程', colors.dim));
|
|
200
203
|
print(colorize(' incspec status # 查看工作流状态', colors.dim));
|
|
201
|
-
print(colorize(' incspec
|
|
204
|
+
print(colorize(' incspec sync # 同步 IDE 集成', colors.dim));
|
|
202
205
|
print('');
|
|
203
206
|
print(colorize(`运行 'incspec help <command>' 查看命令详情。`, colors.dim));
|
|
204
207
|
print('');
|
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
|
@@ -88,10 +88,10 @@ export async function mergeCommand(ctx) {
|
|
|
88
88
|
print('');
|
|
89
89
|
print(colorize(` /incspec/inc-merge ${incrementPath}`, colors.bold, colors.white));
|
|
90
90
|
print('');
|
|
91
|
-
print(colorize('
|
|
91
|
+
print(colorize('或在 Claude Code 中使用 inc-spec-skill 技能:', colors.cyan));
|
|
92
92
|
print('');
|
|
93
93
|
const outDir = path.join(projectRoot, INCSPEC_DIR, DIRS.baselines);
|
|
94
|
-
print(colorize(`
|
|
94
|
+
print(colorize(` 请将 ${incrementPath} 的增量合并到基线 ${outDir}`, colors.dim));
|
|
95
95
|
print('');
|
|
96
96
|
print(colorize('该命令将:', colors.dim));
|
|
97
97
|
print(colorize(' 1. 解析增量设计文件中的时序图和依赖图', colors.dim));
|
|
@@ -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,7 @@ 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 { syncCommand } from './commands/sync.mjs';
|
|
21
21
|
import { helpCommand } from './commands/help.mjs';
|
|
22
22
|
import { colors, colorize } from './lib/terminal.mjs';
|
|
23
23
|
|
|
@@ -203,10 +203,10 @@ async function main() {
|
|
|
203
203
|
await archiveCommand(commandContext);
|
|
204
204
|
break;
|
|
205
205
|
|
|
206
|
-
//
|
|
207
|
-
case '
|
|
208
|
-
case '
|
|
209
|
-
await
|
|
206
|
+
// Sync integrations
|
|
207
|
+
case 'sync':
|
|
208
|
+
case 's':
|
|
209
|
+
await syncCommand(commandContext);
|
|
210
210
|
break;
|
|
211
211
|
|
|
212
212
|
// Help
|
package/lib/claude.mjs
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code integration utilities
|
|
3
|
+
* - Sync inc-spec-skill to global or project
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import * as os from 'os';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
/** Claude skills directory name */
|
|
12
|
+
const CLAUDE_SKILLS_DIR_NAME = 'skills';
|
|
13
|
+
|
|
14
|
+
/** Skill name */
|
|
15
|
+
const SKILL_NAME = 'inc-spec-skill';
|
|
16
|
+
|
|
17
|
+
/** Global Claude skills directory */
|
|
18
|
+
const GLOBAL_CLAUDE_SKILLS_DIR = path.join(os.homedir(), '.claude', CLAUDE_SKILLS_DIR_NAME);
|
|
19
|
+
|
|
20
|
+
/** Project Claude skills directory (relative to project root) */
|
|
21
|
+
const PROJECT_CLAUDE_SKILLS_DIR = path.join('.claude', CLAUDE_SKILLS_DIR_NAME);
|
|
22
|
+
|
|
23
|
+
/** Skill template source directory */
|
|
24
|
+
const SKILL_TEMPLATE_DIR = fileURLToPath(new URL('../templates/inc-spec-skill', import.meta.url));
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Copy directory recursively
|
|
28
|
+
* @param {string} src - Source directory
|
|
29
|
+
* @param {string} dest - Destination directory
|
|
30
|
+
*/
|
|
31
|
+
function copyDirRecursive(src, dest) {
|
|
32
|
+
if (!fs.existsSync(dest)) {
|
|
33
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
37
|
+
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
const srcPath = path.join(src, entry.name);
|
|
40
|
+
const destPath = path.join(dest, entry.name);
|
|
41
|
+
|
|
42
|
+
if (entry.isDirectory()) {
|
|
43
|
+
copyDirRecursive(srcPath, destPath);
|
|
44
|
+
} else {
|
|
45
|
+
fs.copyFileSync(srcPath, destPath);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Count files in directory recursively
|
|
52
|
+
* @param {string} dir - Directory path
|
|
53
|
+
* @returns {number} File count
|
|
54
|
+
*/
|
|
55
|
+
function countFiles(dir) {
|
|
56
|
+
let count = 0;
|
|
57
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
58
|
+
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
if (entry.isDirectory()) {
|
|
61
|
+
count += countFiles(path.join(dir, entry.name));
|
|
62
|
+
} else {
|
|
63
|
+
count++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return count;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Sync skill to global Claude skills directory
|
|
72
|
+
* @returns {{count: number, targetDir: string}}
|
|
73
|
+
*/
|
|
74
|
+
export function syncToGlobalClaude() {
|
|
75
|
+
const targetDir = path.join(GLOBAL_CLAUDE_SKILLS_DIR, SKILL_NAME);
|
|
76
|
+
|
|
77
|
+
// Remove existing if present
|
|
78
|
+
if (fs.existsSync(targetDir)) {
|
|
79
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Copy skill template
|
|
83
|
+
copyDirRecursive(SKILL_TEMPLATE_DIR, targetDir);
|
|
84
|
+
|
|
85
|
+
const count = countFiles(targetDir);
|
|
86
|
+
|
|
87
|
+
return { count, targetDir };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Sync skill to project Claude skills directory
|
|
92
|
+
* @param {string} projectRoot - Project root path
|
|
93
|
+
* @returns {{count: number, targetDir: string}}
|
|
94
|
+
*/
|
|
95
|
+
export function syncToProjectClaude(projectRoot) {
|
|
96
|
+
const targetDir = path.join(projectRoot, PROJECT_CLAUDE_SKILLS_DIR, SKILL_NAME);
|
|
97
|
+
|
|
98
|
+
// Remove existing if present
|
|
99
|
+
if (fs.existsSync(targetDir)) {
|
|
100
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Copy skill template
|
|
104
|
+
copyDirRecursive(SKILL_TEMPLATE_DIR, targetDir);
|
|
105
|
+
|
|
106
|
+
const count = countFiles(targetDir);
|
|
107
|
+
|
|
108
|
+
return { count, targetDir };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if Claude skill exists
|
|
113
|
+
* @param {string} projectRoot - Optional project root
|
|
114
|
+
* @returns {{project: boolean, global: boolean}}
|
|
115
|
+
*/
|
|
116
|
+
export function checkClaudeSkill(projectRoot = null) {
|
|
117
|
+
const globalDir = path.join(GLOBAL_CLAUDE_SKILLS_DIR, SKILL_NAME);
|
|
118
|
+
const result = {
|
|
119
|
+
global: fs.existsSync(globalDir) && fs.readdirSync(globalDir).length > 0,
|
|
120
|
+
project: false,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (projectRoot) {
|
|
124
|
+
const projectDir = path.join(projectRoot, PROJECT_CLAUDE_SKILLS_DIR, SKILL_NAME);
|
|
125
|
+
result.project = fs.existsSync(projectDir) && fs.readdirSync(projectDir).length > 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get skill template info
|
|
133
|
+
* @returns {{fileCount: number, hasSkillMd: boolean, hasReferences: boolean}}
|
|
134
|
+
*/
|
|
135
|
+
export function getSkillTemplateInfo() {
|
|
136
|
+
const skillMdPath = path.join(SKILL_TEMPLATE_DIR, 'SKILL.md');
|
|
137
|
+
const referencesDir = path.join(SKILL_TEMPLATE_DIR, 'references');
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
fileCount: countFiles(SKILL_TEMPLATE_DIR),
|
|
141
|
+
hasSkillMd: fs.existsSync(skillMdPath),
|
|
142
|
+
hasReferences: fs.existsSync(referencesDir) && fs.readdirSync(referencesDir).length > 0,
|
|
143
|
+
};
|
|
144
|
+
}
|
package/lib/config.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import * as fs from 'fs';
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
|
+
import { formatLocalDate } from './terminal.mjs';
|
|
11
12
|
|
|
12
13
|
/** incspec directory name */
|
|
13
14
|
export const INCSPEC_DIR = 'incspec';
|
|
@@ -51,7 +52,9 @@ export function findProjectRoot(startDir) {
|
|
|
51
52
|
|
|
52
53
|
while (currentDir !== root) {
|
|
53
54
|
const incspecPath = path.join(currentDir, INCSPEC_DIR);
|
|
54
|
-
|
|
55
|
+
const projectFile = path.join(incspecPath, FILES.project);
|
|
56
|
+
// 检查 project.md 是否存在,避免 macOS 大小写不敏感导致的误判
|
|
57
|
+
if (fs.existsSync(projectFile)) {
|
|
55
58
|
return currentDir;
|
|
56
59
|
}
|
|
57
60
|
currentDir = path.dirname(currentDir);
|
|
@@ -205,7 +208,7 @@ function generateProjectContent(config) {
|
|
|
205
208
|
content = content.replace(/\{\{name\}\}/g, config.name || '');
|
|
206
209
|
content = content.replace(/\{\{version\}\}/g, config.version || '1.0.0');
|
|
207
210
|
content = content.replace(/\{\{source_dir\}\}/g, config.source_dir || 'src');
|
|
208
|
-
content = content.replace(/\{\{created_at\}\}/g, config.created_at || new Date()
|
|
211
|
+
content = content.replace(/\{\{created_at\}\}/g, config.created_at || formatLocalDate(new Date()));
|
|
209
212
|
|
|
210
213
|
// Handle tech_stack array
|
|
211
214
|
const techStackLines = (config.tech_stack || []).map(item => ` - ${item}`).join('\n');
|
|
@@ -234,7 +237,7 @@ function generateFallbackProjectContent(config) {
|
|
|
234
237
|
* @returns {Object}
|
|
235
238
|
*/
|
|
236
239
|
export function getDefaultConfig(options = {}) {
|
|
237
|
-
const now = new Date()
|
|
240
|
+
const now = formatLocalDate(new Date());
|
|
238
241
|
|
|
239
242
|
return {
|
|
240
243
|
name: options.name || path.basename(process.cwd()),
|