@vdkit/cli 3.0.0 → 3.0.2

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.
Files changed (80) hide show
  1. package/README.md +1 -1
  2. package/cli.js +30 -1
  3. package/package.json +8 -8
  4. package/src/blueprints/BlueprintManifest.js +1 -1
  5. package/src/blueprints/PluginPackager.js +1 -1
  6. package/src/blueprints/retrieval/BlueprintRetrievalEngine.js +8 -6
  7. package/src/blueprints-client.js +1 -1
  8. package/src/commands/base/BaseCommand.js +1 -1
  9. package/src/commands/blueprints/BrowseCommand.js +1 -0
  10. package/src/commands/blueprints/CreateCommand.js +1 -1
  11. package/src/commands/blueprints/DeployCommand.js +65 -2
  12. package/src/commands/blueprints/RepoStatsCommand.js +6 -6
  13. package/src/commands/blueprints/SyncCommand.js +8 -4
  14. package/src/commands/community/PublishCommand.js +10 -10
  15. package/src/commands/core/ConvertCommand.js +2 -2
  16. package/src/commands/core/ImportCommand.js +1 -3
  17. package/src/commands/core/InitCommand.js +3 -3
  18. package/src/commands/core/ScanCommand.js +6 -2
  19. package/src/commands/core/StatusCommand.js +6 -2
  20. package/src/commands/core/ValidateCommand.js +1 -1
  21. package/src/commands/hub/HubGenerateCommand.js +413 -11
  22. package/src/commands/migration/SchemaMigrateCommand.js +5 -1
  23. package/src/commands/migration/UnifiedMigrateCommand.js +21 -7
  24. package/src/commands/shared/CommandContext.js +7 -3
  25. package/src/commands/team/ShareCommand.js +44 -9
  26. package/src/community/CommunityDeployer.js +109 -33
  27. package/src/hub/ConfigManager.js +3 -3
  28. package/src/hub/HubIntegration.js +3 -3
  29. package/src/hub/TelemetryManager.js +3 -3
  30. package/src/hub/VDKHubClient.js +141 -92
  31. package/src/hub/index.js +8 -8
  32. package/src/integrations/base-integration.js +3 -1
  33. package/src/integrations/claude-code-integration.js +6 -6
  34. package/src/integrations/cursor-integration.js +2 -2
  35. package/src/integrations/generic-ai-integration.js +9 -9
  36. package/src/integrations/generic-ide-integration.js +5 -5
  37. package/src/integrations/integration-manager.js +1 -1
  38. package/src/integrations/jetbrains-integration.js +3 -3
  39. package/src/integrations/vscode-variants-integration.js +2 -2
  40. package/src/integrations/windsurf-integration.js +1 -1
  41. package/src/integrations/zed-integration.js +2 -2
  42. package/src/ir/README.md +2 -2
  43. package/src/ir/generators.js +4 -5
  44. package/src/ir/index.js +1 -6
  45. package/src/ir/performance.js +3 -3
  46. package/src/mcp/McpManager.js +3 -3
  47. package/src/migration/AutoMigrator.js +46 -32
  48. package/src/migration/converters/context-converter.js +3 -3
  49. package/src/migration/converters/schema-v3-migrator.js +1 -1
  50. package/src/migration/core/MigrationBackup.js +23 -17
  51. package/src/migration/core/migration-detector.js +3 -3
  52. package/src/migration/detectors/rule-detector.js +2 -2
  53. package/src/migration/migration-manager.js +7 -6
  54. package/src/plugins/registry.js +1 -1
  55. package/src/publishing/PublishManager.js +294 -24
  56. package/src/publishing/UniversalFormatConverter.js +113 -1
  57. package/src/publishing/clients/GitHubPRClient.js +169 -27
  58. package/src/scanner/README.md +1 -1
  59. package/src/scanner/USER-GUIDE.md +1 -1
  60. package/src/scanner/core/BlueprintLoader.js +18 -7
  61. package/src/scanner/core/ClaudeCodeAdapter.js +18 -12
  62. package/src/scanner/core/CopilotAdapter.js +1 -1
  63. package/src/scanner/core/PatternDetector.js +1 -1
  64. package/src/scanner/core/PlatformConfigExtractor.js +4 -4
  65. package/src/scanner/core/RuleAdapter.js +6 -6
  66. package/src/scanner/core/RuleGenerator.js +8 -3
  67. package/src/scanner/core/TechnologyAnalyzer.js +1 -1
  68. package/src/scanner/utils/constants.js +1 -1
  69. package/src/scanner/utils/package-analyzer.js +2 -2
  70. package/src/scanner/utils/version.js +1 -1
  71. package/src/shared/ProjectContextAnalyzer.js +4 -0
  72. package/src/shared/blueprint-artifact-paths.js +32 -0
  73. package/src/shared/ide-configuration.js +8 -8
  74. package/src/shared/sync-operations.js +83 -16
  75. package/src/utils/file-system.js +17 -15
  76. package/src/utils/filename-generator.js +2 -4
  77. package/src/utils/schema-validator.js +1 -1
  78. package/src/utils/update-mcp-config.js +2 -2
  79. package/src/validation/check-duplicates.js +1 -1
  80. package/src/validation/validate-rules.js +7 -7
