korext 0.8.2 → 0.9.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.
Files changed (2) hide show
  1. package/bin/korext.js +121 -1
  2. package/package.json +1 -1
package/bin/korext.js CHANGED
@@ -1184,5 +1184,125 @@ policyCmd
1184
1184
 
1185
1185
  // ─── END POLICY COMMANDS ──────────────────────────────────────────────────
1186
1186
 
1187
- program.parse(process.argv);
1187
+ // ─── WATCH COMMAND (Initiative #5 - CLI Watch Mode) ──────────────────────
1188
+
1189
+ program
1190
+ .command('watch [dir]')
1191
+ .description('Continuously enforce policies on file changes (live development mode)')
1192
+ .option('-p, --pack <packId>', 'Policy Pack ID to enforce', 'web')
1193
+ .option('--offline', 'Force local-only analysis using cached rule definitions', false)
1194
+ .action(async (dirArg, options) => {
1195
+ const dir = path.resolve(dirArg || '.');
1196
+ const pack = options.pack;
1197
+ const forceOffline = options.offline;
1198
+
1199
+ console.log(`\n${chalk.bold.hex('#F27D26')('\u25b2 KOREXT WATCH MODE')} v${version}`);
1200
+ console.log(chalk.dim('======================================='));
1201
+ console.log(` Directory: ${chalk.cyan(dir)}`);
1202
+ console.log(` Pack: ${chalk.cyan(pack)}`);
1203
+ console.log(` Mode: ${forceOffline ? chalk.yellow('Offline (local engine)') : chalk.green('Online (server + local fallback)')}`);
1204
+ console.log(chalk.dim('\n Watching for file changes... Press Ctrl+C to stop.\n'));
1205
+
1206
+ const token = getToken();
1207
+ let localDefinitions = getRuleDefinitionsCache();
1208
+
1209
+ if (forceOffline && !localDefinitions) {
1210
+ console.error(chalk.red('Offline mode requires cached rule definitions.'));
1211
+ console.error(chalk.dim(` Run: ${chalk.green('korext rules sync')} first while online.`));
1212
+ process.exit(1);
1213
+ }
1214
+
1215
+ if (!forceOffline && !localDefinitions) {
1216
+ try {
1217
+ localDefinitions = await fetchAndCacheRules();
1218
+ console.log(chalk.green(` Synced ${localDefinitions.ruleCount} rules for local fallback.\n`));
1219
+ } catch { /* will use server */ }
1220
+ }
1221
+
1222
+ const SUPPORTED_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.java', '.rs']);
1223
+ const IGNORE_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.korext']);
1224
+ const debounceTimers = new Map();
1225
+ const DEBOUNCE_MS = 800;
1226
+ let totalViolations = 0;
1227
+ let filesChecked = 0;
1228
+
1229
+ async function analyzeWatchedFile(filePath) {
1230
+ const displayPath = path.relative(dir, filePath);
1231
+ const ext = path.extname(filePath);
1232
+ if (!SUPPORTED_EXTS.has(ext)) return;
1233
+
1234
+ let fileContent;
1235
+ try { fileContent = fs.readFileSync(filePath, 'utf-8'); } catch { return; }
1236
+ if (fileContent.length > 500000) return;
1237
+
1238
+ const language = getLanguageFromExt(ext);
1239
+ let violations = [];
1240
+ filesChecked++;
1241
+
1242
+ if (forceOffline && localDefinitions) {
1243
+ violations = analyzeLocally(fileContent, pack, localDefinitions);
1244
+ } else {
1245
+ try {
1246
+ const res = await fetch(`${API_URL}/api/ide/analyze`, {
1247
+ method: 'POST',
1248
+ headers: {
1249
+ 'Content-Type': 'application/json',
1250
+ ...(token && { 'Authorization': `Bearer ${token}` })
1251
+ },
1252
+ body: JSON.stringify({ fileContent, language, fileName: filePath, packId: pack, requestSignature: false })
1253
+ });
1254
+ if (res.ok) {
1255
+ const result = await res.json();
1256
+ violations = result.violations || [];
1257
+ } else { throw new Error(`HTTP ${res.status}`); }
1258
+ } catch {
1259
+ if (localDefinitions) violations = analyzeLocally(fileContent, pack, localDefinitions);
1260
+ }
1261
+ }
1262
+
1263
+ const now = new Date().toLocaleTimeString();
1264
+ if (violations.length > 0) {
1265
+ totalViolations += violations.length;
1266
+ console.log(`\n${chalk.dim(now)} ${chalk.bold.underline(displayPath)}`);
1267
+ for (const v of violations) {
1268
+ let tag = chalk.blue('LOW');
1269
+ if (v.severity === 'critical') tag = chalk.bgRed.white.bold(' CRITICAL ');
1270
+ else if (v.severity === 'high') tag = chalk.red.bold('HIGH');
1271
+ else if (v.severity === 'medium') tag = chalk.yellow.bold('MED');
1272
+ console.log(` ${chalk.dim(v.line + ':')} ${tag} ${v.ruleName || v.explanation}`);
1273
+ }
1274
+ } else {
1275
+ console.log(`${chalk.dim(now)} ${chalk.green('\u2713')} ${displayPath}`);
1276
+ }
1277
+ }
1188
1278
 
1279
+ try {
1280
+ fs.watch(dir, { recursive: true }, (eventType, filename) => {
1281
+ if (!filename) return;
1282
+ const parts = filename.split(path.sep);
1283
+ if (parts.some(p => IGNORE_DIRS.has(p))) return;
1284
+ if (!SUPPORTED_EXTS.has(path.extname(filename))) return;
1285
+
1286
+ const fullPath = path.join(dir, filename);
1287
+ if (debounceTimers.has(fullPath)) clearTimeout(debounceTimers.get(fullPath));
1288
+ debounceTimers.set(fullPath, setTimeout(() => {
1289
+ debounceTimers.delete(fullPath);
1290
+ if (fs.existsSync(fullPath)) analyzeWatchedFile(fullPath);
1291
+ }, DEBOUNCE_MS));
1292
+ });
1293
+ } catch (e) {
1294
+ console.error(chalk.red(`Failed to watch ${dir}: ${e.message}`));
1295
+ process.exit(1);
1296
+ }
1297
+
1298
+ process.on('SIGINT', () => {
1299
+ console.log(`\n\n${chalk.bold.hex('#F27D26')('\u25b2 KOREXT WATCH SUMMARY')}`);
1300
+ console.log(chalk.dim('======================================='));
1301
+ console.log(` Files checked: ${chalk.cyan(filesChecked)}`);
1302
+ console.log(` Total violations: ${totalViolations > 0 ? chalk.red(totalViolations) : chalk.green('0')}`);
1303
+ console.log(chalk.dim('\nGoodbye.\n'));
1304
+ process.exit(0);
1305
+ });
1306
+ });
1307
+
1308
+ program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "korext",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "Korext Command Line Interface",
5
5
  "type": "module",
6
6
  "main": "bin/korext.js",