korext 0.7.0 → 0.8.0

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## 0.8.0 - 2026-03-27
2
+
3
+ ### New
4
+ - Scoped AI fixes: fixes are constrained to the violation region. Surrounding code is never modified.
5
+ - Four deterministic verification guards: every fix is checked for scope boundary, security function preservation, dangerous function introduction, and middleware chain integrity before it is applied.
6
+ - Silent token refresh: sessions no longer expire after one hour. Tokens refresh automatically in the background.
7
+ - Tiered AI concurrency: Enterprise (15), Team (10), Free (5) concurrent AI calls for faster explanations on large files.
8
+ - Offline mode improvements: status bar shows "Offline (local rules only)" with helpful messaging instead of error states.
9
+ - 478 policy rules across 44 packs with real detection logic. Zero stubs.
10
+ - Full 3-layer governance mappings on every rule: regulatory citations, technical standards (CWE, OWASP), and security intelligence (MITRE ATT&CK).
11
+ - 9 jurisdiction coverage: US, EU, UK, Canada, Australia, New Zealand, Japan, Taiwan, Singapore.
12
+
13
+ ### Fixed
14
+ - IDE and dashboard fix paths unified. Both use the same scoped extraction and deterministic guards.
15
+ - HMAC signature verification now returns signatureValid: true for correctly signed bundles.
16
+ - Auto-subscription for first-time users. No more access denied on sign-in.
17
+
18
+ ### Improved
19
+ - Consistent KOREXT_API_TOKEN environment variable across all clients. KOREXT_TOKEN is deprecated with a warning.
20
+ - Rate limiting adjusted: IDE status polling (200/min), explain stream (30/min).
21
+
1
22
  ## 0.7.0 - 2026-03-19
2
23
 
3
24
  ### Added
package/README.md CHANGED
@@ -1,41 +1,115 @@
1
- # KOREXT - AI Code Governance
1
+ # Korext CLI
2
2
 
3
- Real time policy enforcement and compliance proof for AI-generated code.
3
+ Enforce compliance on AI-generated code from the command line and CI/CD pipelines. 478 rules across 44 policy packs with real detection logic. Every violation mapped to specific regulatory clauses. SARIF output for CI scanner integration.
4
4
 
5
- ## What It Does
5
+ ## Install
6
6
 
7
- KOREXT enforces your compliance policies at the point of code generation. Every file is checked against your selected policy pack. Violations appear as CLI-based error logs with severity, governance context (regulatory, technical standards, security intelligence), and an AI-powered explanation. One click applies a verified fix with diff preview. Every enforcement decision generates a cryptographically signed proof bundle your compliance team can verify.
7
+ ```bash
8
+ npm install -g korext
9
+ ```
8
10
 
9
- ## Core Commands
11
+ ## Quick Start
10
12
 
11
- - `korext login`: Login via browser (recommended)
12
- - `korext status`: View your current authentication state and active Policy Pack
13
- - `korext enforce`: Scan a target directory or file and cross-reference it against your policy pack
14
- - `korext packs list`: Retrieve and list all available internal Policy Packs
15
- - `korext policy init`: Initialize a new custom policy workflow
16
- - `korext policy extract`: Extract rules from a PDF/Markdown policy document
17
- - `korext policy review`: Interactively review extracted rules
18
- - `korext policy publish`: Deploy and activate custom policy packs
13
+ ```bash
14
+ korext login
15
+ korext enforce ./src --pack web-platform-v2
16
+ ```
19
17
 
20
- ## Features
18
+ ## Commands
21
19
 
