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.
- package/bin/korext.js +121 -1
- 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
|
-
|
|
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);
|