speccrew 0.6.67 → 0.6.69

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.
@@ -66,6 +66,16 @@
66
66
  const fs = require('fs');
67
67
  const path = require('path');
68
68
 
69
+ // Platform type to tech-stack-mappings category mapping
70
+ const PLATFORM_TYPE_TO_CATEGORY = {
71
+ 'frontend': 'web',
72
+ 'web': 'web',
73
+ 'backend': 'backend',
74
+ 'mobile': 'mobile',
75
+ 'desktop': 'desktop',
76
+ 'api': 'api'
77
+ };
78
+
69
79
  /**
70
80
  * Parse array parameter that supports both JSON format and comma-separated format.
71
81
  * JSON format: '["vue","typescript"]'
@@ -264,12 +274,15 @@ function loadTechStackConfig(platformType, framework, projectRoot) {
264
274
  const configContent = fs.readFileSync(configPath, 'utf8');
265
275
  const config = JSON.parse(configContent);
266
276
 
277
+ // Map platformType to tech-stack-mappings category
278
+ const techCategory = PLATFORM_TYPE_TO_CATEGORY[platformType] || platformType;
279
+
267
280
  // Try exact match first
268
281
  let techConfig = null;
269
282
  if (config.tech_stacks &&
270
- config.tech_stacks[platformType] &&
271
- config.tech_stacks[platformType][framework]) {
272
- techConfig = config.tech_stacks[platformType][framework];
283
+ config.tech_stacks[techCategory] &&
284
+ config.tech_stacks[techCategory][framework]) {
285
+ techConfig = config.tech_stacks[techCategory][framework];
273
286
  }
274
287
 
275
288
  // If not found, try normalized identifier (remove language prefix)
@@ -277,9 +290,9 @@ function loadTechStackConfig(platformType, framework, projectRoot) {
277
290
  const normalizedFramework = normalizeTechIdentifier(framework);
278
291
  if (normalizedFramework !== framework &&
279
292
  config.tech_stacks &&
280
- config.tech_stacks[platformType] &&
281
- config.tech_stacks[platformType][normalizedFramework]) {
282
- techConfig = config.tech_stacks[platformType][normalizedFramework];
293
+ config.tech_stacks[techCategory] &&
294
+ config.tech_stacks[techCategory][normalizedFramework]) {
295
+ techConfig = config.tech_stacks[techCategory][normalizedFramework];
283
296
  console.log(`Using normalized tech identifier: ${framework} → ${normalizedFramework}`);
284
297
  }
285
298
  }
@@ -295,6 +308,37 @@ function loadTechStackConfig(platformType, framework, projectRoot) {
295
308
  return { extensions: [], exclude_file_suffixes: [], exclude_file_names: [] };
296
309
  }
297
310
 
311
+ /**
312
+ * Validate entry-dirs JSON schema
313
+ * @param {object} data - Parsed entry-dirs JSON data
314
+ * @returns {string[]|null} Array of error messages or null if valid
315
+ */
316
+ function validateEntryDirsSchema(data) {
317
+ const errors = [];
318
+ if (!data.platformId || typeof data.platformId !== 'string') {
319
+ errors.push('platformId must be a non-empty string');
320
+ } else if (!/^[a-zA-Z0-9_-]+$/.test(data.platformId)) {
321
+ errors.push(`platformId format invalid: "${data.platformId}" (only alphanumeric, hyphen, underscore allowed)`);
322
+ }
323
+ if (!data.sourcePath || typeof data.sourcePath !== 'string') {
324
+ errors.push('sourcePath must be a non-empty string');
325
+ }
326
+ if (!Array.isArray(data.modules) || data.modules.length === 0) {
327
+ errors.push('modules must be a non-empty array');
328
+ } else {
329
+ for (let i = 0; i < data.modules.length; i++) {
330
+ const mod = data.modules[i];
331
+ if (!mod.name || typeof mod.name !== 'string') {
332
+ errors.push(`modules[${i}].name must be a non-empty string`);
333
+ }
334
+ if (!Array.isArray(mod.entryDirs) || mod.entryDirs.length === 0) {
335
+ errors.push(`modules[${i}].entryDirs must be a non-empty array`);
336
+ }
337
+ }
338
+ }
339
+ return errors.length > 0 ? errors : null;
340
+ }
341
+
298
342
  /**
299
343
  * Infer platform info from platformId
300
344
  * @param {string} platformId - Platform ID like "backend-ai", "web-vue", "mobile-uniapp"
@@ -533,7 +577,7 @@ function generateFromEntryDirs(entryDirsData, platformConfig, projectRoot, outpu
533
577
  platformName: platformConfig.platformName,
534
578
  platformType: platformType,
535
579
  sourcePath: sourcePath,
536
- techStack: [framework],
580
+ techStack: entryDirsData.techStack || [framework],
537
581
  modules: [...new Set(moduleNames)].sort(),
538
582
  totalFiles: features.length,
539
583
  analyzedCount: 0,
@@ -667,7 +711,15 @@ function main() {
667
711
  console.error(`Found top-level keys: ${foundKeys}`);
668
712
  process.exit(1);
669
713
  }
670
-
714
+
715
+ // Validate entry-dirs JSON schema
716
+ const schemaErrors = validateEntryDirsSchema(entryDirsData);
717
+ if (schemaErrors) {
718
+ console.error('Error: entry-dirs JSON schema validation failed:');
719
+ schemaErrors.forEach(err => console.error(` - ${err}`));
720
+ process.exit(1);
721
+ }
722
+
671
723
  // Find project root (use current directory or entryDirsFile directory)
672
724
  const projectRoot = findProjectRoot(path.dirname(entryDirsFilePath));
673
725
 
@@ -718,7 +770,10 @@ function main() {
718
770
  outputDir = path.resolve(params.outputDir);
719
771
  console.log(`Using outputDir from parameter: ${outputDir}`);
720
772
  } else {
773
+ console.warn('WARNING: --outputDir not specified, falling back to default path.');
774
+ console.warn(' Recommended: explicitly pass --outputDir to avoid incorrect output location.');
721
775
  outputDir = path.join(projectRoot, 'speccrew-workspace', 'knowledges', 'base', 'sync-state', 'knowledge-bizs');
776
+ console.warn(` Using fallback outputDir: ${outputDir}`);
722
777
  }
723
778
 
724
779
  // Generate features from entry dirs
@@ -762,25 +817,28 @@ function main() {
762
817
  const configContent = fs.readFileSync(configPath, 'utf8');
763
818
  const config = JSON.parse(configContent);
764
819
 
820
+ // Map platformType to tech-stack-mappings category
821
+ const techCategory = PLATFORM_TYPE_TO_CATEGORY[platformType] || platformType;
822
+
765
823
  // Load tech-stack-specific exclude_dirs
766
824
  let techExcludeDirs = [];
767
825
  let effectiveTechIdentifier = techIdentifier;
768
826
 
769
827
  // Try exact match first
770
828
  if (config.tech_stacks &&
771
- config.tech_stacks[platformType] &&
772
- config.tech_stacks[platformType][techIdentifier] &&
773
- config.tech_stacks[platformType][techIdentifier].exclude_dirs) {
774
- techExcludeDirs = config.tech_stacks[platformType][techIdentifier].exclude_dirs;
829
+ config.tech_stacks[techCategory] &&
830
+ config.tech_stacks[techCategory][techIdentifier] &&
831
+ config.tech_stacks[techCategory][techIdentifier].exclude_dirs) {
832
+ techExcludeDirs = config.tech_stacks[techCategory][techIdentifier].exclude_dirs;
775
833
  } else {
776
834
  // Try normalized identifier (remove language prefix like "python-fastapi" → "fastapi")
777
835
  const normalizedIdentifier = normalizeTechIdentifier(techIdentifier);
778
836
  if (normalizedIdentifier !== techIdentifier &&
779
837
  config.tech_stacks &&
780
- config.tech_stacks[platformType] &&
781
- config.tech_stacks[platformType][normalizedIdentifier] &&
782
- config.tech_stacks[platformType][normalizedIdentifier].exclude_dirs) {
783
- techExcludeDirs = config.tech_stacks[platformType][normalizedIdentifier].exclude_dirs;
838
+ config.tech_stacks[techCategory] &&
839
+ config.tech_stacks[techCategory][normalizedIdentifier] &&
840
+ config.tech_stacks[techCategory][normalizedIdentifier].exclude_dirs) {
841
+ techExcludeDirs = config.tech_stacks[techCategory][normalizedIdentifier].exclude_dirs;
784
842
  effectiveTechIdentifier = normalizedIdentifier;
785
843
  console.log(`Using normalized tech identifier for exclude_dirs: ${techIdentifier} → ${normalizedIdentifier}`);
786
844
  }
@@ -796,10 +854,10 @@ function main() {
796
854
 
797
855
  // Load tech-stack-specific exclude_file_suffixes
798
856
  if (config.tech_stacks &&
799
- config.tech_stacks[platformType] &&
800
- config.tech_stacks[platformType][effectiveTechIdentifier] &&
801
- config.tech_stacks[platformType][effectiveTechIdentifier].exclude_file_suffixes) {
802
- excludeFileSuffixes = config.tech_stacks[platformType][effectiveTechIdentifier].exclude_file_suffixes;
857
+ config.tech_stacks[techCategory] &&
858
+ config.tech_stacks[techCategory][effectiveTechIdentifier] &&
859
+ config.tech_stacks[techCategory][effectiveTechIdentifier].exclude_file_suffixes) {
860
+ excludeFileSuffixes = config.tech_stacks[techCategory][effectiveTechIdentifier].exclude_file_suffixes;
803
861
  if (excludeFileSuffixes.length > 0) {
804
862
  console.log(`Loaded exclude_file_suffixes from tech-stack-mappings.json: ${excludeFileSuffixes.join(', ')}`);
805
863
  }
@@ -807,10 +865,10 @@ function main() {
807
865
 
808
866
  // Load tech-stack-specific exclude_file_names
809
867
  if (config.tech_stacks &&
810
- config.tech_stacks[platformType] &&
811
- config.tech_stacks[platformType][effectiveTechIdentifier] &&
812
- config.tech_stacks[platformType][effectiveTechIdentifier].exclude_file_names) {
813
- excludeFileNames = config.tech_stacks[platformType][effectiveTechIdentifier].exclude_file_names;
868
+ config.tech_stacks[techCategory] &&
869
+ config.tech_stacks[techCategory][effectiveTechIdentifier] &&
870
+ config.tech_stacks[techCategory][effectiveTechIdentifier].exclude_file_names) {
871
+ excludeFileNames = config.tech_stacks[techCategory][effectiveTechIdentifier].exclude_file_names;
814
872
  if (excludeFileNames.length > 0) {
815
873
  console.log(`Loaded exclude_file_names from tech-stack-mappings.json: ${excludeFileNames.join(', ')}`);
816
874
  }
@@ -840,7 +898,10 @@ function main() {
840
898
  syncStateDir = path.resolve(params.outputDir);
841
899
  console.log(`Using outputDir from parameter: ${syncStateDir}`);
842
900
  } else {
901
+ console.warn('WARNING: --outputDir not specified, falling back to default path.');
902
+ console.warn(' Recommended: explicitly pass --outputDir to avoid incorrect output location.');
843
903
  syncStateDir = path.join(projectRoot, 'speccrew-workspace', 'knowledges', 'base', 'sync-state', 'knowledge-bizs');
904
+ console.warn(` Using fallback outputDir: ${syncStateDir}`);
844
905
  }
845
906
  const outputPath = path.join(syncStateDir, outputFileName);
846
907
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "speccrew",
3
- "version": "0.6.67",
3
+ "version": "0.6.69",
4
4
  "description": "Spec-Driven Development toolkit for AI-powered IDEs",
5
5
  "author": "charlesmu99",
6
6
  "repository": {