korext 0.9.4 → 0.9.6

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 (3) hide show
  1. package/README.md +11 -1
  2. package/bin/korext.js +49 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -99,6 +99,16 @@ OWASP Top 10 | PCI-DSS | HIPAA | GDPR | SOC 2 | NIST SP 800-53 | NIST SP 800-171
99
99
  | `KOREXT_API_TOKEN` | API authentication token (recommended) |
100
100
  | `KOREXT_TOKEN` | Deprecated alias (shows warning) |
101
101
 
102
+ ## Changelog
103
+
104
+ ### v0.9.5
105
+
106
+ Fixed
107
+ - Watch mode now detects file changes correctly and scans on startup
108
+ - Enforcing a nonexistent directory now prints an error and exits with code 2 instead of silently passing
109
+ - Offline enforcement prints how many rules are available versus how many require server analysis
110
+ - Policy commands now default to the production API instead of localhost
111
+
102
112
  ## Links
103
113
 
104
114
  - Website: [korext.com](https://www.korext.com)
@@ -112,4 +122,4 @@ OWASP Top 10 | PCI-DSS | HIPAA | GDPR | SOC 2 | NIST SP 800-53 | NIST SP 800-171
112
122
 
113
123
  **Publisher**: Korext
114
124
  **License**: Proprietary
115
- **Version**: 0.8.0
125
+ **Version**: 0.9.5
package/bin/korext.js CHANGED
@@ -254,19 +254,30 @@ program
254
254
  program
255
255
  .command('enforce [dir]')
256
256
  .description('Statically analyze files in a directory against Korext policies')
257
- .option('-p, --pack <packId>', 'Policy Pack ID to enforce', 'web')
257
+ .option('-p, --pack <packIds>', 'Policy Pack ID(s) to enforce (comma-separated, e.g. web,pci-dss-v1)', 'web')
258
258
  .option('-f, --format <format>', 'Output format (text, json, sarif)', 'text')
259
259
  .option('--offline', 'Force local-only analysis using cached rule definitions (no server calls)', false)
260
260
  .option('--sync-rules', 'Fetch and cache latest rule definitions before running analysis', false)
261
261
  .action(async (dirArg, options) => {
262
262
  const dir = dirArg || '.';
263
- const pack = options.pack;
263
+ const packInput = options.pack;
264
+ // Multi-pack: split on comma, trim whitespace
265
+ const packIds = packInput.split(',').map(p => p.trim()).filter(Boolean);
266
+ const pack = packIds.length === 1 ? packIds[0] : packIds;
264
267
  const format = options.format.toLowerCase();
265
268
  const isText = format === 'text';
266
269
 
267
270
  let forceOffline = options.offline;
268
271
  let localDefinitions = null;
269
272
 
273
+ // Pre-flight: verify directory exists
274
+ const resolvedInputDir = path.resolve(dir);
275
+ if (!fs.existsSync(resolvedInputDir)) {
276
+ if (isText) console.error(chalk.red(`\n✖ Directory does not exist: ${resolvedInputDir}`));
277
+ else console.error(JSON.stringify({ error: `Directory does not exist: ${resolvedInputDir}` }));
278
+ process.exit(2);
279
+ }
280
+
270
281
  if (isText) {
271
282
  console.log(`\n${chalk.bold.hex('#F27D26')('▲ KOREXT CLI ENFORCEMENT ENGINE')} v${version}`);
272
283
  console.log(chalk.dim('======================================='));
@@ -295,6 +306,11 @@ program
295
306
  if (isText) {
296
307
  console.log(chalk.yellow('\n⚡ Offline mode: using local rule engine (regex-based analysis)'));
297
308
  console.log(chalk.dim(` Cached rules: v${localDefinitions.version} · ${localDefinitions.ruleCount} rules · ${localDefinitions.packCount} packs`));
309
+ // Show rule coverage breakdown
310
+ const packRules = localDefinitions.packs?.[pack]?.rules || [];
311
+ const availableCount = packRules.filter(r => localDefinitions.rules?.[r]).length;
312
+ const serverOnlyCount = packRules.length - availableCount;
313
+ console.log(chalk.dim(` Offline mode: ${availableCount} of ${packRules.length} rules available. ${serverOnlyCount} rules require server analysis.`));
298
314
  console.log(chalk.dim(' Note: AI-powered deep analysis is unavailable in offline mode.\n'));
299
315
  }
300
316
  }
@@ -308,7 +324,8 @@ program
308
324
 
309
325
  const report = {
310
326
  version,
311
- packId: pack,
327
+ packId: Array.isArray(pack) ? pack[0] : pack,
328
+ packIds,
312
329
  directory: dir,
313
330
  summary: { totalFiles: 0, scannedFiles: 0, skippedFiles: 0, errorFiles: 0, critical: 0, high: 0, medium: 0, low: 0, totalViolations: 0 },
314
331
  results: []
@@ -330,7 +347,7 @@ program
330
347
  process.exit(0);
331
348
  }
332
349
 
333
- if (isText) console.log(`Found ${files.length} files. Starting analysis with pack: ${chalk.cyan(pack)}...\n`);
350
+ if (isText) console.log(`Found ${files.length} files. Starting analysis with pack${packIds.length > 1 ? 's' : ''}: ${chalk.cyan(packIds.join(', '))}...\n`);
334
351
 
335
352
  let usedLocalEngine = false;
336
353
 
@@ -364,6 +381,8 @@ program
364
381
  } else {
365
382
  // Online mode: try server, fall back to local on failure
366
383
  try {
384
+ const controller = new AbortController();
385
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
367
386
  const res = await fetch(`${API_URL}/api/ide/analyze`, {
368
387
  method: 'POST',
369
388
  headers: {
@@ -377,8 +396,10 @@ program
377
396
  packId: pack,
378
397
  requestSignature: false,
379
398
  asyncExplanations: false
380
- })
399
+ }),
400
+ signal: controller.signal
381
401
  });
402
+ clearTimeout(timeoutId);
382
403
 
383
404
  if (!res.ok) {
384
405
  throw new Error(`HTTP ${res.status}`);
@@ -1309,6 +1330,29 @@ program
1309
1330
  if (fs.existsSync(fullPath)) analyzeWatchedFile(fullPath);
1310
1331
  }, DEBOUNCE_MS));
1311
1332
  });
1333
+
1334
+ // Also do an initial scan of all existing files on startup
1335
+ const initialFiles = [];
1336
+ function walkDir(d) {
1337
+ let entries;
1338
+ try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
1339
+ for (const entry of entries) {
1340
+ if (IGNORE_DIRS.has(entry.name)) continue;
1341
+ const fullPath = path.join(d, entry.name);
1342
+ if (entry.isDirectory()) {
1343
+ walkDir(fullPath);
1344
+ } else if (SUPPORTED_EXTS.has(path.extname(entry.name))) {
1345
+ initialFiles.push(fullPath);
1346
+ }
1347
+ }
1348
+ }
1349
+ walkDir(dir);
1350
+ if (initialFiles.length > 0) {
1351
+ console.log(chalk.dim(` Initial scan: ${initialFiles.length} file(s)...\n`));
1352
+ for (const f of initialFiles) {
1353
+ await analyzeWatchedFile(f);
1354
+ }
1355
+ }
1312
1356
  } catch (e) {
1313
1357
  console.error(chalk.red(`Failed to watch ${dir}: ${e.message}`));
1314
1358
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "korext",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "Korext Command Line Interface",
5
5
  "type": "module",
6
6
  "main": "bin/korext.js",