speccrew 0.6.11 → 0.6.13

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.
@@ -67,7 +67,7 @@ function writeSpeccrewRCNew(projectRoot, config) {
67
67
  fs.writeFileSync(newPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
68
68
  }
69
69
 
70
- // 解析命令行参数
70
+ // Parse command line arguments
71
71
  function parseArgs() {
72
72
  const args = process.argv.slice(2);
73
73
  let ide = null;
@@ -82,7 +82,7 @@ function parseArgs() {
82
82
  return { ide };
83
83
  }
84
84
 
85
- // 递归获取目录下所有文件(相对路径)
85
+ // Recursively get all files in directory (relative paths)
86
86
  function getAllFiles(dir, baseDir = dir, result = []) {
87
87
  if (!fs.existsSync(dir)) return result;
88
88
 
@@ -99,7 +99,7 @@ function getAllFiles(dir, baseDir = dir, result = []) {
99
99
  return result;
100
100
  }
101
101
 
102
- // 递归获取目录下所有子目录(相对路径)
102
+ // Recursively get all subdirectories in directory (relative paths)
103
103
  function getAllDirs(dir, baseDir = dir, result = []) {
104
104
  if (!fs.existsSync(dir)) return result;
105
105
 
@@ -115,8 +115,8 @@ function getAllDirs(dir, baseDir = dir, result = []) {
115
115
  return result;
116
116
  }
117
117
 
118
- // 复制文件并返回是否实际复制(目标不存在或内容不同)
119
- // contentTransform: 可选的内容转换函数 (content: string) => string
118
+ // Copy file and return whether actually copied (target doesn't exist or content differs)
119
+ // contentTransform: Optional content transform function (content: string) => string
120
120
  function copyFileIfChanged(src, dest, contentTransform = null) {
121
121
  if (!fs.existsSync(src)) return { copied: false, isNew: false };
122
122
 
@@ -133,18 +133,18 @@ function copyFileIfChanged(src, dest, contentTransform = null) {
133
133
  return { copied: true, isNew: true };
134
134
  }
135
135
 
136
- // 比较文件内容
136
+ // Compare file content
137
137
  const srcContent = fs.readFileSync(src, 'utf8');
138
138
  const destContent = fs.readFileSync(dest, 'utf8');
139
139
 
140
- // 如果有转换函数,比较转换后的内容
140
+ // If transform function provided, compare transformed content
141
141
  const srcContentToCompare = contentTransform ? contentTransform(srcContent) : srcContent;
142
142
 
143
143
  if (srcContentToCompare === destContent) {
144
144
  return { copied: false, isNew: false };
145
145
  }
146
146
 
147
- // 写入转换后的内容(如果有转换函数)
147
+ // Write transformed content (if transform function provided)
148
148
  if (contentTransform) {
149
149
  fs.writeFileSync(dest, srcContentToCompare, 'utf8');
150
150
  } else {
@@ -153,11 +153,11 @@ function copyFileIfChanged(src, dest, contentTransform = null) {
153
153
  return { copied: true, isNew: false };
154
154
  }
155
155
 
156
- // 更新 agents 目录
156
+ // Update agents directory
157
157
  function updateAgents(srcDir, destDir, stats, ideConfig = null) {
158
158
  if (!fs.existsSync(srcDir)) return;
159
159
 
160
- // 确定 contentTransform 函数
160
+ // Determine contentTransform function
161
161
  const contentTransform = (ideConfig && ideConfig.transformFrontmatter)
162
162
  ? (content) => transformAgentForIDE(content, ideConfig)
163
163
  : null;
@@ -178,7 +178,7 @@ function updateAgents(srcDir, destDir, stats, ideConfig = null) {
178
178
  }
179
179
  }
180
180
 
181
- // 检测目标目录中多余的 speccrew-* 文件
181
+ // Detect extra speccrew-* files in target directory
182
182
  if (fs.existsSync(destDir)) {
183
183
  const destEntries = fs.readdirSync(destDir, { withFileTypes: true });
184
184
  for (const entry of destEntries) {
@@ -193,11 +193,11 @@ function updateAgents(srcDir, destDir, stats, ideConfig = null) {
193
193
  }
194
194
  }
195
195
 
196
- // 递归更新 skills 目录
196
+ // Recursively update skills directory
197
197
  function updateSkillsRecursive(srcDir, destDir, stats, currentRelPath = '', ideConfig = null) {
198
198
  if (!fs.existsSync(srcDir)) return;
199
199
 
200
- // 确保目标目录存在
200
+ // Ensure target directory exists
201
201
  if (!fs.existsSync(destDir)) {
202
202
  fs.mkdirSync(destDir, { recursive: true });
203
203
  }
@@ -209,18 +209,18 @@ function updateSkillsRecursive(srcDir, destDir, stats, currentRelPath = '', ideC
209
209
  const relPath = currentRelPath ? path.join(currentRelPath, entry.name) : entry.name;
210
210
 
211
211
  if (entry.isDirectory()) {
212
- // 只处理 speccrew-* 前缀的目录
212
+ // Only process directories with speccrew-* prefix
213
213
  if (!isSpeccrewFile(entry.name)) continue;
214
214
 
215
215
  const dirStats = { added: 0, updated: 0 };
216
216
  updateSkillsRecursive(srcPath, destPath, stats, relPath, ideConfig);
217
217
  } else {
218
- // speccrew-* 目录下的文件
219
- // 检查是否在 speccrew-* 父目录下
218
+ // Files under speccrew-* directory
219
+ // Check if under speccrew-* parent directory
220
220
  const parentDir = path.basename(srcDir);
221
221
  if (!isSpeccrewFile(parentDir)) continue;
222
222
 
223
- // SKILL.md 文件应用 frontmatter 转化(如果需要)
223
+ // Apply frontmatter transformation to SKILL.md files (if needed)
224
224
  const isSkillMd = entry.name === 'SKILL.md';
225
225
  const contentTransform = (isSkillMd && ideConfig && ideConfig.transformFrontmatter)
226
226
  ? (content) => transformSkillForIDE(content, ideConfig)
@@ -235,7 +235,7 @@ function updateSkillsRecursive(srcDir, destDir, stats, currentRelPath = '', ideC
235
235
  }
236
236
  }
237
237
 
238
- // 检测目标目录中多余的 speccrew-* 目录或文件
238
+ // Detect extra speccrew-* directories or files in target directory
239
239
  if (fs.existsSync(destDir)) {
240
240
  const parentDir = path.basename(srcDir);
241
241
  const inSpeccrewDir = isSpeccrewFile(parentDir);
@@ -257,16 +257,16 @@ function updateSkillsRecursive(srcDir, destDir, stats, currentRelPath = '', ideC
257
257
  }
258
258
  }
259
259
 
260
- // 更新 skills 目录(入口)
260
+ // Update skills directory (entry point)
261
261
  function updateSkills(srcDir, destDir, stats, ideConfig = null) {
262
262
  if (!fs.existsSync(srcDir)) return;
263
263
 
264
- // 确保目标目录存在
264
+ // Ensure target directory exists
265
265
  if (!fs.existsSync(destDir)) {
266
266
  fs.mkdirSync(destDir, { recursive: true });
267
267
  }
268
268
 
269
- // 遍历源目录中的 speccrew-* 技能目录
269
+ // Iterate over speccrew-* skill directories in source
270
270
  const entries = fs.readdirSync(srcDir, { withFileTypes: true });
271
271
  for (const entry of entries) {
272
272
  if (!entry.isDirectory()) continue;
@@ -275,15 +275,15 @@ function updateSkills(srcDir, destDir, stats, ideConfig = null) {
275
275
  const srcSkillDir = path.join(srcDir, entry.name);
276
276
  const destSkillDir = path.join(destDir, entry.name);
277
277
 
278
- // 检查是否是新增的技能
278
+ // Check if this is a new skill
279
279
  const isNewSkill = !fs.existsSync(destSkillDir);
280
280
 
281
- // 递归复制技能目录
281
+ // Recursively copy skill directory
282
282
  const skillStats = { added: 0, updated: 0, extra: [], extraDirs: [] };
283
283
  updateSkillsRecursive(srcSkillDir, destSkillDir, skillStats, entry.name, ideConfig);
284
284
 
285
285
  if (isNewSkill) {
286
- // 如果是全新技能,计算文件数量作为 added
286
+ // If brand new skill, count files as added
287
287
  const files = getAllFiles(destSkillDir);
288
288
  stats.added += files.length;
289
289
  } else {
@@ -295,7 +295,7 @@ function updateSkills(srcDir, destDir, stats, ideConfig = null) {
295
295
  stats.extraDirs.push(...skillStats.extraDirs);
296
296
  }
297
297
 
298
- // 检测目标目录中多余的 speccrew-* 技能目录
298
+ // Detect extra speccrew-* skill directories in target
299
299
  const destEntries = fs.readdirSync(destDir, { withFileTypes: true });
300
300
  for (const entry of destEntries) {
301
301
  if (!entry.isDirectory()) continue;
@@ -308,7 +308,7 @@ function updateSkills(srcDir, destDir, stats, ideConfig = null) {
308
308
  }
309
309
  }
310
310
 
311
- // 更新 workspace docs
311
+ // Update workspace docs
312
312
  function updateWorkspaceDocs(srcDir, destDir, stats) {
313
313
  if (!fs.existsSync(srcDir)) return;
314
314
 
@@ -333,7 +333,7 @@ function updateWorkspaceDocs(srcDir, destDir, stats) {
333
333
  }
334
334
  }
335
335
 
336
- // 更新 npm 包文档 (GETTING-STARTED*.md README*.md)
336
+ // Update npm package docs (GETTING-STARTED*.md and README*.md)
337
337
  function updatePackageDocs(packageRoot, workspaceDir, stats) {
338
338
  // Update GETTING-STARTED*.md files from docs/ directory
339
339
  const packageDocsDir = path.join(packageRoot, 'docs');
@@ -375,7 +375,7 @@ function updatePackageDocs(packageRoot, workspaceDir, stats) {
375
375
  }
376
376
  }
377
377
 
378
- // 主函数
378
+ // Main function
379
379
  function run() {
380
380
  try {
381
381
  const args = parseArgs();
@@ -398,18 +398,18 @@ function run() {
398
398
 
399
399
  const projectRoot = process.cwd();
400
400
 
401
- // 读取 .speccrewrc (with migration support)
401
+ // Read .speccrewrc (with migration support)
402
402
  const rc = readSpeccrewRCWithMigration(projectRoot);
403
403
  if (!rc) {
404
404
  console.error('Error: .speccrewrc not found. Please run "speccrew init" first.');
405
405
  process.exit(1);
406
406
  }
407
407
 
408
- // 获取版本信息
408
+ // Get version info
409
409
  const currentVersion = getPackageVersion();
410
410
  const installedVersion = rc.version || 'unknown';
411
411
 
412
- // 解析 IDE 列表
412
+ // Parse IDE list
413
413
  let ides;
414
414
  if (args.ide) {
415
415
  ides = [getIDEConfig(args.ide)];
@@ -424,7 +424,7 @@ function run() {
424
424
  const sourceRoot = getSourceRoot();
425
425
  const workspaceTemplatePath = getWorkspaceTemplatePath();
426
426
 
427
- // 统计信息
427
+ // Statistics
428
428
  const totalStats = {
429
429
  agents: { updated: 0, added: 0 },
430
430
  skills: { updated: 0, added: 0 },
@@ -434,9 +434,9 @@ function run() {
434
434
  extraDirs: [],
435
435
  };
436
436
 
437
- // 对每个 IDE 执行更新
437
+ // Execute update for each IDE
438
438
  for (const ide of ides) {
439
- // 更新 agents
439
+ // Update agents
440
440
  const srcAgentsDir = path.join(sourceRoot, 'agents');
441
441
  const destAgentsDir = path.join(projectRoot, ide.agentsDir);
442
442
  const agentStats = { updated: 0, added: 0, extra: [] };
@@ -445,7 +445,7 @@ function run() {
445
445
  totalStats.agents.added += agentStats.added;
446
446
  totalStats.extra.push(...agentStats.extra);
447
447
 
448
- // 更新 skills
448
+ // Update skills
449
449
  const srcSkillsDir = path.join(sourceRoot, 'skills');
450
450
  const destSkillsDir = path.join(projectRoot, ide.skillsDir);
451
451
  const skillStats = { updated: 0, added: 0, extra: [], extraDirs: [] };
@@ -462,11 +462,11 @@ function run() {
462
462
  }
463
463
  }
464
464
 
465
- // 更新 workspace 文件(docs, scripts 等所有子目录)
465
+ // Update workspace files (docs, scripts, and all subdirectories)
466
466
  const destWorkspaceDir = path.join(projectRoot, 'speccrew-workspace');
467
467
  const workspaceStats = { updated: 0, added: 0 };
468
468
 
469
- // 遍历 workspace-template 下的所有一级子目录并同步
469
+ // Iterate over all first-level subdirectories in workspace-template and sync
470
470
  if (fs.existsSync(workspaceTemplatePath)) {
471
471
  const templateEntries = fs.readdirSync(workspaceTemplatePath, { withFileTypes: true });
472
472
  for (const entry of templateEntries) {
@@ -483,7 +483,7 @@ function run() {
483
483
  totalStats.workspaceDocs.updated = workspaceStats.updated;
484
484
  totalStats.workspaceDocs.added = workspaceStats.added;
485
485
 
486
- // 更新 npm 包文档 (GETTING-STARTED*.md and README*.md)
486
+ // Update npm package docs (GETTING-STARTED*.md and README*.md)
487
487
  const packageRoot = getPackageRoot();
488
488
  const workspaceDir = path.join(projectRoot, 'speccrew-workspace');
489
489
  const packageDocsStats = { updated: 0, added: 0 };
@@ -491,12 +491,12 @@ function run() {
491
491
  totalStats.packageDocs.updated = packageDocsStats.updated;
492
492
  totalStats.packageDocs.added = packageDocsStats.added;
493
493
 
494
- // 更新 .speccrewrc
494
+ // Update .speccrewrc
495
495
  rc.version = currentVersion;
496
496
  rc.updatedAt = new Date().toISOString();
497
497
  writeSpeccrewRCNew(projectRoot, rc);
498
498
 
499
- // 输出结果
499
+ // Output results
500
500
  if (installedVersion === currentVersion) {
501
501
  console.log(`Already up to date: v${currentVersion}\n`);
502
502
  } else {
@@ -508,7 +508,7 @@ function run() {
508
508
  console.log(`Workspace: ${totalStats.workspaceDocs.updated} updated, ${totalStats.workspaceDocs.added} added`);
509
509
  console.log(`Docs: ${totalStats.packageDocs.updated} updated, ${totalStats.packageDocs.added} added`);
510
510
 
511
- // 输出警告(过滤掉已废弃的 Skill)
511
+ // Output warnings (filter out deprecated Skills)
512
512
  const allExtras = [...new Set([...totalStats.extra, ...totalStats.extraDirs])]
513
513
  .filter(item => !DEPRECATED_SKILLS.includes(item));
514
514
  if (allExtras.length > 0) {
@@ -27,13 +27,13 @@ const IDE_CONFIGS = {
27
27
  skillsDir: '.claude/skills',
28
28
  agentsDir: '.claude/agents',
29
29
  transformFrontmatter: true,
30
- agentToolsAction: 'filter', // 保留 tools 但过滤不支持的
30
+ agentToolsAction: 'filter', // Keep tools but filter unsupported ones
31
31
  skillToolsAction: 'rename', // tools → allowed-tools
32
32
  unsupportedTools: ['WebFetch', 'WebSearch', 'Task', 'Skill', 'SearchCodebase'],
33
33
  },
34
34
  };
35
35
 
36
- // 自动检测项目根目录下存在的 IDE 目录
36
+ // Auto-detect IDE directories in project root
37
37
  function detectIDE(projectRoot) {
38
38
  const detected = [];
39
39
  for (const [key, config] of Object.entries(IDE_CONFIGS)) {
@@ -45,7 +45,7 @@ function detectIDE(projectRoot) {
45
45
  return detected;
46
46
  }
47
47
 
48
- // 获取指定 IDE 的配置
48
+ // Get configuration for specified IDE
49
49
  function getIDEConfig(ideId) {
50
50
  const config = IDE_CONFIGS[ideId];
51
51
  if (!config) {
@@ -55,14 +55,14 @@ function getIDEConfig(ideId) {
55
55
  return { id: ideId, ...config };
56
56
  }
57
57
 
58
- // 解析 IDE 参数:优先用 --ide 参数,其次自动检测,最后从 .speccrewrc 读取
58
+ // Resolve IDE parameter: CLI arg first, then auto-detect, then read from .speccrewrc
59
59
  function resolveIDE(projectRoot, cliIdeArg) {
60
- // 1. CLI 参数优先
60
+ // 1. CLI argument takes priority
61
61
  if (cliIdeArg) {
62
62
  return [getIDEConfig(cliIdeArg)];
63
63
  }
64
64
 
65
- // 2. .speccrewrc 读取(先检查 workspace 目录,再检查旧位置)
65
+ // 2. Read from .speccrewrc (check workspace dir first, then old location)
66
66
  const workspaceRcPath = path.join(projectRoot, 'speccrew-workspace', '.speccrewrc');
67
67
  const oldRcPath = path.join(projectRoot, '.speccrewrc');
68
68
  const rcPath = fs.existsSync(workspaceRcPath) ? workspaceRcPath : oldRcPath;
@@ -79,7 +79,7 @@ function resolveIDE(projectRoot, cliIdeArg) {
79
79
  }
80
80
  }
81
81
 
82
- // 3. 自动检测
82
+ // 3. Auto-detect
83
83
  const detected = detectIDE(projectRoot);
84
84
  if (detected.length === 0) {
85
85
  throw new Error(
@@ -91,8 +91,8 @@ function resolveIDE(projectRoot, cliIdeArg) {
91
91
  }
92
92
 
93
93
  /**
94
- * 解析 frontmatter,返回 { frontmatter: string, body: string, parsed: object }
95
- * 如果没有 frontmatter,返回 { frontmatter: null, body: content, parsed: null }
94
+ * Parse frontmatter, returns { frontmatter: string, body: string, parsed: object }
95
+ * If no frontmatter, returns { frontmatter: null, body: content, parsed: null }
96
96
  */
97
97
  function parseFrontmatter(content) {
98
98
  // Normalize line endings for cross-platform compatibility
@@ -108,7 +108,7 @@ function parseFrontmatter(content) {
108
108
  const frontmatter = match[1];
109
109
  const body = content.slice(match[0].length);
110
110
 
111
- // 简单解析 YAML 为对象(只处理简单的 key: value 格式)
111
+ // Simple YAML parser (only handles simple key: value format)
112
112
  const parsed = {};
113
113
  const lines = frontmatter.split('\n');
114
114
  let currentKey = null;
@@ -117,21 +117,21 @@ function parseFrontmatter(content) {
117
117
  for (const line of lines) {
118
118
  const trimmed = line.trim();
119
119
 
120
- // 跳过空行
120
+ // Skip empty lines
121
121
  if (!trimmed) continue;
122
122
 
123
- // 检测 key: value 格式(支持多行列表)
123
+ // Detect key: value format (supports multi-line lists)
124
124
  const keyMatch = line.match(/^(\w+):\s*(.*)$/);
125
125
 
126
126
  if (keyMatch) {
127
- // 保存之前的 key
127
+ // Save previous key
128
128
  if (currentKey !== null) {
129
129
  parsed[currentKey] = currentValue;
130
130
  }
131
131
  currentKey = keyMatch[1];
132
132
  currentValue = keyMatch[2].trim();
133
133
  } else if (currentKey !== null && line.startsWith(' - ')) {
134
- // 多行列表项
134
+ // Multi-line list item
135
135
  if (!Array.isArray(currentValue)) {
136
136
  currentValue = currentValue ? [currentValue] : [];
137
137
  }
@@ -139,7 +139,7 @@ function parseFrontmatter(content) {
139
139
  }
140
140
  }
141
141
 
142
- // 保存最后一个 key
142
+ // Save last key
143
143
  if (currentKey !== null) {
144
144
  parsed[currentKey] = currentValue;
145
145
  }
@@ -148,7 +148,7 @@ function parseFrontmatter(content) {
148
148
  }
149
149
 
150
150
  /**
151
- * 将对象序列化为 YAML frontmatter 字符串
151
+ * Serialize object to YAML frontmatter string
152
152
  */
153
153
  function serializeFrontmatter(obj) {
154
154
  const lines = [];
@@ -172,7 +172,7 @@ function serializeFrontmatter(obj) {
172
172
  }
173
173
 
174
174
  /**
175
- * 从正文提取第一个非空非标题段落作为 description
175
+ * Extract first non-empty, non-heading paragraph from body as description
176
176
  */
177
177
  function extractDescriptionFromBody(body) {
178
178
  const lines = body.split('\n');
@@ -182,7 +182,7 @@ function extractDescriptionFromBody(body) {
182
182
  for (const line of lines) {
183
183
  const trimmed = line.trim();
184
184
 
185
- // 处理代码块
185
+ // Handle code blocks
186
186
  if (trimmed.startsWith('```')) {
187
187
  inCodeBlock = !inCodeBlock;
188
188
  continue;
@@ -190,19 +190,19 @@ function extractDescriptionFromBody(body) {
190
190
 
191
191
  if (inCodeBlock) continue;
192
192
 
193
- // 跳过标题行和空行
193
+ // Skip heading lines and empty lines
194
194
  if (trimmed.startsWith('#') || !trimmed) {
195
- // 如果已经积累了段落内容,返回它
195
+ // If paragraph content already accumulated, return it
196
196
  if (paragraph) {
197
197
  return paragraph.slice(0, 200).trim();
198
198
  }
199
199
  continue;
200
200
  }
201
201
 
202
- // 积累段落内容
202
+ // Accumulate paragraph content
203
203
  paragraph += (paragraph ? ' ' : '') + trimmed;
204
204
 
205
- // 如果遇到空行且已有段落内容,结束段落
205
+ // If empty line encountered and paragraph content exists, end paragraph
206
206
  if (paragraph && !trimmed) {
207
207
  return paragraph.slice(0, 200).trim();
208
208
  }
@@ -212,10 +212,10 @@ function extractDescriptionFromBody(body) {
212
212
  }
213
213
 
214
214
  /**
215
- * 过滤工具列表,移除不支持的工具
216
- * @param {string} toolsStr - 逗号或空格分隔的工具列表字符串
217
- * @param {string[]} unsupportedTools - 不支持的工具名数组
218
- * @returns {string} - 过滤后的工具列表字符串(逗号+空格分隔)
215
+ * Filter tools list, remove unsupported tools
216
+ * @param {string} toolsStr - Comma or space separated tools string
217
+ * @param {string[]} unsupportedTools - Array of unsupported tool names
218
+ * @returns {string} - Filtered tools string (comma + space separated)
219
219
  */
220
220
  function filterTools(toolsStr, unsupportedTools) {
221
221
  if (!toolsStr || !unsupportedTools || unsupportedTools.length === 0) {
@@ -227,22 +227,22 @@ function filterTools(toolsStr, unsupportedTools) {
227
227
  }
228
228
 
229
229
  /**
230
- * Agent .md 文件的 frontmatter Qoder 格式转为 Cursor 格式
231
- * @param {string} content - 原始文件内容
232
- * @param {object} ideConfig - IDE 配置对象
233
- * @returns {string} - 转换后的内容
230
+ * Transform Agent .md file frontmatter from Qoder format to Cursor format
231
+ * @param {string} content - Original file content
232
+ * @param {object} ideConfig - IDE configuration object
233
+ * @returns {string} - Transformed content
234
234
  */
235
235
  function transformAgentForIDE(content, ideConfig) {
236
236
  const { frontmatter, body, parsed } = parseFrontmatter(content);
237
237
 
238
238
  if (!parsed) {
239
- // 没有 frontmatter,直接返回原内容
239
+ // No frontmatter, return original content directly
240
240
  return content;
241
241
  }
242
242
 
243
- // 处理 tools 字段
243
+ // Process tools field
244
244
  if (ideConfig.agentToolsAction === 'filter' && parsed.tools) {
245
- // Claude: 保留 tools 但过滤掉不支持的工具
245
+ // Claude: Keep tools but filter unsupported ones
246
246
  const filtered = filterTools(parsed.tools, ideConfig.unsupportedTools);
247
247
  if (filtered) {
248
248
  parsed.tools = filtered;
@@ -250,11 +250,11 @@ function transformAgentForIDE(content, ideConfig) {
250
250
  delete parsed.tools;
251
251
  }
252
252
  } else {
253
- // 默认行为(Cursor等):移除 tools 字段
253
+ // Default behavior (Cursor, etc.): Remove tools field
254
254
  delete parsed.tools;
255
255
  }
256
256
 
257
- // 如果没有 description,从正文提取
257
+ // If no description, extract from body
258
258
  if (!parsed.description) {
259
259
  const extractedDesc = extractDescriptionFromBody(body);
260
260
  if (extractedDesc) {
@@ -262,7 +262,7 @@ function transformAgentForIDE(content, ideConfig) {
262
262
  }
263
263
  }
264
264
 
265
- // 添加 agentDefaults 中的字段(不覆盖已存在的)
265
+ // Add fields from agentDefaults (don't override existing)
266
266
  if (ideConfig.agentDefaults) {
267
267
  for (const [key, value] of Object.entries(ideConfig.agentDefaults)) {
268
268
  if (!(key in parsed)) {
@@ -271,39 +271,39 @@ function transformAgentForIDE(content, ideConfig) {
271
271
  }
272
272
  }
273
273
 
274
- // 重新组装 frontmatter body
274
+ // Reassemble frontmatter and body
275
275
  const newFrontmatter = serializeFrontmatter(parsed);
276
276
  return newFrontmatter + body;
277
277
  }
278
278
 
279
279
  /**
280
- * Skill SKILL.md frontmatter 转化
281
- * @param {string} content - 原始文件内容
282
- * @param {object} ideConfig - IDE 配置对象
283
- * @returns {string} - 转换后的内容
280
+ * Transform Skill SKILL.md frontmatter
281
+ * @param {string} content - Original file content
282
+ * @param {object} ideConfig - IDE configuration object
283
+ * @returns {string} - Transformed content
284
284
  */
285
285
  function transformSkillForIDE(content, ideConfig) {
286
286
  const { frontmatter, body, parsed } = parseFrontmatter(content);
287
287
 
288
288
  if (!parsed) {
289
- // 没有 frontmatter,直接返回原内容
289
+ // No frontmatter, return original content directly
290
290
  return content;
291
291
  }
292
292
 
293
- // 处理 tools 字段
293
+ // Process tools field
294
294
  if (ideConfig.skillToolsAction === 'rename' && parsed.tools) {
295
- // Claude: tools → allowed-tools(过滤后)
295
+ // Claude: tools → allowed-tools (filtered)
296
296
  const filtered = filterTools(parsed.tools, ideConfig.unsupportedTools);
297
297
  if (filtered) {
298
298
  parsed['allowed-tools'] = filtered;
299
299
  }
300
300
  delete parsed.tools;
301
301
  } else {
302
- // 默认行为(Cursor等):移除 tools 字段
302
+ // Default behavior (Cursor, etc.): Remove tools field
303
303
  delete parsed.tools;
304
304
  }
305
305
 
306
- // 如果没有 description,从正文提取
306
+ // If no description, extract from body
307
307
  if (!parsed.description) {
308
308
  const extractedDesc = extractDescriptionFromBody(body);
309
309
  if (extractedDesc) {
@@ -311,7 +311,7 @@ function transformSkillForIDE(content, ideConfig) {
311
311
  }
312
312
  }
313
313
 
314
- // 重新组装 frontmatter body
314
+ // Reassemble frontmatter and body
315
315
  const newFrontmatter = serializeFrontmatter(parsed);
316
316
  return newFrontmatter + body;
317
317
  }
package/lib/utils.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
 
4
- // 递归复制目录,支持过滤函数和内容转换
4
+ // Recursively copy directory, supports filter function and content transformation
5
5
  function copyDirRecursive(src, dest, filter, contentTransform) {
6
6
  if (!fs.existsSync(src)) return { copied: 0, skipped: 0 };
7
7
 
@@ -23,7 +23,7 @@ function copyDirRecursive(src, dest, filter, contentTransform) {
23
23
  copied += sub.copied;
24
24
  skipped += sub.skipped;
25
25
  } else {
26
- // 如果提供了 contentTransform,尝试转换内容
26
+ // If contentTransform provided, try to transform content
27
27
  if (contentTransform) {
28
28
  const originalContent = fs.readFileSync(srcPath, 'utf8');
29
29
  const transformedContent = contentTransform(originalContent, entry.name, srcPath);
@@ -32,7 +32,7 @@ function copyDirRecursive(src, dest, filter, contentTransform) {
32
32
  fs.writeFileSync(destPath, transformedContent, 'utf8');
33
33
  copied++;
34
34
  } else {
35
- // transform 返回 null/undefined,按原方式复制
35
+ // transform returns null/undefined, copy as-is
36
36
  fs.copyFileSync(srcPath, destPath);
37
37
  copied++;
38
38
  }
@@ -45,12 +45,12 @@ function copyDirRecursive(src, dest, filter, contentTransform) {
45
45
  return { copied, skipped };
46
46
  }
47
47
 
48
- // 判断是否 speccrew-* 前缀的文件/目录
48
+ // Check if file/directory has speccrew-* prefix
49
49
  function isSpeccrewFile(name) {
50
50
  return name.startsWith('speccrew-');
51
51
  }
52
52
 
53
- // 读取 .speccrewrc 配置
53
+ // Read .speccrewrc configuration
54
54
  function readSpeccrewRC(projectRoot) {
55
55
  const rcPath = path.join(projectRoot, '.speccrewrc');
56
56
  if (!fs.existsSync(rcPath)) return null;
@@ -61,38 +61,38 @@ function readSpeccrewRC(projectRoot) {
61
61
  }
62
62
  }
63
63
 
64
- // 写入 .speccrewrc 配置
65
- // targetDir: 目标目录(通常是 workspace 目录)
64
+ // Write .speccrewrc configuration
65
+ // targetDir: Target directory (usually workspace directory)
66
66
  function writeSpeccrewRC(targetDir, config) {
67
67
  const rcPath = path.join(targetDir, '.speccrewrc');
68
68
  fs.writeFileSync(rcPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
69
69
  }
70
70
 
71
- // 获取包版本
71
+ // Get package version
72
72
  function getPackageVersion() {
73
73
  const pkgPath = path.join(__dirname, '..', 'package.json');
74
74
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
75
75
  return pkg.version;
76
76
  }
77
77
 
78
- // 获取包内 .speccrew 源文件目录
78
+ // Get package .speccrew source directory
79
79
  function getSourceRoot() {
80
80
  return path.join(__dirname, '..', '.speccrew');
81
81
  }
82
82
 
83
- // 获取包内 workspace-template 目录
83
+ // Get package workspace-template directory
84
84
  function getWorkspaceTemplatePath() {
85
85
  return path.join(__dirname, '..', 'workspace-template');
86
86
  }
87
87
 
88
- // 递归创建目录结构(数组形式)
88
+ // Recursively create directory structure (array format)
89
89
  function ensureDirectories(baseDir, dirs) {
90
90
  for (const dir of dirs) {
91
91
  fs.mkdirSync(path.join(baseDir, dir), { recursive: true });
92
92
  }
93
93
  }
94
94
 
95
- // 递归删除目录
95
+ // Recursively delete directory
96
96
  function removeDirRecursive(dirPath) {
97
97
  if (!fs.existsSync(dirPath)) return;
98
98
  const entries = fs.readdirSync(dirPath, { withFileTypes: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "speccrew",
3
- "version": "0.6.11",
3
+ "version": "0.6.13",
4
4
  "description": "Spec-Driven Development toolkit for AI-powered IDEs",
5
5
  "author": "charlesmu99",
6
6
  "repository": {