korext 1.0.0 → 1.0.1

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 +76 -17
  2. package/package.json +1 -1
package/bin/korext.js CHANGED
@@ -295,6 +295,15 @@ async function fetchAndCacheRules() {
295
295
  * Run rules locally against code using cached regex definitions.
296
296
  * Mirrors korext-core/src/engine/localEngine.ts analyzeLocally().
297
297
  */
298
+ // M1: Check if a line is a comment (skip from local engine scanning)
299
+ function isCommentLine(line) {
300
+ const trimmed = line.trim();
301
+ return trimmed.startsWith('//')
302
+ || trimmed.startsWith('*')
303
+ || trimmed.startsWith('/*')
304
+ || trimmed.startsWith('#');
305
+ }
306
+
298
307
  function analyzeLocally(code, packId, definitions) {
299
308
  // Normalize packId to array to support multi-pack (e.g. ["web", "pci-dss-v1"])
300
309
  const packIdList = Array.isArray(packId) ? packId : [packId];
@@ -321,6 +330,8 @@ function analyzeLocally(code, packId, definitions) {
321
330
  try {
322
331
  const re = new RegExp(pattern.regex, pattern.flags || 'gi');
323
332
  lines.forEach((line, i) => {
333
+ // M1: Skip comment lines to avoid false positives
334
+ if (isCommentLine(line)) return;
324
335
  re.lastIndex = 0;
325
336
  if (re.test(line)) {
326
337
  violations.push({
@@ -371,7 +382,12 @@ const program = new Command();
371
382
  program
372
383
  .name('korext')
373
384
  .description('Korext Command Line Interface - Enterprise Policy Enforcement')
374
- .version(version);
385
+ .version(version)
386
+ .action(() => {
387
+ // H3: Show help and exit 0 when no subcommand is provided
388
+ program.outputHelp();
389
+ process.exit(0);
390
+ });
375
391
 
376
392
  program
377
393
  .command('login [token]')
@@ -859,6 +875,13 @@ program
859
875
  const format = options.format.toLowerCase();
860
876
  const isText = format === 'text';
861
877
 
878
+ // H1: Validate output format before proceeding
879
+ const SUPPORTED_FORMATS = ['text', 'json', 'sarif'];
880
+ if (!SUPPORTED_FORMATS.includes(format)) {
881
+ console.error(chalk.red(`\n\u2716 Unsupported format: "${format}". Supported formats: ${SUPPORTED_FORMATS.join(', ')}`));
882
+ process.exit(2);
883
+ }
884
+
862
885
  // ── Workspace config reader ──────────────────────────────────────────────
863
886
  function loadWorkspaceConfig(targetDir) {
864
887
  const configPaths = [
@@ -919,7 +942,7 @@ program
919
942
  } else {
920
943
  console.error(JSON.stringify({ error: `Unknown pack ID(s): ${unknownPacks.join(', ')}` }));
921
944
  }
922
- process.exit(1);
945
+ process.exit(2); // C2: Error exit for invalid pack ID
923
946
  }
924
947
  }
925
948
  } else if (options.industry) {
@@ -928,14 +951,14 @@ program
928
951
  if (!taxonomy) {
929
952
  if (isText) console.error(chalk.red('\n\u2716 --industry requires cached rule definitions.'));
930
953
  if (isText) console.error(chalk.dim(` Run ${chalk.green('korext rules sync')} first to cache pack tags.`));
931
- process.exit(1);
954
+ process.exit(2); // C2: Error exit for missing cache
932
955
  }
933
956
  packIds = resolvePacksForIndustryRegion(options.industry, options.region || null, taxonomy);
934
957
  packSource = 'industry-flag';
935
958
  if (packIds.length === 0) {
936
959
  if (isText) console.error(chalk.red(`\n\u2716 No packs found for industry '${options.industry}'${options.region ? ` in region '${options.region}'` : ''}.`));
937
960
  if (isText) console.error(chalk.dim(` Run ${chalk.green('korext industries')} to see valid industry names.`));
938
- process.exit(1);
961
+ process.exit(2); // C2: Error exit for no matching packs
939
962
  }
940
963
  if (isText) console.log(`[KOREXT] Resolved ${packIds.length} packs for industry: ${options.industry}${options.region ? ` (region: ${options.region})` : ''}`);
941
964
  } else {
@@ -1005,11 +1028,30 @@ program
1005
1028
  let forceOffline = options.offline;
1006
1029
  let localDefinitions = null;
1007
1030
 
1008
- // Pre-flight: verify directory exists
1031
+ // C1: Pre-flight: verify path exists and determine file vs directory
1009
1032
  const resolvedInputDir = path.resolve(dir);
1010
1033
  if (!fs.existsSync(resolvedInputDir)) {
1011
- if (isText) console.error(chalk.red(`\n Directory does not exist: ${resolvedInputDir}`));
1012
- else console.error(JSON.stringify({ error: `Directory does not exist: ${resolvedInputDir}` }));
1034
+ if (isText) console.error(chalk.red(`\n\u2716 Path does not exist: ${resolvedInputDir}`));
1035
+ else console.error(JSON.stringify({ error: `Path does not exist: ${resolvedInputDir}` }));
1036
+ process.exit(2);
1037
+ }
1038
+
1039
+ // C1: Detect single file vs directory
1040
+ let isSingleFile = false;
1041
+ const inputStats = fs.statSync(resolvedInputDir);
1042
+ if (inputStats.isFile()) {
1043
+ isSingleFile = true;
1044
+ const ext = path.extname(resolvedInputDir);
1045
+ const SUPPORTED_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.java', '.rs'];
1046
+ if (!SUPPORTED_EXTENSIONS.includes(ext)) {
1047
+ if (isText) console.error(chalk.red(`\n\u2716 Unsupported file type: ${ext}`));
1048
+ else console.error(JSON.stringify({ error: `Unsupported file type: ${ext}` }));
1049
+ if (isText) console.error(chalk.dim(` Supported: ${SUPPORTED_EXTENSIONS.join(', ')}\n`));
1050
+ process.exit(2);
1051
+ }
1052
+ } else if (!inputStats.isDirectory()) {
1053
+ if (isText) console.error(chalk.red(`\n\u2716 Invalid path (not a file or directory): ${resolvedInputDir}`));
1054
+ else console.error(JSON.stringify({ error: `Invalid path: ${resolvedInputDir}` }));
1013
1055
  process.exit(2);
1014
1056
  }
1015
1057
 
@@ -1036,7 +1078,7 @@ program
1036
1078
  if (!localDefinitions) {
1037
1079
  if (isText) console.error(chalk.red('\n✖ Offline mode requires cached rule definitions.'));
1038
1080
  if (isText) console.error(chalk.dim(` Run ${chalk.green('korext rules sync')} or ${chalk.green('korext enforce --sync-rules')} first while online.`));
1039
- process.exit(1);
1081
+ process.exit(2); // C2: Error exit for missing offline cache
1040
1082
  }
1041
1083
  if (isText) {
1042
1084
  console.log(chalk.yellow('\n⚡ Offline mode: using local rule engine (regex-based analysis)'));
@@ -1082,12 +1124,17 @@ program
1082
1124
  bundles: []
1083
1125
  };
1084
1126
 
1127
+ // C1: Single file support
1085
1128
  let files;
1086
- try {
1087
- files = findFiles(dir);
1088
- } catch (e) {
1089
- if (isText) console.error(chalk.red(`Error reading directory: ${e.message}`));
1090
- process.exit(1);
1129
+ if (isSingleFile) {
1130
+ files = [resolvedInputDir];
1131
+ } else {
1132
+ try {
1133
+ files = findFiles(dir);
1134
+ } catch (e) {
1135
+ if (isText) console.error(chalk.red(`Error reading directory: ${e.message}`));
1136
+ process.exit(2); // C2: Error exit for filesystem errors
1137
+ }
1091
1138
  }
1092
1139
 
1093
1140
  report.summary.totalFiles = files.length;
@@ -1153,6 +1200,18 @@ program
1153
1200
  });
1154
1201
  clearTimeout(timeoutId);
1155
1202
 
1203
+ // C3 + H2: Differentiate auth errors from server/network errors
1204
+ if (res.status === 401 || res.status === 403) {
1205
+ if (fileSpinner) fileSpinner.stop();
1206
+ if (isText) {
1207
+ console.error(chalk.red('\n\u2716 Authentication failed. Your token may be expired.'));
1208
+ console.error(chalk.dim(` Run: ${chalk.green('korext login')} to re-authenticate.\n`));
1209
+ } else {
1210
+ console.error(JSON.stringify({ error: 'Authentication failed. Token may be expired. Run: korext login' }));
1211
+ }
1212
+ process.exit(2);
1213
+ }
1214
+
1156
1215
  if (!res.ok) {
1157
1216
  throw new Error(`HTTP ${res.status}`);
1158
1217
  }
@@ -1173,7 +1232,7 @@ program
1173
1232
  file: displayPath,
1174
1233
  bundleId: result.proofBundle.bundleId,
1175
1234
  decision: result.proofBundle.decision,
1176
- signed: !!result.proofBundle.hmacSignature,
1235
+ signed: !!result.proofBundle.hmacSignature || !!result.proofBundle.signature,
1177
1236
  verifyUrl: `https://app.korext.com/verify/${result.proofBundle.bundleId}`,
1178
1237
  });
1179
1238
  }
@@ -1183,12 +1242,12 @@ program
1183
1242
  fetchAndCacheRules().catch(() => {});
1184
1243
  }
1185
1244
  } catch (e) {
1186
- // Server unreachable fall back to local engine
1245
+ // Network error or server 5xx: fall back to local engine
1187
1246
  if (localDefinitions) {
1188
1247
  if (!usedLocalEngine && isText && i === 0) {
1189
1248
  // Show fallback banner once
1190
- console.log(chalk.yellow(`\n Server unreachable. Falling back to local engine (regex-based analysis).`));
1191
- console.log(chalk.dim(` Cached rules: v${localDefinitions.version} · ${localDefinitions.ruleCount} rules\n`));
1249
+ console.log(chalk.yellow(`\n\u26a1 Server unreachable. Falling back to local engine (regex-based analysis).`));
1250
+ console.log(chalk.dim(` Cached rules: v${localDefinitions.version} \u00b7 ${localDefinitions.ruleCount} rules\n`));
1192
1251
  }
1193
1252
  fileViolations = analyzeLocally(fileContent, pack, localDefinitions);
1194
1253
  usedLocalEngine = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "korext",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "mcpName": "io.github.Korext/governance",
5
5
  "description": "KOREXT CLI. AI Code Governance. Enforce compliance policies on human written and AI generated code. 72 policy packs. 532 rules. 13 languages. Signed proof bundles.",
6
6
  "type": "module",