delimit-cli 4.1.2 → 4.1.4

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 +117 -39
  2. package/package.json +1 -1
@@ -2333,9 +2333,36 @@ program
2333
2333
  .command('scan [path]')
2334
2334
  .description('Scan a project or OpenAPI spec for governance insights')
2335
2335
  .action(async (specPath) => {
2336
- const target = specPath || '.';
2336
+ const target = specPath ? path.resolve(specPath) : process.cwd();
2337
2337
  console.log(chalk.bold('\n Delimit Scan\n'));
2338
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
+
2339
2366
  // Detect if target is a spec file or a project directory
2340
2367
  const isFile = fs.existsSync(target) && fs.statSync(target).isFile();
2341
2368
 
@@ -2344,8 +2371,8 @@ program
2344
2371
  console.log(chalk.gray(` Analyzing ${target}...\n`));
2345
2372
  try {
2346
2373
  const result = execSync(
2347
- `python3 -c "import sys,json; sys.path.insert(0,'${continuityContext.serverDir}'); from core.spec_health import score_spec; import yaml; spec=yaml.safe_load(open('${target}')); r=score_spec(spec); print(json.dumps(r))"`,
2348
- { encoding: 'utf-8', timeout: 15000, cwd: continuityContext.serverDir }
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 }
2349
2376
  );
2350
2377
  const health = JSON.parse(result);
2351
2378
  const gradeColors = { A: 'green', B: 'blue', C: 'yellow', D: 'red', F: 'red' };
@@ -2364,55 +2391,106 @@ program
2364
2391
  console.log(` ${chalk.yellow('→')} ${text}`);
2365
2392
  });
2366
2393
  }
2367
- console.log(chalk.bold('\n Next steps:\n'));
2368
- console.log(` ${chalk.green('npx delimit-cli lint')} ${target} ${chalk.gray('— check for breaking changes')}`);
2369
- console.log(` ${chalk.green('npx delimit-cli init')} ${chalk.gray('— set up governance for this project')}\n`);
2394
+ // Interactive next step picker
2395
+ try {
2396
+ const { next } = await inquirer.prompt([{
2397
+ type: 'list',
2398
+ name: 'next',
2399
+ message: 'What next?',
2400
+ choices: [
2401
+ { name: `Lint this spec for breaking changes`, value: 'lint' },
2402
+ { name: 'Set up governance for this project', value: 'init' },
2403
+ { name: 'Configure AI assistants (Claude, Codex, Gemini)', value: 'setup' },
2404
+ { name: 'Exit', value: 'exit' },
2405
+ ],
2406
+ }]);
2407
+ if (next === 'lint') {
2408
+ execSync(`npx delimit-cli lint ${target}`, { stdio: 'inherit' });
2409
+ } else if (next === 'init') {
2410
+ execSync('npx delimit-cli init', { stdio: 'inherit' });
2411
+ } else if (next === 'setup') {
2412
+ execSync('npx delimit-cli setup', { stdio: 'inherit' });
2413
+ }
2414
+ } catch {}
2370
2415
  } catch (e) {
2371
2416
  console.log(chalk.red(` Error: ${e.message}`));
2372
2417
  }
