korext 0.9.10 → 0.9.12

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 (2) hide show
  1. package/bin/korext.js +82 -16
  2. package/package.json +1 -1
package/bin/korext.js CHANGED
@@ -296,13 +296,22 @@ async function fetchAndCacheRules() {
296
296
  * Mirrors korext-core/src/engine/localEngine.ts analyzeLocally().
297
297
  */
298
298
  function analyzeLocally(code, packId, definitions) {
299
- const pack = definitions.packs[packId];
300
- if (!pack) return [];
299
+ // Normalize packId to array to support multi-pack (e.g. ["web", "pci-dss-v1"])
300
+ const packIdList = Array.isArray(packId) ? packId : [packId];
301
+ // Merge rules from all requested packs via Set
302
+ const mergedRuleIds = new Set();
303
+ for (const pid of packIdList) {
304
+ const pack = definitions.packs[pid];
305
+ if (pack && pack.rules) {
306
+ for (const rid of pack.rules) mergedRuleIds.add(rid);
307
+ }
308
+ }
309
+ if (mergedRuleIds.size === 0) return [];
301
310
 
302
311
  const violations = [];
303
312
  const lines = code.split('\n');
304
313
 
305
- for (const ruleId of pack.rules) {
314
+ for (const ruleId of mergedRuleIds) {
306
315
  const rule = definitions.rules[ruleId];
307
316
  if (!rule) continue;
308
317
  if (rule.checkMode === 'server-only') continue;
@@ -630,18 +639,65 @@ program
630
639
  });
631
640
 
632
641
  console.log();
633
- const industryInput = await ask(chalk.bold(' Select industries (comma-separated numbers or IDs): '));
642
+ const industryInput = await ask(chalk.bold(' Select industries or packs (numbers, IDs, or names): '));
643
+
644
+ // Parse user input: accept numbers, industry IDs, industry names (fuzzy), or pack IDs
645
+ const rawTokens = industryInput.split(',').map(s => s.trim()).filter(Boolean);
646
+ const selectedIndustries = [];
647
+ const directPacks = [];
634
648
 
635
- // Parse user input (numbers or IDs)
636
- const selectedIndustries = industryInput.split(',').map(s => s.trim()).filter(Boolean).map(s => {
637
- const num = parseInt(s, 10);
649
+ for (const token of rawTokens) {
650
+ const lower = token.toLowerCase();
651
+
652
+ // Strategy 1: Number (e.g., "8" for Finance)
653
+ const num = parseInt(token, 10);
638
654
  if (!isNaN(num) && num >= 1 && num <= sortedIndustries.length) {
639
- return sortedIndustries[num - 1][0];
655
+ selectedIndustries.push(sortedIndustries[num - 1][0]);
656
+ continue;
657
+ }
658
+
659
+ // Strategy 2: Industry ID exact match (e.g., "finance")
660
+ if (taxonomy.industries[lower]) {
661
+ selectedIndustries.push(lower);
662
+ continue;
640
663
  }
641
- return s.toLowerCase();
642
- }).filter(id => taxonomy.industries[id]);
643
664
 
644
- if (selectedIndustries.length === 0) {
665
+ // Strategy 3: Industry label fuzzy match (e.g., "Finance", "health", "aero")
666
+ const labelMatch = sortedIndustries.find(([id, group]) =>
667
+ group.label.toLowerCase().startsWith(lower)
668
+ );
669
+ if (labelMatch) {
670
+ selectedIndustries.push(labelMatch[0]);
671
+ console.log(chalk.dim(` "${token}" matched industry: ${labelMatch[1].label}`));
672
+ continue;
673
+ }
674
+
675
+ // Strategy 4: Direct pack ID (e.g., "web", "pci-dss-v1", "security")
676
+ const allPackIds = Object.keys(defs.packs);
677
+ if (allPackIds.includes(lower)) {
678
+ directPacks.push(lower);
679
+ console.log(chalk.dim(` "${token}" is a pack ID, adding directly.`));
680
+ continue;
681
+ }
682
+
683
+ // Nothing matched
684
+ console.log(chalk.yellow(` Unknown input: "${token}" (not an industry, pack ID, or name)`));
685
+ }
686
+
687
+ // If user typed only pack IDs (e.g., "web" or "web, security"), skip region prompt
688
+ if (selectedIndustries.length === 0 && directPacks.length > 0) {
689
+ const config = {
690
+ targetPacks: directPacks,
691
+ exclude: ['node_modules', 'dist', 'build', '.next']
692
+ };
693
+ fs.writeFileSync(outputPath, JSON.stringify(config, null, 2));
694
+ console.log(chalk.green(`\n Created ${outputPath} with packs: ${directPacks.join(', ')}\n`));
695
+ console.log(chalk.dim(` Run: ${chalk.green('korext enforce .')} to enforce these packs.\n`));
696
+ rl.close();
697
+ process.exit(0);
698
+ }
699
+
700
+ if (selectedIndustries.length === 0 && directPacks.length === 0) {
645
701
  console.log(chalk.yellow('\n No valid industries selected. Using default: web\n'));
646
702
  const config = {
647
703
  targetPacks: ['web'],
@@ -676,22 +732,25 @@ program
676
732
  }
677
733
  }
678
734
 
679
- // Resolve packs
735
+ // Resolve packs from industries
680
736
  const resolvedPacks = resolvePacksForIndustryRegion(
681
737
  selectedIndustries.join(','),
682
738
  selectedRegion,
683
739
  taxonomy
684
740
  );
685
741
 
686
- console.log(chalk.green(`\n Resolved ${resolvedPacks.length} packs for ${selectedIndustries.map(id => INDUSTRY_LABELS[id]).join(', ')}${selectedRegion ? ` (${REGION_LABELS[selectedRegion] || selectedRegion})` : ''}:`));
687
- for (const pid of resolvedPacks) {
742
+ // Merge any direct pack IDs the user typed alongside industries
743
+ const allPacks = [...new Set([...resolvedPacks, ...directPacks])];
744
+
745
+ console.log(chalk.green(`\n Resolved ${allPacks.length} packs for ${selectedIndustries.map(id => INDUSTRY_LABELS[id]).join(', ')}${selectedRegion ? ` (${REGION_LABELS[selectedRegion] || selectedRegion})` : ''}${directPacks.length > 0 ? ` + ${directPacks.join(', ')}` : ''}:`));
746
+ for (const pid of allPacks) {
688
747
  console.log(` ${chalk.cyan(pid)}`);
689
748
  }
690
749
 
691
750
  const config = {
692
751
  industry: selectedIndustries.join(','),
693
752
  ...(selectedRegion && { region: selectedRegion }),
694
- targetPacks: resolvedPacks,
753
+ targetPacks: allPacks,
695
754
  exclude: ['node_modules', 'dist', 'build', '.next']
696
755
  };
697
756
 
@@ -980,7 +1039,14 @@ program
980
1039
  console.log(chalk.yellow('\n⚡ Offline mode: using local rule engine (regex-based analysis)'));
981
1040
  console.log(chalk.dim(` Cached rules: v${localDefinitions.version} · ${localDefinitions.ruleCount} rules · ${localDefinitions.packCount} packs`));
982
1041
  // Show rule coverage breakdown
983
- const packRules = localDefinitions.packs?.[pack]?.rules || [];
1042
+ // Merge rules from all packs for coverage display
1043
+ const coveragePackList = Array.isArray(pack) ? pack : [pack];
1044
+ const allPackRuleIds = new Set();
1045
+ for (const pid of coveragePackList) {
1046
+ const pr = localDefinitions.packs?.[pid]?.rules || [];
1047
+ for (const rid of pr) allPackRuleIds.add(rid);
1048
+ }
1049
+ const packRules = [...allPackRuleIds];
984
1050
  const availableCount = packRules.filter(r => localDefinitions.rules?.[r]).length;
985
1051
  const serverOnlyCount = packRules.length - availableCount;
986
1052
  console.log(chalk.dim(` Offline mode: ${availableCount} of ${packRules.length} rules available. ${serverOnlyCount} rules require server analysis.`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "korext",
3
- "version": "0.9.10",
3
+ "version": "0.9.12",
4
4
  "mcpName": "io.github.Korext/governance",
5
5
  "description": "Korext Command Line Interface",
6
6
  "type": "module",