22
- - CLI violation detection
23
- - Three-layer governance context: regulatory compliance, technical standards, security intelligence
24
- - Command line verified code fixes via Shadow Test Protocol
25
- - Cryptographically signed proof bundles
26
- - Publication-quality PDF export for auditors
27
- - Covers PCI-DSS, HIPAA, GDPR, SOC 2, OWASP, NIST, CIS, and more
28
- - Custom policy packs on Team plan
29
- - Air-gapped deployment for Enterprise
20
+ | Command | Description |
21
+ |---------|-------------|
22
+ | `korext login [token]` | Save API token |
23
+ | `korext status` | Check connection and subscription |
24
+ | `korext enforce [dir]` | Scan files for violations |
25
+ | `korext policy init` | Initialize a policy document |
26
+ | `korext policy extract` | AI rule extraction from documents |
27
+ | `korext policy review` | Review extracted rules |
28
+ | `korext rules sync` | Cache rules for offline use |
30
29
 
31
- ## Getting Started
30
+ ### Enforce Options
32
31
 
33
- Install the CLI via `npm install -g korext` and sign in. Select a policy pack. Run `korext enforce`. KOREXT runs in the terminal and surfaces violations instantly.
32
+ | Flag | Description |
33
+ |------|-------------|
34
+ | `--pack <id>` | Select a policy pack |
35
+ | `--format text\|json\|sarif` | Output format |
36
+ | `--offline` | Use cached rules only |
37
+ | `--sync-rules` | Download rule cache before scan |
34
38
 
35
- Free tier available. Team and Enterprise plans for organisations that need signed proof bundles, PDF export, and compliance reporting.
39
+ ## CI/CD Integration
40
+
41
+ ### GitHub Actions
42
+
43
+ ```yaml
44
+ - name: Korext Compliance Check
45
+ run: |
46
+ npm install -g korext
47
+ korext enforce ./src \
48
+ --pack cmmc-level2-v1 \
49
+ --format sarif
50
+ env:
51
+ KOREXT_API_TOKEN: ${{ secrets.KOREXT_API_TOKEN }}
52
+ ```
53
+
54
+ ### Exit Codes
55
+
56
+ | Code | Meaning |
57
+ |------|---------|
58
+ | `0` | Clean (no critical or high violations) |
59
+ | `1` | Violations found |
60
+ | `2` | Error |
61
+
62
+ Writes GitHub Actions Step Summary via `GITHUB_STEP_SUMMARY` when detected.
63
+
64
+ ## SARIF Output
65
+
66
+ ```bash
67
+ korext enforce ./src --format sarif > results.sarif
68
+ ```
69
+
70
+ Generates OASIS SARIF 2.1.0 for CI scanner integration (GitHub Code Scanning, Azure DevOps, etc.).
71
+
72
+ ## Offline Mode
73
+
74
+ ```bash
75
+ korext rules sync
76
+ korext enforce ./src --offline
77
+ ```
78
+
79
+ Cached rules enforce locally with zero network calls. Status output shows "Offline (local rules only)".
80
+
81
+ ## Supported Compliance Frameworks
82
+
83
+ OWASP Top 10 | PCI-DSS | HIPAA | GDPR | SOC 2 | NIST SP 800-53 | NIST SP 800-171 | CMMC Level 2/3 | FedRAMP | ISO 27001 | DORA | NIS2 | CIS Benchmarks | UK DPA | Australian Privacy Act | APPI (Japan) | PDPA (Singapore, Taiwan) | and 25+ more
84
+
85
+ ## Key Features
86
+
87
+ - 478 rules across 44 policy packs
88
+ - Three-layer governance: regulatory, technical standards (CWE, OWASP), security intelligence (MITRE ATT&CK)
89
+ - 9 jurisdiction coverage (US, EU, UK, Canada, Australia, New Zealand, Japan, Taiwan, Singapore)
90
+ - SARIF 2.1.0 output for CI scanner integration
91
+ - GitHub Actions Step Summary generation
92
+ - Offline mode with cached rules
93
+ - Custom policy packs from uploaded documents
94
+
95
+ ## Environment Variables
96
+
97
+ | Variable | Description |
98
+ |----------|-------------|
99
+ | `KOREXT_API_TOKEN` | API authentication token (recommended) |
100
+ | `KOREXT_TOKEN` | Deprecated alias (shows warning) |
36
101
 
37
102
  ## Links
38
103
 