@@ -730,7 +730,7 @@ export class RuleAdapter {
730
730
  }
731
731
 
732
732
  // Sort rules by priority (higher priority first)
733
- const prioritizedRules = rules.sort((a, b) => {
733
+ const prioritizedRules = rules.toSorted((a, b) => {
734
734
  const aPriority = a.frontmatter?.priority || priority;
735
735
  const bPriority = b.frontmatter?.priority || priority;
736
736
  return bPriority - aPriority;
@@ -987,7 +987,7 @@ ${r.content}`
987
987
  const fileName = this.getCursorFileName(rule);
988
988
 
989
989
  adaptedFiles.push({
990
- path: path.join(rulesDir, `${prefix}-${fileName}`),
990
+ path: path.join(rulesDir, `${prefix}-${fileName}.mdc`),
991
991
  content: result.content,
992
992
  type: 'rule',
993
993
  activation: activationType,
@@ -2039,7 +2039,7 @@ This project uses VDK CLI for AI assistant integration and follows specific patt
2039
2039
  @CLAUDE-personal.md
2040
2040
 
2041
2041
  ## Important Conventions
2042
- - All AI rules are stored in \`.vdk/rules/\` directory
2042
+ - All AI rule artifacts are stored in \`.vdk/blueprints/rules/\` directory
2043
2043
  - Rules follow unified YAML frontmatter format
2044
2044
  - Project follows VDK CLI naming conventions
2045
2045
  - Memory persistence is enabled for context continuity
@@ -2418,7 +2418,7 @@ ${this.formatForWindsurf(keyContent)}
2418
2418
  // Prioritize rules based on their effectiveness for code review
2419
2419
  return rules
2420
2420
  .filter(rule => rule.frontmatter?.category !== 'assistant') // Skip assistant-specific rules
2421
- .sort((a, b) => {
2421
+ .toSorted((a, b) => {
2422
2422
  const aPriority = this.getCopilotPriority(a);
2423
2423
  const bPriority = this.getCopilotPriority(b);
2424
2424
  return bPriority - aPriority;
@@ -2722,14 +2722,14 @@ This directory contains GitHub Copilot Enterprise coding guidelines generated by
2722
2722
  async adaptForGeneric(rules, projectContext, platformConfig = {}) {
2723
2723
  const adaptedFiles = [];
2724
2724
  const configPath = platformConfig.configPath || '.vdk';
2725
- const rulesPath = platformConfig.rulesPath || '.vdk/rules';
2725
+ const rulesPath = platformConfig.rulesPath || '.vdk/blueprints/rules';
2726
2726
  const priority = platformConfig.priority || 5;
2727
2727
 
2728
2728
  const baseDir = path.join(this.projectPath, configPath);
2729
2729
  const rulesDir = path.join(this.projectPath, rulesPath);
2730
2730
 
2731
2731
  // Sort rules by priority
2732
- const prioritizedRules = rules.sort((a, b) => {
2732
+ const prioritizedRules = rules.toSorted((a, b) => {
2733
2733
  const aPriority = a.frontmatter?.priority || priority;
2734
2734
  const bPriority = b.frontmatter?.priority || priority;
2735
2735
  return bPriority - aPriority;
@@ -20,10 +20,15 @@ import { TechnologyRuleMapper } from './TechnologyRuleMapper.js';
20
20
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
21
 
22
22
  export class RuleGenerator {
23
- constructor(outputPath = './.vdk/rules', template = 'default', overwrite = false, options = {}) {
23
+ constructor(
24
+ outputPath = './.vdk/blueprints/rules',
25
+ template = 'default',
26
+ overwrite = false,
27
+ options = {}
28
+ ) {
24
29
  if (typeof outputPath === 'object') {
25
30
  options = outputPath;
26
- outputPath = options.outputPath || './.vdk/rules';
31
+ outputPath = options.outputPath || './.vdk/blueprints/rules';
27
32
  template = options.template || 'default';
28
33
  overwrite = options.overwrite;
29
34
  }
@@ -35,7 +40,7 @@ export class RuleGenerator {
35
40
  this.projectPath = options.projectPath || process.cwd();
36
41
  this.enableAnalytics = options.enableAnalytics !== false;
37
42
  this.hubEndpoint = options.hubEndpoint || 'https://vdk.tools';
38
- this.ecosystemVersion = '2.1.0';
43
+ this.ecosystemVersion = '3.0.0';
39
44
 
40
45
  // Initialize Components
41
46
  this.mapper = new TechnologyRuleMapper();
@@ -72,7 +72,7 @@ export class TechnologyAnalyzer {
72
72
  console.error(chalk.red(`Error in technology analysis: ${error.message}`));
73
73
  console.error(chalk.gray(error.stack));
74
74
  }
75
- throw new Error(`Technology analysis failed: ${error.message}`);
75
+ throw new Error(`Technology analysis failed: ${error.message}`, { cause: error });
76
76
  }
77
77
  }
78
78
 
@@ -113,6 +113,6 @@ export const PATTERN_RULE_MAP = {
113
113
 
114
114
  // Default values
115
115
  export const DEFAULTS = {
116
- OUTPUT_PATH: './.vdk/rules',
116
+ OUTPUT_PATH: './.vdk/blueprints/rules',
117
117
  PROJECT_NAME: 'Your Project',
118
118
  };
@@ -594,8 +594,8 @@ export class PackageAnalyzer {
594
594
 
595
595
  // Extract dependencies
596
596
  const allDependencies = {
597
- ...(packageJson.dependencies || {}),
598
- ...(packageJson.devDependencies || {}),
597
+ ...packageJson.dependencies,
598
+ ...packageJson.devDependencies,
599
599
  };
600
600
 
601
601
  // Detect technologies based on dependencies
@@ -33,5 +33,5 @@ export async function getVersionAsync() {
33
33
  export function getVersion() {
34
34
  // For simplicity in the synchronous version, we'll just return the current version
35
35
  // This could be enhanced to use fs.readFileSync if needed
36
- return '2.0.0';
36
+ return '3.0.1';
37
37
  }
@@ -333,6 +333,10 @@ export class ProjectContextAnalyzer {
333
333
  }
334
334
  }
335
335
 
336
+ if (platforms.length === 0) {
337
+ return ['claude-code', 'cursor'];
338
+ }
339
+
336
340
  return platforms;
337
341
  }
338
342
 
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Canonical VDK blueprint artifact paths
3
+ * --------------------------------------
4
+ * Raw fetched/generated artifacts are stored by kind under:
5
+ * .vdk/blueprints/{rules,commands,agents,skills,workflows,plugins}
6
+ */
7
+
8
+ export const VDK_ROOT_DIR = '.vdk';
9
+ export const BLUEPRINT_ARTIFACTS_ROOT = `${VDK_ROOT_DIR}/blueprints`;
10
+
11
+ export const BLUEPRINT_ARTIFACT_KINDS = Object.freeze([
12
+ 'rules',
13
+ 'commands',
14
+ 'agents',
15
+ 'skills',
16
+ 'workflows',
17
+ 'plugins',
18
+ ]);
19
+
20
+ export const DEFAULT_BLUEPRINT_RULES_PATH = `${BLUEPRINT_ARTIFACTS_ROOT}/rules`;
21
+
22
+ export function getBlueprintKindPath(kind) {
23
+ const normalizedKind = String(kind || '')
24
+ .toLowerCase()
25
+ .trim();
26
+
27
+ if (!BLUEPRINT_ARTIFACT_KINDS.includes(normalizedKind)) {
28
+ throw new Error(`Unsupported blueprint artifact kind: ${kind}`);
29
+ }
30
+
31
+ return `${BLUEPRINT_ARTIFACTS_ROOT}/${normalizedKind}`;
32
+ }
@@ -359,17 +359,17 @@ const IDE_CONFIGURATIONS = [
359
359
  {
360
360
  id: 'generic-ai',
361
361
  name: 'Generic AI Platform',
362
- configFolder: '.ai',
363
- rulesFolder: '.ai/rules',
364
- configFiles: ['.ai/config.json'],
362
+ configFolder: '.vdk',
363
+ rulesFolder: '.vdk/blueprints/rules',
364
+ configFiles: ['.vdk/config.json'],
365
365
  description: 'Works with most AI coding assistants and platforms.',
366
366
  },
367
367
  {
368
368
  id: 'generic',
369
369
  name: 'Generic AI Tool',
370
- configFolder: '.ai',
371
- rulesFolder: '.ai/rules',
372
- configFiles: ['.ai/config.json'],
370
+ configFolder: '.vdk',
371
+ rulesFolder: '.vdk/blueprints/rules',
372
+ configFiles: ['.vdk/config.json'],
373
373
  description: 'Works with most AI coding assistants and is the VDK CLI standard.',
374
374
  },
375
375
  ];
@@ -532,7 +532,7 @@ function detectSpecificJetBrainsIDEs(projectPath) {
532
532
  });
533
533
  if (hasMatch) matchCount++;
534
534
  }
535
- } catch (_error) {
535
+ } catch {
536
536
  // Ignore directory read errors
537
537
  }
538
538
  } else if (fs.existsSync(filePath)) {
@@ -559,7 +559,7 @@ function detectSpecificJetBrainsIDEs(projectPath) {
559
559
  }
560
560
 
561
561
  // Sort by confidence (highest first)
562
- return detectedIDEs.sort((a, b) => b.confidence - a.confidence);
562
+ return detectedIDEs.toSorted((a, b) => b.confidence - a.confidence);
563
563
  }
564
564
 
565
565
  /**
@@ -9,6 +9,7 @@ import { execSync } from 'node:child_process';
9
9
  import fs from 'node:fs/promises';
10
10
  import path from 'node:path';
11
11
  import { downloadRule, fetchRuleList } from '../blueprints-client.js';
12
+ import { BLUEPRINT_ARTIFACT_KINDS } from './blueprint-artifact-paths.js';
12
13
 
13
14
  /**
14
15
  * Base sync operations that both blueprint and team sync can use
@@ -45,14 +46,18 @@ export class SyncOperations {
45
46
  /**
46
47
  * Sync blueprints from Hub (original blueprint sync logic)
47
48
  */
48
- async syncBlueprintsFromHub(rulesDir, options, spinner) {
49
+ async syncBlueprintsFromHub(artifactsRoot, options, spinner) {
49
50
  const hubResult = await this.hubOps.syncBlueprints(options);
50
51
  const synced = hubResult.blueprints.length;
51
52
 
52
- // Save Hub blueprints
53
+ // Save Hub blueprints by kind
53
54
  for (const blueprint of hubResult.blueprints) {
55
+ const kind = this.resolveBlueprintKind(blueprint);
56
+ const kindDir = path.join(artifactsRoot, kind);
57
+ await fs.mkdir(kindDir, { recursive: true });
58
+
54
59
  const fileName = `${blueprint.slug || blueprint.id}.hub.md`;
55
- const filePath = path.join(rulesDir, fileName);
60
+ const filePath = path.join(kindDir, fileName);
56
61
  await fs.writeFile(filePath, blueprint.content);
57
62
  }
58
63
 
@@ -73,7 +78,7 @@ export class SyncOperations {
73
78
  * Sync team configuration from Hub (team sync logic)
74
79
  */
75
80
  async syncTeamConfigFromHub(projectPath, options, spinner) {
76
- const { teamId, force = false } = options;
81
+ const { teamId } = options;
77
82
  const finalTeamId = teamId || process.env.VDK_TEAM_ID;
78
83
 
79
84
  if (!finalTeamId) {
@@ -134,10 +139,13 @@ export class SyncOperations {
134
139
  /**
135
140
  * Sync blueprints from repository (original repository sync logic)
136
141
  */
137
- async syncBlueprintsFromRepository(rulesDir, options, spinner) {
142
+ async syncBlueprintsFromRepository(artifactsRoot, options, spinner) {
138
143
  const { force, category } = options;
139
144
  const remoteRules = await fetchRuleList();
140
145
 
146
+ const rulesDir = path.join(artifactsRoot, 'rules');
147
+ await fs.mkdir(rulesDir, { recursive: true });
148
+
141
149
  if (remoteRules.length === 0) {
142
150
  spinner.fail('No blueprints found in repository or failed to connect');
143
151
  return { synced: 0 };
@@ -189,8 +197,6 @@ export class SyncOperations {
189
197
  * Sync team configuration from Git (team sync logic)
190
198
  */
191
199
  async syncTeamConfigFromGit(projectPath, options, spinner) {
192
- const { force = false } = options;
193
-
194
200
  // Check if this is a Git repository
195
201
  try {
196
202
  execSync('git rev-parse --git-dir', { cwd: projectPath, stdio: 'ignore' });
@@ -202,14 +208,14 @@ export class SyncOperations {
202
208
  if (!options.dryRun) {
203
209
  try {
204
210
  execSync('git pull', { cwd: projectPath, stdio: 'inherit' });
205
- } catch (_error) {
211
+ } catch {
206
212
  this.command.logWarning('⚠️ Git pull failed - continuing with local files');
207
213
  }
208
214
  }
209
215
 
210
216
  // Check for VDK files in repository
211
217
  const vdkFiles = [];
212
- const filesToCheck = ['.vdk/rules/', '.vdk/config.json'];
218
+ const filesToCheck = ['.vdk/blueprints/', '.vdk/config.json'];
213
219
 
214
220
  for (const file of filesToCheck) {
215
221
  const fullPath = path.join(projectPath, file);
@@ -300,9 +306,11 @@ export class SyncOperations {
300
306
  async applyHubConfiguration(projectPath, teamConfig) {
301
307
  const appliedFiles = [];
302
308
  const vdkPath = path.join(projectPath, '.vdk');
309
+ const blueprintsPath = path.join(vdkPath, 'blueprints');
303
310
 
304
311
  // Ensure .vdk directory exists
305
312
  await fs.mkdir(vdkPath, { recursive: true });
313
+ await fs.mkdir(blueprintsPath, { recursive: true });
306
314
 
307
315
  // Apply main configuration
308
316
  if (teamConfig.main) {
@@ -311,18 +319,77 @@ export class SyncOperations {
311
319
  appliedFiles.push('.vdk/config.json');
312
320
  }
313
321
 
314
- // Apply rules
322
+ // Apply rule artifacts from legacy team payload
315
323
  if (teamConfig.rules) {
316
- const rulesPath = path.join(vdkPath, 'rules');
317
- await fs.mkdir(rulesPath, { recursive: true });
324
+ await this.writeBlueprintKindFiles(blueprintsPath, 'rules', teamConfig.rules, appliedFiles);
325
+ }
318
326
 
319
- for (const [filename, content] of Object.entries(teamConfig.rules)) {
320
- const rulePath = path.join(rulesPath, filename);
321
- await fs.writeFile(rulePath, content);
322
- appliedFiles.push(`.vdk/rules/${filename}`);
327
+ // Apply structured artifact payload by kind
328
+ if (teamConfig.blueprints && typeof teamConfig.blueprints === 'object') {
329
+ for (const [kind, files] of Object.entries(teamConfig.blueprints)) {
330
+ await this.writeBlueprintKindFiles(blueprintsPath, kind, files, appliedFiles);
323
331
  }
324
332
  }
325
333
 
326
334
  return appliedFiles;
327
335
  }
336
+
337
+ resolveBlueprintKind(blueprint) {
338
+ const normalizedKind = String(blueprint?.kind || '')
339
+ .toLowerCase()
340
+ .trim();
341
+
342
+ if (BLUEPRINT_ARTIFACT_KINDS.includes(normalizedKind)) {
343
+ return normalizedKind;
344
+ }
345
+
346
+ const sourceCandidates = [
347
+ blueprint?.sourcePath,
348
+ blueprint?.path,
349
+ blueprint?.slug,
350
+ blueprint?.id,
351
+ ]
352
+ .filter(Boolean)
353
+ .map(value => String(value).toLowerCase());
354
+
355
+ for (const candidate of sourceCandidates) {
356
+ for (const kind of BLUEPRINT_ARTIFACT_KINDS) {
357
+ if (
358
+ candidate.includes(`/library/${kind}/`) ||
359
+ candidate.includes(`/${kind}/`) ||
360
+ candidate.startsWith(`${kind}/`)
361
+ ) {
362
+ return kind;
363
+ }
364
+ }
365
+ }
366
+
367
+ throw new Error(
368
+ `Unable to determine blueprint kind for item: ${blueprint?.id || blueprint?.slug || 'unknown'}`
369
+ );
370
+ }
371
+
372
+ async writeBlueprintKindFiles(blueprintsPath, kind, files, appliedFiles) {
373
+ const normalizedKind = String(kind || '')
374
+ .toLowerCase()
375
+ .trim();
376
+
377
+ if (!BLUEPRINT_ARTIFACT_KINDS.includes(normalizedKind)) {
378
+ throw new Error(`Unsupported blueprint artifact kind: ${kind}`);
379
+ }
380
+
381
+ if (!files || typeof files !== 'object') {
382
+ return;
383
+ }
384
+
385
+ const kindPath = path.join(blueprintsPath, normalizedKind);
386
+ await fs.mkdir(kindPath, { recursive: true });
387
+
388
+ for (const [relativeFilePath, content] of Object.entries(files)) {
389
+ const outputPath = path.join(kindPath, relativeFilePath);
390
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
391
+ await fs.writeFile(outputPath, content);
392
+ appliedFiles.push(`.vdk/blueprints/${normalizedKind}/${relativeFilePath}`);
393
+ }
394
+ }
328
395
  }
@@ -20,7 +20,7 @@ export const fileSystem = {
20
20
  try {
21
21
  return await fs.readFile(filePath, encoding);
22
22
  } catch (error) {
23
- throw new Error(`Failed to read file ${filePath}: ${error.message}`);
23
+ throw new Error(`Failed to read file ${filePath}: ${error.message}`, { cause: error });
24
24
  }
25
25
  },
26
26
 
@@ -35,7 +35,7 @@ export const fileSystem = {
35
35
 
36
36
  return await fs.writeFile(filePath, data, options);
37
37
  } catch (error) {
38
- throw new Error(`Failed to write file ${filePath}: ${error.message}`);
38
+ throw new Error(`Failed to write file ${filePath}: ${error.message}`, { cause: error });
39
39
  }
40
40
  },
41
41
 
@@ -49,7 +49,7 @@ export const fileSystem = {
49
49
 
50
50
  return await fs.appendFile(filePath, data, options);
51
51
  } catch (error) {
52
- throw new Error(`Failed to append to file ${filePath}: ${error.message}`);
52
+ throw new Error(`Failed to append to file ${filePath}: ${error.message}`, { cause: error });
53
53
  }
54
54
  },
55
55
 
@@ -72,7 +72,7 @@ export const fileSystem = {
72
72
  try {
73
73
  return await fs.stat(filePath);
74
74
  } catch (error) {
75
- throw new Error(`Failed to get stats for ${filePath}: ${error.message}`);
75
+ throw new Error(`Failed to get stats for ${filePath}: ${error.message}`, { cause: error });
76
76
  }
77
77
  },
78
78
 
@@ -107,7 +107,7 @@ export const fileSystem = {
107
107
  try {
108
108
  await fs.mkdir(dirPath, { recursive: true });
109
109
  } catch (error) {
110
- throw new Error(`Failed to create directory ${dirPath}: ${error.message}`);
110
+ throw new Error(`Failed to create directory ${dirPath}: ${error.message}`, { cause: error });
111
111
  }
112
112
  },
113
113
 
@@ -119,7 +119,7 @@ export const fileSystem = {
119
119
  await fs.unlink(filePath);
120
120
  } catch (error) {
121
121
  if (error.code !== 'ENOENT') {
122
- throw new Error(`Failed to remove file ${filePath}: ${error.message}`);
122
+ throw new Error(`Failed to remove file ${filePath}: ${error.message}`, { cause: error });
123
123
  }
124
124
  }
125
125
  },
@@ -131,7 +131,7 @@ export const fileSystem = {
131
131
  try {
132
132
  await fs.rm(dirPath, { recursive: true, force: true });
133
133
  } catch (error) {
134
- throw new Error(`Failed to remove directory ${dirPath}: ${error.message}`);
134
+ throw new Error(`Failed to remove directory ${dirPath}: ${error.message}`, { cause: error });
135
135
  }
136
136
  },
137
137
 
@@ -142,7 +142,7 @@ export const fileSystem = {
142
142
  try {
143
143
  return await fs.readdir(dirPath);
144
144
  } catch (error) {
145
- throw new Error(`Failed to read directory ${dirPath}: ${error.message}`);
145
+ throw new Error(`Failed to read directory ${dirPath}: ${error.message}`, { cause: error });
146
146
  }
147
147
  },
148
148
 
@@ -156,7 +156,7 @@ export const fileSystem = {
156
156
 
157
157
  await fs.copyFile(src, dest);
158
158
  } catch (error) {
159
- throw new Error(`Failed to copy ${src} to ${dest}: ${error.message}`);
159
+ throw new Error(`Failed to copy ${src} to ${dest}: ${error.message}`, { cause: error });
160
160
  }
161
161
  },
162
162
 
@@ -170,7 +170,7 @@ export const fileSystem = {
170
170
 
171
171
  await fs.rename(src, dest);
172
172
  } catch (error) {
173
- throw new Error(`Failed to move ${src} to ${dest}: ${error.message}`);
173
+ throw new Error(`Failed to move ${src} to ${dest}: ${error.message}`, { cause: error });
174
174
  }
175
175
  },
176
176
 
@@ -182,7 +182,7 @@ export const fileSystem = {
182
182
  const content = await this.readFile(filePath);
183
183
  return JSON.parse(content);
184
184
  } catch (error) {
185
- throw new Error(`Failed to read JSON from ${filePath}: ${error.message}`);
185
+ throw new Error(`Failed to read JSON from ${filePath}: ${error.message}`, { cause: error });
186
186
  }
187
187
  },
188
188
 
@@ -194,7 +194,7 @@ export const fileSystem = {
194
194
  const content = JSON.stringify(data, null, indent);
195
195
  await this.writeFile(filePath, content);
196
196
  } catch (error) {
197
- throw new Error(`Failed to write JSON to ${filePath}: ${error.message}`);
197
+ throw new Error(`Failed to write JSON to ${filePath}: ${error.message}`, { cause: error });
198
198
  }
199
199
  },
200
200
 
@@ -225,7 +225,7 @@ export const fileSystem = {
225
225
  }
226
226
  }
227
227
  }
228
- } catch (_error) {
228
+ } catch {
229
229
  // Skip directories we can't read
230
230
  }
231
231
  }
@@ -242,7 +242,7 @@ export const fileSystem = {
242
242
  const stats = await fs.stat(filePath);
243
243
  return stats.size;
244
244
  } catch (error) {
245
- throw new Error(`Failed to get size of ${filePath}: ${error.message}`);
245
+ throw new Error(`Failed to get size of ${filePath}: ${error.message}`, { cause: error });
246
246
  }
247
247
  },
248
248
 
@@ -254,7 +254,9 @@ export const fileSystem = {
254
254
  const stats = await fs.stat(filePath);
255
255
  return stats.mtime;
256
256
  } catch (error) {
257
- throw new Error(`Failed to get modification time of ${filePath}: ${error.message}`);
257
+ throw new Error(`Failed to get modification time of ${filePath}: ${error.message}`, {
258
+ cause: error,
259
+ });
258
260
  }
259
261
  },
260
262
 
@@ -25,7 +25,6 @@ export function generateSafeFilename(input, options = {}) {
25
25
  extension = '',
26
26
  preserveCase = false,
27
27
  separator = '-',
28
- allowedChars = /[a-z0-9-_]/,
29
28
  fallbackName = 'untitled',
30
29
  } = options;
31
30
 
@@ -60,9 +59,9 @@ export function generateSafeFilename(input, options = {}) {
60
59
 
61
60
  /**
62
61
  * Generate unique filename for Cursor IDE rules
63
- * Handles the specific requirements for Cursor .cursorrules format
62
+ * Handles canonical Cursor `.cursor/rules/*.mdc` naming requirements
64
63
  * @param {Object} rule - Rule object with frontmatter
65
- * @returns {string} Unique filename
64
+ * @returns {string} Unique base filename (without extension)
66
65
  */
67
66
  export function generateCursorFilename(rule) {
68
67
  const category = rule.frontmatter?.category || 'general';
@@ -96,7 +95,6 @@ export function generateCursorFilename(rule) {
96
95
 
97
96
  return generateSafeFilename(baseName, {
98
97
  maxLength: 40,
99
- extension: '.md',
100
98
  separator: '-',
101
99
  });
102
100
  }
@@ -58,7 +58,7 @@ async function loadSchemaDefinition(schemaName) {
58
58
  const schemaContent = await fs.readFile(schemaPath, 'utf8');
59
59
  return JSON.parse(schemaContent);
60
60
  } catch (error) {
61
- throw new Error(`Failed to load schema '${schemaName}': ${error.message}`);
61
+ throw new Error(`Failed to load schema '${schemaName}': ${error.message}`, { cause: error });
62
62
  }
63
63
  }
64
64
 
@@ -133,7 +133,7 @@ async function updateMCPConfig() {
133
133
  // Then, check common rule directories if none found from IDEs
134
134
  if (ruleDirectories.length === 0) {
135
135
  const commonPaths = [
136
- '.vdk/rules',
136
+ '.vdk/blueprints/rules',
137
137
  '.vscode/ai-rules',
138
138
  '.vscode-insiders/ai-rules',
139
139
  '.vscode-oss/ai-rules',
@@ -164,7 +164,7 @@ async function updateMCPConfig() {
164
164
 
165
165
  // If force option is provided, use the default path
166
166
  if (options.force) {
167
- const defaultPath = path.join(options.path, '.vdk/rules');
167
+ const defaultPath = path.join(options.path, '.vdk/blueprints/rules');
168
168
  console.log(`${colors.yellow}Creating default rule directory: ${defaultPath}${colors.reset}`);
169
169
 
170
170
  if (!fs.existsSync(defaultPath)) {
@@ -39,7 +39,7 @@ async function checkDuplicateRules() {
39
39
  try {
40
40
  await fs.access(dirPath);
41
41
  dirExists = true;
42
- } catch (_err) {
42
+ } catch {
43
43
  continue;
44
44
  }
45
45
 
@@ -21,13 +21,13 @@ const __dirname = pathUtils.getDirname(__filename);
21
21
  // Rule repository paths
22
22
  const rulesRootDir = pathUtils.join(__dirname, '../..');
23
23
  const ruleDirectories = [
24
- '.vdk/rules',
25
- '.vdk/rules/assistants',
26
- '.vdk/rules/languages',
27
- '.vdk/rules/stacks',
28
- '.vdk/rules/tasks',
29
- '.vdk/rules/technologies',
30
- '.vdk/rules/tools',
24
+ '.vdk/blueprints/rules',
25
+ '.vdk/blueprints/rules/assistants',
26
+ '.vdk/blueprints/rules/languages',
27
+ '.vdk/blueprints/rules/stacks',
28
+ '.vdk/blueprints/rules/tasks',
29
+ '.vdk/blueprints/rules/technologies',
30
+ '.vdk/blueprints/rules/tools',
31
31
  ];
32
32
 
33
33
  // Track all rule IDs to check for duplicates