delimit-cli 4.1.1 → 4.1.3

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/delimit-cli.js +133 -0
  2. package/package.json +1 -1
@@ -2328,6 +2328,139 @@ program
2328
2328
  console.log('');
2329
2329
  });
2330
2330
 
2331
+ // Scan command — instant governance analysis of any project or spec
2332
+ program
2333
+ .command('scan [path]')
2334
+ .description('Scan a project or OpenAPI spec for governance insights')
2335
+ .action(async (specPath) => {
2336
+ const target = specPath ? path.resolve(specPath) : process.cwd();
2337
+ console.log(chalk.bold('\n Delimit Scan\n'));
2338
+
2339
+ // Resolve gateway dir: installed server > bundled in npm package
2340
+ const bundledGateway = path.join(__dirname, '..', 'gateway');
2341
+ const serverDir = (continuityContext.serverDir && continuityContext.serverDir !== 'undefined' && fs.existsSync(continuityContext.serverDir))
2342
+ ? continuityContext.serverDir
2343
+ : fs.existsSync(bundledGateway) ? bundledGateway : null;
2344
+
2345
+ if (!serverDir) {
2346
+ console.log(chalk.yellow(' Gateway not found. Installing...\n'));
2347
+ try {
2348
+ execSync('npx delimit-cli setup --yes', { stdio: 'inherit', timeout: 60000 });
2349
+ // Retry after setup
2350
+ console.log(chalk.green('\n Setup complete. Re-running scan...\n'));
2351
+ execSync(`npx delimit-cli scan ${specPath || ''}`, { stdio: 'inherit', timeout: 30000 });
2352
+ } catch {
2353
+ console.log(chalk.red('\n Auto-setup failed. Run manually: npx delimit-cli setup'));
2354
+ }
2355
+ return;
2356
+ }
2357
+
2358
+ // Check Python + yaml dependency
2359
+ try {
2360
+ execSync('python3 -c "import yaml"', { stdio: 'ignore', timeout: 5000 });
2361
+ } catch {
2362
+ console.log(chalk.yellow(' Installing Python dependency (pyyaml)...\n'));
2363
+ try { execSync('pip3 install pyyaml -q', { stdio: 'ignore', timeout: 30000 }); } catch {}
2364
+ }
2365
+
2366
+ // Detect if target is a spec file or a project directory
2367
+ const isFile = fs.existsSync(target) && fs.statSync(target).isFile();
2368
+
2369
+ if (isFile) {
2370
+ // Spec file — run spec health + show results
2371
+ console.log(chalk.gray(` Analyzing ${target}...\n`));
2372
+ try {
2373
+ const result = execSync(
2374
+ `python3 -c "import sys,json; sys.path.insert(0,'${serverDir}'); from core.spec_health import score_spec; import yaml; spec=yaml.safe_load(open('${target}')); r=score_spec(spec); print(json.dumps(r))"`,
2375
+ { encoding: 'utf-8', timeout: 15000, cwd: serverDir }
2376
+ );
2377
+ const health = JSON.parse(result);
2378
+ const gradeColors = { A: 'green', B: 'blue', C: 'yellow', D: 'red', F: 'red' };
2379
+ const gradeColor = gradeColors[health.grade] || 'white';
2380
+ console.log(` ${chalk[gradeColor].bold(health.grade)} ${chalk.white.bold(health.overall_score + '/100')} ${chalk.gray('Spec Health Score')}\n`);
2381
+ for (const [dim, data] of Object.entries(health.dimensions || {})) {
2382
+ const score = data.score || 0;
2383
+ const bar = '█'.repeat(Math.round(score / 5)) + '░'.repeat(20 - Math.round(score / 5));
2384
+ const color = score >= 70 ? 'green' : score >= 40 ? 'yellow' : 'red';
2385
+ console.log(` ${chalk.gray(dim.padEnd(16))} ${chalk[color](bar)} ${score}`);
2386
+ }
2387
+ if (health.recommendations && health.recommendations.length > 0) {
2388
+ console.log(chalk.bold('\n Recommendations:\n'));
2389
+ health.recommendations.slice(0, 5).forEach(r => {
2390
+ const text = typeof r === 'object' ? (r.recommendation || r.text || JSON.stringify(r)) : r;
2391
+ console.log(` ${chalk.yellow('→')} ${text}`);
2392
+ });
2393
+ }
2394
+ console.log(chalk.bold('\n Next steps:\n'));
2395
+ console.log(` ${chalk.green('npx delimit-cli lint')} ${target} ${chalk.gray('— check for breaking changes')}`);
2396
+ console.log(` ${chalk.green('npx delimit-cli init')} ${chalk.gray('— set up governance for this project')}\n`);
2397
+ } catch (e) {
2398
+ console.log(chalk.red(` Error: ${e.message}`));
2399
+ }
2400
+ } else {
2401
+ // Project directory — find specs using simple glob, no server.py needed
2402
+ console.log(chalk.gray(` Scanning ${target}...\n`));
2403
+ try {
2404
+ // Find OpenAPI/Swagger specs
2405
+ const specPatterns = ['openapi.yaml', 'openapi.yml', 'openapi.json', 'swagger.yaml', 'swagger.yml', 'swagger.json'];
2406
+ const found = [];
2407
+ const _findSpecs = (dir, depth) => {
2408
+ if (depth > 4) return;
2409
+ try {
2410
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
2411
+ if (entry.name === 'node_modules' || entry.name === '.next' || entry.name === 'venv' || entry.name === '.git') continue;
2412
+ const full = path.join(dir, entry.name);
2413
+ if (entry.isFile() && specPatterns.includes(entry.name.toLowerCase())) {
2414
+ found.push(path.relative(target, full));
2415
+ } else if (entry.isDirectory()) {
2416
+ _findSpecs(full, depth + 1);
2417
+ }
2418
+ }
2419
+ } catch {}
2420
+ };
2421
+ _findSpecs(target, 0);
2422
+
2423
+ if (found.length > 0) {
2424
+ console.log(` ${chalk.green('✓')} Found ${found.length} OpenAPI spec(s): ${found.slice(0, 3).join(', ')}`);
2425
+ // Run health on the first spec
2426
+ const specFile = path.join(target, found[0]);
2427
+ console.log(chalk.gray(`\n Scoring ${found[0]}...\n`));
2428
+ try {
2429
+ const healthResult = execSync(
2430
+ `python3 -c "import sys,json; sys.path.insert(0,'${serverDir}'); from core.spec_health import score_spec; import yaml; spec=yaml.safe_load(open('${specFile}')); r=score_spec(spec); print(json.dumps(r))"`,
2431
+ { encoding: 'utf-8', timeout: 15000, cwd: serverDir }
2432
+ );
2433
+ const health = JSON.parse(healthResult);
2434
+ const gradeColors = { A: 'green', B: 'blue', C: 'yellow', D: 'red', F: 'red' };
2435
+ const gradeColor = gradeColors[health.grade] || 'white';
2436
+ console.log(` ${chalk[gradeColor].bold(health.grade)} ${chalk.white.bold(health.overall_score + '/100')} ${chalk.gray('Spec Health Score')}\n`);
2437
+ for (const [dim, data] of Object.entries(health.dimensions || {})) {
2438
+ const score = data.score || 0;
2439
+ const bar = '\u2588'.repeat(Math.round(score / 5)) + '\u2591'.repeat(20 - Math.round(score / 5));
2440
+ const color = score >= 70 ? 'green' : score >= 40 ? 'yellow' : 'red';
2441
+ console.log(` ${chalk.gray(dim.padEnd(16))} ${chalk[color](bar)} ${score}`);
2442
+ }
2443
+ if (health.recommendations && health.recommendations.length > 0) {
2444
+ console.log(chalk.bold('\n Recommendations:\n'));
2445
+ health.recommendations.slice(0, 5).forEach(r => {
2446
+ const text = typeof r === 'object' ? (r.recommendation || r.text || JSON.stringify(r)) : r;
2447
+ console.log(` ${chalk.yellow('\u2192')} ${text}`);
2448
+ });
2449
+ }
2450
+ } catch {}
2451
+ } else {
2452
+ console.log(` ${chalk.yellow('\u2014')} No OpenAPI specs found in this directory`);
2453
+ console.log(chalk.gray(' Tip: point at a spec file directly: npx delimit-cli scan openapi.yaml'));
2454
+ }
2455
+ console.log(chalk.bold('\n Next:\n'));
2456
+ console.log(` ${chalk.green('npx delimit-cli init')} ${chalk.gray('\u2014 set up governance')}`);
2457
+ console.log(` ${chalk.green('npx delimit-cli setup')} ${chalk.gray('\u2014 configure AI assistants')}\n`);
2458
+ } catch (e) {
2459
+ console.log(chalk.red(` Error: ${e.message}`));
2460
+ }
2461
+ }
2462
+ });
2463
+
2331
2464
  // Try command — zero-risk demo with Markdown report artifact (LED-264)
2332
2465
  program
2333
2466
  .command('try')
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "delimit-cli",
3
3
  "mcpName": "io.github.delimit-ai/delimit-mcp-server",
4
- "version": "4.1.1",
4
+ "version": "4.1.3",
5
5
  "description": "Unify Claude Code, Codex, Cursor, and Gemini CLI with persistent context, governance, and multi-model debate.",
6
6
  "main": "index.js",
7
7
  "files": [