delimit-cli 2.3.0 → 2.3.1

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 +175 -119
  2. package/package.json +5 -5
@@ -49,8 +49,8 @@ async function ensureAgent() {
49
49
 
50
50
  program
51
51
  .name('delimit')
52
- .description('Dynamic AI Governance with seamless mode switching')
53
- .version('2.0.0');
52
+ .description('Prevent breaking API changes before they reach production')
53
+ .version('2.3.0');
54
54
 
55
55
  // Install command with modes
56
56
  program
@@ -158,6 +158,7 @@ program
158
158
  program
159
159
  .command('mode [mode]')
160
160
  .description('Switch governance mode (advisory, guarded, enforce, auto)')
161
+
161
162
  .action(async (mode) => {
162
163
  await ensureAgent();
163
164
 
@@ -181,6 +182,7 @@ program
181
182
  program
182
183
  .command('status')
183
184
  .description('Show governance status')
185
+
184
186
  .option('--verbose', 'Show detailed status')
185
187
  .action(async (options) => {
186
188
  const agentRunning = await checkAgent();
@@ -274,6 +276,7 @@ program
274
276
  program
275
277
  .command('policy')
276
278
  .description('Manage governance policies')
279
+
277
280
  .option('--init', 'Create example policy file')
278
281
  .option('--validate', 'Validate policy syntax')
279
282
  .action(async (options) => {
@@ -326,6 +329,7 @@ overrides:
326
329
  program
327
330
  .command('auth')
328
331
  .description('Setup authentication and credentials for services')
332
+
329
333
  .option('--all', 'Setup all available services')
330
334
  .option('--github', 'Setup GitHub authentication')
331
335
  .option('--ai', 'Setup AI tools authentication')
@@ -387,6 +391,7 @@ program
387
391
  program
388
392
  .command('audit')
389
393
  .description('View governance audit log')
394
+
390
395
  .option('--tail <n>', 'Show last N entries', '10')
391
396
  .action(async (options) => {
392
397
  await ensureAgent();
@@ -412,120 +417,14 @@ program
412
417
  });
413
418
  });
414
419
 
415
- // Doctor command - diagnose issues
416
- program
417
- .command('doctor')
418
- .description('Diagnose Delimit configuration and issues')
419
- .action(async () => {
420
- console.log(chalk.blue.bold('\n🩺 Delimit Doctor\n'));
421
- const issues = [];
422
- const warnings = [];
423
- const info = [];
424
-
425
- // Check agent status
426
- const agentRunning = await checkAgent();
427
- if (!agentRunning) {
428
- issues.push('Agent is not running. Run "delimit status" to start it.');
429
- } else {
430
- info.push('Agent is running and responsive');
431
- }
432
-
433
- // Check Git hooks
434
- try {
435
- const hooksPath = execSync('git config --global core.hooksPath').toString().trim();
436
- if (hooksPath.includes('.delimit')) {
437
- info.push('Git hooks are configured correctly');
438
-
439
- // Check hook files exist
440
- const hookFiles = ['pre-commit', 'pre-push'];
441
- hookFiles.forEach(hook => {
442
- const hookFile = path.join(hooksPath, hook);
443
- if (!fs.existsSync(hookFile)) {
444
- warnings.push(`Missing hook file: ${hook}`);
445
- }
446
- });
447
- } else {
448
- warnings.push('Git hooks not pointing to Delimit. Run "delimit install" to fix.');
449
- }
450
- } catch (e) {
451
- issues.push('Git hooks not configured. Run "delimit install" to set up.');
452
- }
453
-
454
- // Check PATH
455
- const pathHasDelimit = process.env.PATH.includes('.delimit/shims');
456
- if (pathHasDelimit) {
457
- warnings.push('PATH hijacking is active (for AI tool interception)');
458
- } else {
459
- info.push('PATH is clean (no AI tool interception)');
460
- }
461
-
462
- // Check policy files
463
- const policies = [];
464
- if (fs.existsSync('delimit.yml')) {
465
- policies.push('project');
466
- // Validate policy
467
- try {
468
- const policy = yaml.load(fs.readFileSync('delimit.yml', 'utf8'));
469
- if (!policy.rules) {
470
- warnings.push('Project policy has no rules defined');
471
- }
472
- } catch (e) {
473
- issues.push(`Project policy is invalid: ${e.message}`);
474
- }
475
- }
476
-
477
- const userPolicyPath = path.join(process.env.HOME, '.config', 'delimit', 'delimit.yml');
478
- if (fs.existsSync(userPolicyPath)) {
479
- policies.push('user');
480
- }
481
-
482
- if (policies.length === 0) {
483
- warnings.push('No policy files found. Run "delimit policy --init" to create one.');
484
- } else {
485
- info.push(`Policy files loaded: ${policies.join(', ')}`);
486
- }
487
-
488
- // Check audit log
489
- const auditDir = path.join(process.env.HOME, '.delimit', 'audit');
490
- if (fs.existsSync(auditDir)) {
491
- const files = fs.readdirSync(auditDir);
492
- info.push(`Audit log has ${files.length} day(s) of history`);
493
- } else {
494
- warnings.push('No audit logs found yet');
495
- }
496
-
497
- // Display results
498
- if (issues.length > 0) {
499
- console.log(chalk.red.bold('āŒ Issues Found:\n'));
500
- issues.forEach(issue => console.log(chalk.red(` • ${issue}`)));
501
- console.log();
502
- }
503
-
504
- if (warnings.length > 0) {
505
- console.log(chalk.yellow.bold('āš ļø Warnings:\n'));
506
- warnings.forEach(warning => console.log(chalk.yellow(` • ${warning}`)));
507
- console.log();
508
- }
509
-
510
- if (info.length > 0) {
511
- console.log(chalk.green.bold('āœ… Working Correctly:\n'));
512
- info.forEach(item => console.log(chalk.green(` • ${item}`)));
513
- console.log();
514
- }
515
-
516
- // Overall status
517
- if (issues.length === 0) {
518
- console.log(chalk.green.bold('šŸŽ‰ Delimit is healthy!'));
519
- } else {
520
- console.log(chalk.red.bold('šŸ”§ Please fix the issues above'));
521
- process.exit(1);
522
- }
523
- });
420
+ // Doctor command - verify setup for API governance
421
+ // (legacy doctor replaced with v1-focused checks)
524
422
 
525
423
  // Explain-decision command - show governance decision reasoning
526
424
  program
527
425
  .command('explain-decision [decision-id]')
528
426
  .description('Explain a governance decision')
427
+
529
428
  .action(async (decisionId) => {
530
429
  await ensureAgent();
531
430
 
@@ -545,6 +444,7 @@ program
545
444
  program
546
445
  .command('uninstall')
547
446
  .description('Remove Delimit governance')
447
+
548
448
  .action(async () => {
549
449
  const { confirm } = await inquirer.prompt([{
550
450
  type: 'confirm',
@@ -637,6 +537,7 @@ program
637
537
  .command('proxy <tool>')
638
538
  .allowUnknownOption()
639
539
  .description('Proxy AI tool execution with governance')
540
+
640
541
  .action(async (tool, options) => {
641
542
  const { proxyAITool } = require('../lib/proxy-handler');
642
543
  // Get all args after the tool name
@@ -649,6 +550,7 @@ program
649
550
  program
650
551
  .command('hook <type>')
651
552
  .description('Internal hook handler')
553
+
652
554
  .action(async (type) => {
653
555
  await ensureAgent();
654
556
 
@@ -882,20 +784,167 @@ program
882
784
 
883
785
  fs.mkdirSync(configDir, { recursive: true });
884
786
  fs.writeFileSync(policyFile, POLICY_PRESETS[preset]);
885
- console.log(chalk.green(`Created .delimit/policies.yml (preset: ${preset})`));
886
- console.log('');
887
- console.log(` ${chalk.bold('strict')} — zero tolerance, all breaking changes are errors`);
888
- console.log(` ${chalk.bold('default')} — balanced, blocks destructive changes, warns on risky`);
889
- console.log(` ${chalk.bold('relaxed')} — warnings only, never blocks CI`);
890
- console.log('');
891
- console.log(`Switch preset: ${chalk.bold('delimit init --preset strict')}`);
892
- console.log('');
787
+ console.log(chalk.green(`\n Created .delimit/policies.yml (preset: ${preset})\n`));
788
+
789
+ // Auto-detect OpenAPI spec files
790
+ const specPatterns = [
791
+ 'openapi.yaml', 'openapi.yml', 'openapi.json',
792
+ 'swagger.yaml', 'swagger.yml', 'swagger.json',
793
+ 'api.yaml', 'api.yml', 'api.json',
794
+ 'docs/openapi.yaml', 'docs/openapi.yml', 'docs/openapi.json',
795
+ 'spec/openapi.yaml', 'spec/openapi.json',
796
+ 'specs/openapi.yaml', 'specs/openapi.json',
797
+ 'api/openapi.yaml', 'api/openapi.json',
798
+ 'contrib/openapi.json',
799
+ ];
800
+ const foundSpecs = specPatterns.filter(p => fs.existsSync(path.join(process.cwd(), p)));
801
+
802
+ if (foundSpecs.length > 0) {
803
+ const specPath = foundSpecs[0];
804
+ console.log(` Detected spec: ${chalk.bold(specPath)}`);
805
+ console.log('');
806
+ console.log(chalk.bold(' Add this to .github/workflows/api-governance.yml:\n'));
807
+ console.log(chalk.gray(` name: API Governance
808
+ on:
809
+ pull_request:
810
+ paths:
811
+ - '${specPath}'
812
+ permissions:
813
+ contents: read
814
+ pull-requests: write
815
+ jobs:
816
+ api-governance:
817
+ runs-on: ubuntu-latest
818
+ steps:
819
+ - uses: actions/checkout@v4
820
+ - uses: actions/checkout@v4
821
+ with:
822
+ ref: \${{ github.event.pull_request.base.sha }}
823
+ path: _base
824
+ - uses: delimit-ai/delimit@v1
825
+ with:
826
+ old_spec: _base/${specPath}
827
+ new_spec: ${specPath}
828
+ mode: advisory`));
829
+ console.log('');
830
+ } else {
831
+ console.log(' No OpenAPI spec file detected.');
832
+ console.log(` Delimit also supports ${chalk.bold('Zero-Spec Mode')} — run ${chalk.bold('delimit lint')} in a FastAPI/NestJS/Express project.`);
833
+ console.log('');
834
+ }
835
+
836
+ console.log(` ${chalk.bold('Presets')}: strict | default | relaxed`);
837
+ console.log(` Switch: ${chalk.bold('delimit init --preset strict')}\n`);
893
838
  console.log('Next steps:');
894
839
  console.log(` ${chalk.bold('delimit lint')} old.yaml new.yaml — check for breaking changes`);
895
840
  console.log(` ${chalk.bold('delimit diff')} old.yaml new.yaml — see all changes`);
896
841
  console.log(` ${chalk.bold('delimit explain')} old.yaml new.yaml — human-readable summary`);
897
842
  });
898
843
 
844
+ // Doctor command — verify setup is correct
845
+ program
846
+ .command('doctor')
847
+ .description('Verify Delimit setup and diagnose common issues')
848
+ .action(async () => {
849
+ console.log(chalk.bold('\n Delimit Doctor\n'));
850
+ let ok = 0;
851
+ let warn = 0;
852
+ let fail = 0;
853
+
854
+ // Check policy file
855
+ const policyPath = path.join(process.cwd(), '.delimit', 'policies.yml');
856
+ if (fs.existsSync(policyPath)) {
857
+ console.log(chalk.green(' āœ“ .delimit/policies.yml found'));
858
+ ok++;
859
+ try {
860
+ const yaml = require('js-yaml');
861
+ const policy = yaml.load(fs.readFileSync(policyPath, 'utf8'));
862
+ if (policy && (policy.rules !== undefined || policy.override_defaults !== undefined)) {
863
+ console.log(chalk.green(' āœ“ Policy file is valid YAML'));
864
+ ok++;
865
+ } else {
866
+ console.log(chalk.yellow(' ⚠ Policy file has no rules section'));
867
+ warn++;
868
+ }
869
+ } catch (e) {
870
+ console.log(chalk.red(` āœ— Policy file has invalid YAML: ${e.message}`));
871
+ fail++;
872
+ }
873
+ } else {
874
+ console.log(chalk.red(' āœ— No .delimit/policies.yml — run: delimit init'));
875
+ fail++;
876
+ }
877
+
878
+ // Check for OpenAPI spec
879
+ const specPatterns = [
880
+ 'openapi.yaml', 'openapi.yml', 'openapi.json',
881
+ 'swagger.yaml', 'swagger.yml', 'swagger.json',
882
+ 'docs/openapi.yaml', 'docs/openapi.yml', 'docs/openapi.json',
883
+ 'spec/openapi.yaml', 'spec/openapi.json',
884
+ 'api/openapi.yaml', 'api/openapi.json',
885
+ 'contrib/openapi.json',
886
+ ];
887
+ const foundSpecs = specPatterns.filter(p => fs.existsSync(path.join(process.cwd(), p)));
888
+ if (foundSpecs.length > 0) {
889
+ console.log(chalk.green(` āœ“ OpenAPI spec found: ${foundSpecs[0]}`));
890
+ ok++;
891
+ } else {
892
+ // Check for framework (Zero-Spec candidate)
893
+ const pkgJson = path.join(process.cwd(), 'package.json');
894
+ const reqTxt = path.join(process.cwd(), 'requirements.txt');
895
+ if (fs.existsSync(pkgJson) || fs.existsSync(reqTxt)) {
896
+ console.log(chalk.yellow(' ⚠ No OpenAPI spec file — Zero-Spec Mode may work if this is a FastAPI/NestJS/Express project'));
897
+ warn++;
898
+ } else {
899
+ console.log(chalk.red(' āœ— No OpenAPI spec file found'));
900
+ fail++;
901
+ }
902
+ }
903
+
904
+ // Check for GitHub workflow
905
+ const workflowDir = path.join(process.cwd(), '.github', 'workflows');
906
+ if (fs.existsSync(workflowDir)) {
907
+ const workflows = fs.readdirSync(workflowDir);
908
+ const hasDelimit = workflows.some(f => {
909
+ try {
910
+ const content = fs.readFileSync(path.join(workflowDir, f), 'utf8');
911
+ return content.includes('delimit-ai/delimit') || content.includes('delimit');
912
+ } catch { return false; }
913
+ });
914
+ if (hasDelimit) {
915
+ console.log(chalk.green(' āœ“ GitHub Action workflow found'));
916
+ ok++;
917
+ } else {
918
+ console.log(chalk.yellow(' ⚠ No Delimit GitHub Action workflow — run delimit init for setup instructions'));
919
+ warn++;
920
+ }
921
+ } else {
922
+ console.log(chalk.yellow(' ⚠ No .github/workflows/ directory'));
923
+ warn++;
924
+ }
925
+
926
+ // Check git
927
+ try {
928
+ const { execSync } = require('child_process');
929
+ execSync('git rev-parse --git-dir', { stdio: 'pipe' });
930
+ console.log(chalk.green(' āœ“ Git repository detected'));
931
+ ok++;
932
+ } catch {
933
+ console.log(chalk.yellow(' ⚠ Not a git repository'));
934
+ warn++;
935
+ }
936
+
937
+ // Summary
938
+ console.log('');
939
+ if (fail === 0 && warn === 0) {
940
+ console.log(chalk.green.bold(' All checks passed! Ready to lint.\n'));
941
+ } else if (fail === 0) {
942
+ console.log(chalk.yellow.bold(` ${ok} passed, ${warn} warning(s). Setup looks good.\n`));
943
+ } else {
944
+ console.log(chalk.red.bold(` ${ok} passed, ${warn} warning(s), ${fail} error(s). Fix errors above.\n`));
945
+ }
946
+ });
947
+
899
948
  // Lint command — diff + policy (primary command)
900
949
  // Supports zero-spec mode: `delimit lint` (no args) auto-extracts from FastAPI
901
950
  program
@@ -1078,4 +1127,11 @@ program
1078
1127
  }
1079
1128
  });
1080
1129
 
1130
+ // Hide legacy/internal commands from --help
1131
+ ['install', 'mode', 'status', 'policy', 'auth', 'audit',
1132
+ 'explain-decision', 'uninstall', 'proxy', 'hook'].forEach(name => {
1133
+ const cmd = program.commands.find(c => c.name() === name);
1134
+ if (cmd) cmd._hidden = true;
1135
+ });
1136
+
1081
1137
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "delimit-cli",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "description": "Prevent breaking API changes before they reach production. Deterministic diff engine + policy enforcement + semver classification.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -9,7 +9,7 @@
9
9
  "scripts": {
10
10
  "postinstall": "echo 'Run \"delimit install\" to set up governance'",
11
11
  "install": "bash ./hooks/install-hooks.sh install",
12
- "install-mcp": "bash ./hooks/install-hooks.sh mcp-only",
12
+ "install-mcp": "bash ./hooks/install-hooks.sh mcp-only",
13
13
  "test-mcp": "bash ./hooks/install-hooks.sh troubleshoot",
14
14
  "fix-mcp": "bash ./hooks/install-hooks.sh fix-mcp",
15
15
  "test": "echo 'Governance is context-aware' && exit 0"
@@ -44,15 +44,15 @@
44
44
  "url": "https://github.com/delimit-ai/delimit.git"
45
45
  },
46
46
  "dependencies": {
47
- "commander": "^9.0.0",
48
47
  "axios": "^1.0.0",
49
48
  "chalk": "^4.1.2",
50
- "inquirer": "^8.2.0",
49
+ "commander": "^12.1.0",
51
50
  "express": "^4.18.0",
51
+ "inquirer": "^8.2.0",
52
52
  "js-yaml": "^4.1.0",
53
53
  "minimatch": "^5.1.0"
54
54
  },
55
55
  "engines": {
56
56
  "node": ">=14.0.0"
57
57
  }
58
- }
58
+ }