@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 CHANGED
@@ -100,12 +100,12 @@ AI 编程助手在处理复杂前端代码库时常常力不从心,因为 API
100
100
  <details>
101
101
  <summary><strong>原生斜杠命令</strong>(点击展开)</summary>
102
102
 
103
- 运行 `incspec cursor-sync` 后,这些工具可使用内置的 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** | 使用 AGENTS.md 工作流指令 |
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:同步 Cursor 命令(可选)
162
+ #### 步骤 3:同步 IDE 集成(可选)
163
163
 
164
- 如果使用 Cursor IDE
164
+ 同步到 Cursor 或 Claude Code
165
165
  ```bash
166
- incspec cursor-sync
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
- | `cursor-sync` | `cs` | 同步 |
508
+ | `sync` | `s` | 同步 |
505
509
  | `update` | `up` | 更新 |
506
510
  | `help` | `h` | 帮助 |
507
511
 
@@ -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('或使用 Claude Code 命令:', colors.cyan));
210
+ print(colorize('或在 Claude Code 中使用 inc-spec-skill 技能:', colors.cyan));
211
211
  print('');
212
- print(colorize(` /ai-increment:analyze-codeflow ${sourcePath} ${path.join(projectRoot, INCSPEC_DIR, DIRS.baselines)}`, colors.bold, colors.white));
212
+ print(colorize(` 请分析 ${sourcePath} 的代码流程,生成基线报告到 ${path.join(projectRoot, INCSPEC_DIR, DIRS.baselines)}`, colors.dim));
213
213
  print('');
214
214
  printInfo(`完成后运行 'incspec status' 查看进度`);
215
215
  print('');
@@ -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('或使用 Claude Code 命令:', colors.cyan));
91
+ print(colorize('或在 Claude Code 中使用 inc-spec-skill 技能:', colors.cyan));
92
92
  print('');
93
- print(colorize(` /ai-increment:apply-increment-code ${incrementPath} ${path.join(projectRoot, sourceDir)}`, colors.bold, colors.white));
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));
@@ -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('或使用 Claude Code 命令:', colors.cyan));
65
+ print(colorize('或在 Claude Code 中使用 inc-spec-skill 技能:', colors.cyan));
66
66
  print('');
67
- print(colorize(` /ai-increment:ui-dependency-collection ${path.join(projectRoot, INCSPEC_DIR, DIRS.requirements)}`, colors.bold, colors.white));
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));
@@ -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('或使用 Claude Code 命令:', colors.cyan));
65
+ print(colorize('或在 Claude Code 中使用 inc-spec-skill 技能:', colors.cyan));
66
66
  print('');
67
- print(colorize(` /ai-increment:structured-requirements-collection ${path.join(projectRoot, INCSPEC_DIR, DIRS.requirements)}`, colors.bold, colors.white));
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));
@@ -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('或使用 Claude Code 命令:', colors.cyan));
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(` /ai-increment:analyze-increment-codeflow ${baselinePath} ${reqPath} ${depPath} ${outDir}`, colors.bold, colors.white));
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
- 'cursor-sync': {
121
- usage: 'incspec cursor-sync [--project|--global]',
122
- aliases: ['cs'],
123
- description: '同步 Cursor slash commands',
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
- ['--project', '同步到项目目录'],
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(' incspec - 增量规范驱动开发工具', colors.bold, colors.cyan));
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', 'cursor-sync', 'help'];
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 cursor-sync # 同步 Cursor 命令', colors.dim));
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.toISOString().replace('T', ' ').slice(0, 16);
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}`);
@@ -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('或使用 Claude Code 命令:', colors.cyan));
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(` /ai-increment:merge-to-baseline ${incrementPath} ${outDir}`, colors.bold, colors.white));
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
+ }
@@ -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().toISOString().replace('T', ' ').slice(0, 16);
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 { cursorSyncCommand } from './commands/cursor-sync.mjs';
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
- // Cursor integration
207
- case 'cursor-sync':
208
- case 'cs':
209
- await cursorSyncCommand(commandContext);
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
- if (fs.existsSync(incspecPath) && fs.statSync(incspecPath).isDirectory()) {
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().toISOString().split('T')[0]);
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().toISOString().split('T')[0];
240
+ const now = formatLocalDate(new Date());
238
241
 
239
242
  return {
240
243
  name: options.name || path.basename(process.cwd()),