2373
2418
  } else {
2374
- // Project directory — find specs and scan
2375
- console.log(chalk.gray(` Scanning ${path.resolve(target)}...\n`));
2419
+ // Project directory — find specs using simple glob, no server.py needed
2420
+ console.log(chalk.gray(` Scanning ${target}...\n`));
2376
2421
  try {
2377
- const result = execSync(
2378
- `python3 -c "import sys,json; sys.path.insert(0,'${continuityContext.serverDir}'); from ai.server import delimit_scan; r=delimit_scan.fn('${target}') if hasattr(delimit_scan,'fn') else delimit_scan('${target}'); print(json.dumps(r))"`,
2379
- { encoding: 'utf-8', timeout: 30000, cwd: continuityContext.serverDir }
2380
- );
2381
- const scan = JSON.parse(result);
2382
- const findings = scan.findings || [];
2383
- const specs = findings.find(f => f.type === 'openapi_specs');
2384
- if (specs) {
2385
- console.log(` ${chalk.green('✓')} Found ${specs.count} OpenAPI spec(s): ${specs.files.slice(0, 3).join(', ')}`);
2386
- } else {
2387
- console.log(` ${chalk.yellow('—')} No OpenAPI specs found`);
2388
- }
2389
- const frameworks = findings.filter(f => f.type === 'framework');
2390
- frameworks.forEach(f => console.log(` ${chalk.green('✓')} Framework: ${f.name}`));
2391
- const suggestions = scan.suggestions || [];
2392
- if (suggestions.length > 0) {
2393
- console.log(chalk.bold('\n Suggestions:\n'));
2394
- suggestions.slice(0, 3).forEach(s => {
2395
- console.log(` ${chalk.blue('→')} ${s.detail}`);
2396
- });
2397
- }
2398
- // If spec found, run health on it
2399
- if (specs && specs.files.length > 0) {
2400
- const specFile = path.join(target, specs.files[0]);
2401
- console.log(chalk.gray(`\n Running spec health on ${specs.files[0]}...`));
2422
+ // Find OpenAPI/Swagger specs
2423
+ const specPatterns = ['openapi.yaml', 'openapi.yml', 'openapi.json', 'swagger.yaml', 'swagger.yml', 'swagger.json'];
2424
+ const found = [];
2425
+ const _findSpecs = (dir, depth) => {
2426
+ if (depth > 4) return;
2427
+ try {
2428
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
2429
+ if (entry.name === 'node_modules' || entry.name === '.next' || entry.name === 'venv' || entry.name === '.git') continue;
2430
+ const full = path.join(dir, entry.name);
2431
+ if (entry.isFile() && specPatterns.includes(entry.name.toLowerCase())) {
2432
+ found.push(path.relative(target, full));
2433
+ } else if (entry.isDirectory()) {
2434
+ _findSpecs(full, depth + 1);
2435
+ }
2436
+ }
2437
+ } catch {}
2438
+ };
2439
+ _findSpecs(target, 0);
2440
+
2441
+ if (found.length > 0) {
2442
+ console.log(` ${chalk.green('✓')} Found ${found.length} OpenAPI spec(s): ${found.slice(0, 3).join(', ')}`);
2443
+ // Run health on the first spec
2444
+ const specFile = path.join(target, found[0]);
2445
+ console.log(chalk.gray(`\n Scoring ${found[0]}...\n`));
2402
2446
  try {
2403
2447
  const healthResult = execSync(
2404
- `python3 -c "import sys,json; sys.path.insert(0,'${continuityContext.serverDir}'); from core.spec_health import score_spec; import yaml; spec=yaml.safe_load(open('${specFile}')); r=score_spec(spec); print(json.dumps(r))"`,
2405
- { encoding: 'utf-8', timeout: 15000, cwd: continuityContext.serverDir }
2448
+ `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))"`,
2449
+ { encoding: 'utf-8', timeout: 15000, cwd: serverDir }
2406
2450
  );
2407
2451
  const health = JSON.parse(healthResult);
2408
2452
  const gradeColors = { A: 'green', B: 'blue', C: 'yellow', D: 'red', F: 'red' };
2409
2453
  const gradeColor = gradeColors[health.grade] || 'white';
2410
- console.log(`\n ${chalk[gradeColor].bold(health.grade)} ${chalk.white.bold(health.overall_score + '/100')} ${chalk.gray('Spec Health Score')}`);
2454
+ console.log(` ${chalk[gradeColor].bold(health.grade)} ${chalk.white.bold(health.overall_score + '/100')} ${chalk.gray('Spec Health Score')}\n`);
2455
+ for (const [dim, data] of Object.entries(health.dimensions || {})) {
2456
+ const score = data.score || 0;
2457
+ const bar = '\u2588'.repeat(Math.round(score / 5)) + '\u2591'.repeat(20 - Math.round(score / 5));
2458
+ const color = score >= 70 ? 'green' : score >= 40 ? 'yellow' : 'red';
2459
+ console.log(` ${chalk.gray(dim.padEnd(16))} ${chalk[color](bar)} ${score}`);
2460
+ }
2461
+ if (health.recommendations && health.recommendations.length > 0) {
2462
+ console.log(chalk.bold('\n Recommendations:\n'));
2463
+ health.recommendations.slice(0, 5).forEach(r => {
2464
+ const text = typeof r === 'object' ? (r.recommendation || r.text || JSON.stringify(r)) : r;
2465
+ console.log(` ${chalk.yellow('\u2192')} ${text}`);
2466
+ });
2467
+ }
2411
2468
  } catch {}
2469
+ } else {
2470
+ console.log(` ${chalk.yellow('\u2014')} No OpenAPI specs found in this directory`);
2471
+ console.log(chalk.gray(' Tip: point at a spec file directly: npx delimit-cli scan openapi.yaml'));
2412
2472
  }
2413
- console.log(chalk.bold('\n Next:\n'));
2414
- console.log(` ${chalk.green('npx delimit-cli init')} ${chalk.gray('— set up governance')}`);
2415
- console.log(` ${chalk.green('npx delimit-cli setup')} ${chalk.gray('— configure AI assistants')}\n`);
2473
+ // Interactive next step picker
2474
+ try {
2475
+ const { next } = await inquirer.prompt([{
2476
+ type: 'list',
2477
+ name: 'next',
2478
+ message: 'What next?',
2479
+ choices: [
2480
+ { name: 'Set up governance for this project', value: 'init' },
2481
+ { name: 'Configure AI assistants (Claude, Codex, Gemini)', value: 'setup' },
2482
+ { name: 'Run a breaking change demo', value: 'try' },
2483
+ { name: 'Exit', value: 'exit' },
2484
+ ],
2485
+ }]);
2486
+ if (next === 'init') {
2487
+ execSync('npx delimit-cli init', { stdio: 'inherit' });
2488
+ } else if (next === 'setup') {
2489
+ execSync('npx delimit-cli setup', { stdio: 'inherit' });
2490
+ } else if (next === 'try') {
2491
+ execSync('npx delimit-cli try', { stdio: 'inherit' });
2492
+ }
2493
+ } catch {}
2416
2494
  } catch (e) {
2417
2495
  console.log(chalk.red(` Error: ${e.message}`));
2418
2496
  }
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.2",
4
+ "version": "4.1.4",
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": [