39
- - Website: https://www.korext.com
40
- - Dashboard: https://app.korext.com
41
- - Documentation: https://www.korext.com/docs
104
+ - Website: [korext.com](https://www.korext.com)
105
+ - Dashboard: [app.korext.com](https://app.korext.com)
106
+ - VS Code Extension: [marketplace.visualstudio.com](https://marketplace.visualstudio.com/items?itemName=Korext.korext)
107
+ - LinkedIn: [linkedin.com/company/korext](https://www.linkedin.com/company/korext)
108
+ - GitHub: [github.com/Korext](https://github.com/Korext)
109
+ - Support: support@korext.com
110
+
111
+ ---
112
+
113
+ **Publisher**: Korext
114
+ **License**: Proprietary
115
+ **Version**: 0.8.0
package/bin/korext.js CHANGED
@@ -34,8 +34,17 @@ function saveConfig(config) {
34
34
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
35
35
  }
36
36
 
37
+ let _deprecationWarned = false;
37
38
  function getToken() {
38
- return process.env.KOREXT_TOKEN || getConfig().token || null;
39
+ if (process.env.KOREXT_API_TOKEN) return process.env.KOREXT_API_TOKEN;
40
+ if (process.env.KOREXT_TOKEN) {
41
+ if (!_deprecationWarned) {
42
+ console.warn('\x1b[33m⚠ KOREXT_TOKEN is deprecated. Use KOREXT_API_TOKEN instead.\x1b[0m');
43
+ _deprecationWarned = true;
44
+ }
45
+ return process.env.KOREXT_TOKEN;
46
+ }
47
+ return getConfig().token || null;
39
48
  }
40
49
 
41
50
  function getLanguageFromExt(ext) {
@@ -48,6 +57,90 @@ function getLanguageFromExt(ext) {
48
57
  return map[ext] || 'plaintext';
49
58
  }
50
59
 
60
+ // ─── LOCAL ENGINE: Rule Definition Caching + Offline Analysis ─────────────────
61
+
62
+ const RULES_CACHE_FILE = path.join(CONFIG_DIR, 'rule-definitions.json');
63
+
64
+ function getRuleDefinitionsCache() {
65
+ if (fs.existsSync(RULES_CACHE_FILE)) {
66
+ try {
67
+ const cached = JSON.parse(fs.readFileSync(RULES_CACHE_FILE, 'utf-8'));
68
+ if (cached && cached.rules && cached.packs) return cached;
69
+ } catch { /* corrupt cache, ignore */ }
70
+ }
71
+ return null;
72
+ }
73
+
74
+ function saveRuleDefinitionsCache(payload) {
75
+ if (!fs.existsSync(CONFIG_DIR)) {
76
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
77
+ }
78
+ fs.writeFileSync(RULES_CACHE_FILE, JSON.stringify(payload, null, 2), { mode: 0o600 });
79
+ }
80
+
81
+ async function fetchAndCacheRules() {
82
+ const token = getToken();
83
+ const res = await fetch(`${API_URL}/api/rules/definitions`, {
84
+ method: 'GET',
85
+ headers: {
86
+ 'Content-Type': 'application/json',
87
+ ...(token && { 'Authorization': `Bearer ${token}` })
88
+ }
89
+ });
90
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
91
+ const payload = await res.json();
92
+ saveRuleDefinitionsCache(payload);
93
+ return payload;
94
+ }
95
+
96
+ /**
97
+ * Run rules locally against code using cached regex definitions.
98
+ * Mirrors korext-core/src/engine/localEngine.ts analyzeLocally().
99
+ */
100
+ function analyzeLocally(code, packId, definitions) {
101
+ const pack = definitions.packs[packId];
102
+ if (!pack) return [];
103
+
104
+ const violations = [];
105
+ const lines = code.split('\n');
106
+
107
+ for (const ruleId of pack.rules) {
108
+ const rule = definitions.rules[ruleId];
109
+ if (!rule) continue;
110
+ if (rule.checkMode === 'server-only') continue;
111
+ if (!rule.patterns || rule.patterns.length === 0) continue;
112
+
113
+ for (const pattern of rule.patterns) {
114
+ try {
115
+ const re = new RegExp(pattern.regex, pattern.flags || 'gi');
116
+ lines.forEach((line, i) => {
117
+ re.lastIndex = 0;
118
+ if (re.test(line)) {
119
+ violations.push({
120
+ ruleName: rule.label,
121
+ severity: rule.severity,
122
+ explanation: pattern.message || rule.message || `${rule.label} violation detected`,
123
+ line: i + 1,
124
+ column: 0,
125
+ snippet: line.trim().slice(0, 90),
126
+ detectedBy: 'local-static'
127
+ });
128
+ }
129
+ });
130
+ } catch { /* invalid regex, skip */ }
131
+ }
132
+ }
133
+
134
+ // Deduplicate by ruleId + line + message
135
+ const seen = new Set();
136
+ return violations.filter(v => {
137
+ const key = `${v.ruleName}:${v.line}:${v.explanation}`;
138
+ if (seen.has(key)) return false;
139
+ seen.add(key);
140
+ return true;
141
+ });
142
+ }
143
+
51
144
  function findFiles(dir, fileList = []) {
52
145
  if (!fs.existsSync(dir)) return fileList;
53
146
  const files = fs.readdirSync(dir);
@@ -160,19 +253,51 @@ program
160
253
  .description('Statically analyze files in a directory against Korext policies')
161
254
  .option('-p, --pack <packId>', 'Policy Pack ID to enforce', 'web')
162
255
  .option('-f, --format <format>', 'Output format (text, json, sarif)', 'text')
256
+ .option('--offline', 'Force local-only analysis using cached rule definitions (no server calls)', false)
257
+ .option('--sync-rules', 'Fetch and cache latest rule definitions before running analysis', false)
163
258
  .action(async (dirArg, options) => {
164
259
  const dir = dirArg || '.';
165
260
  const pack = options.pack;
166
261
  const format = options.format.toLowerCase();
167
262
  const isText = format === 'text';
168
263
 
264
+ let forceOffline = options.offline;
265
+ let localDefinitions = null;
266
+
169
267
  if (isText) {
170
268
  console.log(`\n${chalk.bold.hex('#F27D26')('▲ KOREXT CLI ENFORCEMENT ENGINE')} v${version}`);
171
269
  console.log(chalk.dim('======================================='));
172
270
  }
173
271
 
272
+ // Pre-flight: sync rules if requested
273
+ if (options.syncRules) {
274
+ const syncSpinner = isText ? ora('Syncing rule definitions...').start() : null;
275
+ try {
276
+ const defs = await fetchAndCacheRules();
277
+ if (syncSpinner) syncSpinner.succeed(chalk.green(`Synced ${defs.ruleCount} rules, ${defs.packCount} packs (v${defs.version})`));
278
+ } catch (e) {
279
+ if (syncSpinner) syncSpinner.warn(chalk.yellow(`Rule sync failed: ${e.message}. Will use cached definitions if available.`));
280
+ }
281
+ }
282
+
283
+ // Load cached rule definitions for offline/fallback use
284
+ localDefinitions = getRuleDefinitionsCache();
285
+
286
+ if (forceOffline) {
287
+ if (!localDefinitions) {
288
+ if (isText) console.error(chalk.red('\n✖ Offline mode requires cached rule definitions.'));
289
+ if (isText) console.error(chalk.dim(` Run ${chalk.green('korext rules sync')} or ${chalk.green('korext enforce --sync-rules')} first while online.`));
290
+ process.exit(1);
291
+ }
292
+ if (isText) {
293
+ console.log(chalk.yellow('\n⚡ Offline mode: using local rule engine (regex-based analysis)'));
294
+ console.log(chalk.dim(` Cached rules: v${localDefinitions.version} · ${localDefinitions.ruleCount} rules · ${localDefinitions.packCount} packs`));
295
+ console.log(chalk.dim(' Note: AI-powered deep analysis is unavailable in offline mode.\n'));
296
+ }
297
+ }
298
+
174
299
  const token = getToken();
175
- if (!token && isText) {
300
+ if (!token && !forceOffline && isText) {
176
301
  console.log(chalk.yellow('\n⚠ Warning: No authentication token found.'));
177
302
  console.log(chalk.dim('Anonymous evaluation runs are limited to 20 requests per hour per IP.'));
178
303
  console.log(chalk.dim(`Run ${chalk.green('korext login')} to authenticate for unlimited CI/CD analytics.\n`));
@@ -204,6 +329,8 @@ program
204
329
 
205
330
  if (isText) console.log(`Found ${files.length} files. Starting analysis with pack: ${chalk.cyan(pack)}...\n`);
206
331
 
332
+ let usedLocalEngine = false;
333
+
207
334
  for (let i = 0; i < files.length; i++) {
208
335
  const file = files[i];
209
336
  const displayPath = path.relative(process.cwd(), file);
@@ -221,66 +348,94 @@ program
221
348
  const ext = path.extname(file);
222
349
  const language = getLanguageFromExt(ext);
223
350
 
224
- try {
225
- const res = await fetch(`${API_URL}/api/ide/analyze`, {
226
- method: 'POST',
227
- headers: {
228
- 'Content-Type': 'application/json',
229
- ...(token && { 'Authorization': `Bearer ${token}` })
230
- },
231
- body: JSON.stringify({
232
- fileContent,
233
- language,
234
- fileName: file,
235
- packId: pack,
236
- requestSignature: false
237
- })
238
- });
351
+ // ── Analysis: server-first with local fallback ─────────────────────
352
+ let fileViolations = [];
239
353
 
240
- if (!res.ok) {
241
- if (fileSpinner) fileSpinner.fail(chalk.red(`Server error analyzing ${displayPath}: HTTP ${res.status}`));
242
- report.summary.errorFiles++;
243
- continue;
244
- }
354
+ if (forceOffline) {
355
+ // Offline mode: local engine only
356
+ fileViolations = analyzeLocally(fileContent, pack, localDefinitions);
357
+ usedLocalEngine = true;
358
+ } else {
359
+ // Online mode: try server, fall back to local on failure
360
+ try {
361
+ const res = await fetch(`${API_URL}/api/ide/analyze`, {
362
+ method: 'POST',
363
+ headers: {
364
+ 'Content-Type': 'application/json',
365
+ ...(token && { 'Authorization': `Bearer ${token}` })
366
+ },
367
+ body: JSON.stringify({
368
+ fileContent,
369
+ language,
370
+ fileName: file,
371
+ packId: pack,
372
+ requestSignature: false
373
+ })
374
+ });
245
375
 
246
- const result = await res.json();
247
-
248
- if (result.skipped) {
249
- if (fileSpinner) fileSpinner.info(chalk.yellow(`Skipped ${displayPath}: ${result.reason}`));
250
- report.summary.skippedFiles++;
251
- continue;
252
- }
376
+ if (!res.ok) {
377
+ throw new Error(`HTTP ${res.status}`);
378
+ }
379
+
380
+ const result = await res.json();
381
+
382
+ if (result.skipped) {
383
+ if (fileSpinner) fileSpinner.info(chalk.yellow(`Skipped ${displayPath}: ${result.reason}`));
384
+ report.summary.skippedFiles++;
385
+ continue;
386
+ }
253
387
 
254
- report.summary.scannedFiles++;
255
- const fileViolations = result.violations || [];
256
- report.summary.totalViolations += fileViolations.length;
257
-
258
- if (fileViolations.length > 0) {
259
- if (fileSpinner) fileSpinner.stop();
260
- if (isText) console.log(`\n📄 ${chalk.bold.underline(displayPath)}`);
261
-
262
- const storedV = [];
263
- for (const v of fileViolations) {
264
- let severityTag = chalk.blue('LOW');
265
- if (v.severity === 'critical') { severityTag = chalk.bgRed.white.bold(' CRITICAL '); report.summary.critical++; }
266
- else if (v.severity === 'high') { severityTag = chalk.red.bold('HIGH'); report.summary.high++; }
267
- else if (v.severity === 'medium') { severityTag = chalk.yellow.bold('MED'); report.summary.medium++; }
268
- else { report.summary.low++; }
269
-
270
- if (isText) {
271
- console.log(` ${chalk.dim(v.line + ':' + (v.column || 0))} ${severityTag} ${v.ruleName}`);
272
- console.log(` ${chalk.dim('↳')} ${chalk.italic(v.explanation)} `);
388
+ fileViolations = result.violations || [];
389
+
390
+ // Cache rule definitions on first successful server response (opportunistic sync)
391
+ if (!localDefinitions && i === 0) {
392
+ fetchAndCacheRules().catch(() => {});
393
+ }
394
+ } catch (e) {
395
+ // Server unreachable — fall back to local engine
396
+ if (localDefinitions) {
397
+ if (!usedLocalEngine && isText && i === 0) {
398
+ // Show fallback banner once
399
+ console.log(chalk.yellow(`\n⚡ Server unreachable. Falling back to local engine (regex-based analysis).`));
400
+ console.log(chalk.dim(` Cached rules: v${localDefinitions.version} · ${localDefinitions.ruleCount} rules\n`));
273
401
  }
274
- storedV.push(v);
402
+ fileViolations = analyzeLocally(fileContent, pack, localDefinitions);
403
+ usedLocalEngine = true;
404
+ forceOffline = true; // Don't retry server for remaining files
405
+ } else {
406
+ if (fileSpinner) fileSpinner.fail(chalk.red(`Failed to analyze ${displayPath}: ${e.message}`));
407
+ report.summary.errorFiles++;
408
+ continue;
275
409
  }
276
- report.results.push({ file: displayPath, violations: storedV });
277
- } else {
278
- if (fileSpinner) fileSpinner.stop();
279
- report.results.push({ file: displayPath, violations: [] });
280
410
  }
281
- } catch (e) {
282
- if (fileSpinner) fileSpinner.fail(chalk.red(`Failed to analyze ${displayPath}: ${e.message}`));
283
- report.summary.errorFiles++;
411
+ }
412
+
413
+ report.summary.scannedFiles++;
414
+ report.summary.totalViolations += fileViolations.length;
415
+
416
+ if (fileViolations.length > 0) {
417
+ if (fileSpinner) fileSpinner.stop();
418
+ if (isText) console.log(`\n📄 ${chalk.bold.underline(displayPath)}`);
419
+
420
+ const storedV = [];
421
+ for (const v of fileViolations) {
422
+ let severityTag = chalk.blue('LOW');
423
+ if (v.severity === 'critical') { severityTag = chalk.bgRed.white.bold(' CRITICAL '); report.summary.critical++; }
424
+ else if (v.severity === 'high') { severityTag = chalk.red.bold('HIGH'); report.summary.high++; }
425
+ else if (v.severity === 'medium') { severityTag = chalk.yellow.bold('MED'); report.summary.medium++; }
426
+ else { report.summary.low++; }
427
+
428
+ if (isText) {
429
+ const localTag = v.detectedBy === 'local-static' ? chalk.dim(' [local]') : '';
430
+ console.log(` ${chalk.dim(v.line + ':' + (v.column || 0))} ${severityTag} ${v.ruleName}${localTag}`);
431
+ console.log(` ${chalk.dim('↳')} ${chalk.italic(v.explanation)} `);
432
+ }
433
+ storedV.push(v);
434
+ }
435
+ report.results.push({ file: displayPath, violations: storedV });
436
+ } else {
437
+ if (fileSpinner) fileSpinner.stop();
438
+ report.results.push({ file: displayPath, violations: [] });
284
439
  }
285
440
  }
286
441
 
@@ -313,6 +468,9 @@ program
313
468
  console.log(JSON.stringify(sarif, null, 2));
314
469
  } else {
315
470
  console.log('\n' + chalk.dim('======================================='));
471
+ if (usedLocalEngine) {
472
+ console.log(chalk.dim('Engine: LOCAL (regex-based, cached rules)'));
473
+ }
316
474
  if (report.summary.totalViolations === 0 && report.summary.errorFiles === 0) {
317
475
  console.log(chalk.green.bold('✔ Success! Found 0 policy violations. Your code is clean.\n'));
318
476
  } else {
@@ -359,6 +517,43 @@ program
359
517
  process.exit(0);
360
518
  });
361
519
 
520
+ // ─── RULE SYNC COMMAND ──────────────────────────────────────────────────────
521
+
522
+ const rulesCmd = program.command('rules').description('Rule definition management commands');
523
+
524
+ rulesCmd
525
+ .command('sync')
526
+ .description('Download and cache rule definitions for offline enforcement')
527
+ .action(async () => {
528
+ console.log(chalk.bold.hex('#F27D26')('\n▲ KOREXT RULE SYNC'));
529
+ console.log(chalk.dim('======================================='));
530
+ const spinner = ora('Fetching rule definitions...').start();
531
+ try {
532
+ const defs = await fetchAndCacheRules();
533
+ spinner.succeed(chalk.green(`Cached ${defs.ruleCount} rules across ${defs.packCount} packs`));
534
+ console.log(chalk.dim(` Version: ${defs.version}`));
535
+ console.log(chalk.dim(` Saved to: ${RULES_CACHE_FILE}`));
536
+ console.log(chalk.dim(` Generated: ${defs.generatedAt}`));
537
+
538
+ // List available packs
539
+ console.log(`\n${chalk.bold('Available packs for offline enforcement:')}`);
540
+ for (const [packId, pack] of Object.entries(defs.packs)) {
541
+ const ruleCount = pack.rules ? pack.rules.length : 0;
542
+ console.log(` ${chalk.cyan(packId)} — ${pack.label} (${ruleCount} rules)`);
543
+ }
544
+ console.log(`\n Run: ${chalk.green('korext enforce . --offline --pack <packId>')} to enforce offline.\n`);
545
+ } catch (e) {
546
+ spinner.fail(chalk.red(`Failed to fetch rule definitions: ${e.message}`));
547
+ const cached = getRuleDefinitionsCache();
548
+ if (cached) {
549
+ console.log(chalk.yellow(` Existing cache still available (v${cached.version}, ${cached.ruleCount} rules)`));
550
+ } else {
551
+ console.log(chalk.dim(' No cached definitions available. Connect to the network and try again.'));
552
+ }
553
+ process.exit(1);
554
+ }
555
+ });
556
+
362
557
  // ─── POLICY COMMANDS ─────────────────────────────────────────────────────────
363
558
 
364
559
  const LOCAL_DRAFT_DIR = path.join(process.cwd(), '.korext');
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "korext",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Korext Command Line Interface",
5
5
  "type": "module",
6
6
  "main": "bin/korext.js",
7
7
  "bin": {
8
8
  "korext": "bin/korext.js"
9
9
  },
10
- "author": "Korext",
10
+ "author": "Korext <support@korext.com>",
11
11
  "license": "ISC",
12
12
  "dependencies": {
13
13
  "chalk": "^4.1.2",
@@ -30,5 +30,23 @@
30
30
  "glob",
31
31
  "node-fetch",
32
32
  "ora"
33
+ ],
34
+ "homepage": "https://www.korext.com",
35
+ "bugs": {
36
+ "email": "support@korext.com"
37
+ },
38
+ "keywords": [
39
+ "governance",
40
+ "compliance",
41
+ "security",
42
+ "cli",
43
+ "cicd",
44
+ "sarif",
45
+ "AI",
46
+ "code review",
47
+ "PCI-DSS",
48
+ "HIPAA",
49
+ "OWASP",
50
+ "korext"
33
51
  ]
34
52
  }