delimit-cli 4.1.2 → 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 +77 -35
  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' };
@@ -2371,48 +2398,63 @@ program
2371
2398
  console.log(chalk.red(` Error: ${e.message}`));
2372
2399
  }
2373
2400
  } else {
2374
- // Project directory — find specs and scan
2375
- console.log(chalk.gray(` Scanning ${path.resolve(target)}...\n`));
2401
+ // Project directory — find specs using simple glob, no server.py needed
2402
+ console.log(chalk.gray(` Scanning ${target}...\n`));
2376
2403
  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]}...`));
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`));
2402
2428
  try {
2403
2429
  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 }
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 }
2406
2432
  );
2407
2433
  const health = JSON.parse(healthResult);
2408
2434
  const gradeColors = { A: 'green', B: 'blue', C: 'yellow', D: 'red', F: 'red' };
2409
2435
  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')}`);
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
+ }
2411
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'));
2412
2454
  }
2413
2455
  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`);
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`);
2416
2458
  } catch (e) {
2417
2459
  console.log(chalk.red(` Error: ${e.message}`));
2418
2460
  }
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.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": [