delimit-cli 2.2.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 (3) hide show
  1. package/README.md +91 -37
  2. package/bin/delimit-cli.js +300 -130
  3. package/package.json +6 -6
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # delimit-cli
2
2
 
3
- **ESLint for API contracts** — detect breaking changes, enforce semver, and generate migration guides for OpenAPI specs.
3
+ **Prevent breaking API changes before they reach production.**
4
+
5
+ Deterministic diff engine + policy enforcement + semver classification for OpenAPI specs. The independent successor to Optic.
4
6
 
5
7
  [![npm](https://img.shields.io/npm/v/delimit-cli)](https://www.npmjs.com/package/delimit-cli)
6
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
@@ -11,46 +13,76 @@
11
13
  npm install -g delimit-cli
12
14
  ```
13
15
 
14
- This installs the `delimit` command globally.
15
-
16
- ## Quick Start
16
+ ## Quick Start (Under 5 Minutes)
17
17
 
18
18
  ```bash
19
- # Initialize a policy file in your repo
20
- delimit init
19
+ # 1. Initialize with a policy preset
20
+ delimit init --preset default
21
21
 
22
- # Detect breaking changes between two specs
22
+ # 2. Detect breaking changes
23
23
  delimit lint api/openapi-old.yaml api/openapi-new.yaml
24
24
 
25
- # See raw diff output
26
- delimit diff api/openapi-old.yaml api/openapi-new.yaml
27
-
28
- # Generate a human-readable explanation
29
- delimit explain api/openapi-old.yaml api/openapi-new.yaml
25
+ # 3. Add the GitHub Action for automated PR checks
26
+ # Copy .github/workflows/api-governance.yml (see CI section below)
30
27
  ```
31
28
 
29
+ ## What It Catches
30
+
31
+ Delimit deterministically detects 23 types of API changes, including 10 breaking patterns:
32
+
33
+ - Endpoint or method removal
34
+ - Required parameter addition
35
+ - Response field removal
36
+ - Type changes
37
+ - Enum value removal
38
+ - And more
39
+
40
+ Every change is classified as `MAJOR`, `MINOR`, `PATCH`, or `NONE` per semver.
41
+
32
42
  ## Commands
33
43
 
34
44
  | Command | Description |
35
45
  |---------|-------------|
36
- | `delimit init` | Create `.delimit/policies.yml` with default rules |
37
- | `delimit lint <old> <new>` | Diff + policy check with semver badge and violations |
46
+ | `delimit init` | Create `.delimit/policies.yml` with a policy preset |
47
+ | `delimit lint <old> <new>` | Diff + policy check returns exit code 1 on violations |
38
48
  | `delimit diff <old> <new>` | Raw diff with `[BREAKING]`/`[safe]` tags |
39
49
  | `delimit explain <old> <new>` | Human-readable change explanation |
40
50
 
41
- ### Options
51
+ ## Policy Presets
52
+
53
+ Choose a preset that fits your team:
54
+
55
+ ```bash
56
+ delimit init --preset strict # Public APIs, payments — zero tolerance
57
+ delimit init --preset default # Most teams — balanced rules
58
+ delimit init --preset relaxed # Internal APIs, startups — warnings only
59
+ ```
60
+
61
+ | Preset | Breaking changes | Type changes | Field removal |
62
+ |--------|-----------------|--------------|---------------|
63
+ | `strict` | Error (blocks) | Error (blocks) | Error (blocks) |
64
+ | `default` | Error (blocks) | Warning | Error (blocks) |
65
+ | `relaxed` | Warning | Warning | Info |
66
+
67
+ Pass a preset directly to lint:
68
+
69
+ ```bash
70
+ delimit lint --policy strict old.yaml new.yaml
71
+ ```
72
+
73
+ ## Options
42
74
 
43
75
  ```bash
44
- # Specify explainer template (default: developer)
76
+ # Semver classification with version bump
77
+ delimit lint old.yaml new.yaml --current-version 1.0.0
78
+
79
+ # Explainer templates
45
80
  delimit explain old.yaml new.yaml -t migration
46
81
  delimit explain old.yaml new.yaml -t pr_comment
47
82
  delimit explain old.yaml new.yaml -t changelog
48
83
 
49
- # Include semver classification
50
- delimit lint old.yaml new.yaml --current-version 1.0.0
51
-
52
- # Use custom policy file
53
- delimit lint old.yaml new.yaml -p .delimit/policies.yml
84
+ # JSON output for scripting
85
+ delimit lint old.yaml new.yaml --json
54
86
  ```
55
87
 
56
88
  ### Explainer Templates
@@ -67,34 +99,56 @@ delimit lint old.yaml new.yaml -p .delimit/policies.yml
67
99
 
68
100
  ## CI/CD Integration
69
101
 
70
- For automated PR checks, use the GitHub Action:
102
+ Add this workflow to `.github/workflows/api-governance.yml`:
71
103
 
72
104
  ```yaml
73
- - uses: delimit-ai/delimit-action@v1
74
- with:
75
- old_spec: base/api/openapi.yaml
76
- new_spec: api/openapi.yaml
105
+ name: API Governance
106
+ on:
107
+ pull_request:
108
+ paths:
109
+ - 'path/to/openapi.yaml' # adjust to your spec path
110
+ permissions:
111
+ contents: read
112
+ pull-requests: write
113
+ jobs:
114
+ api-governance:
115
+ runs-on: ubuntu-latest
116
+ steps:
117
+ - uses: actions/checkout@v4
118
+ - uses: actions/checkout@v4
119
+ with:
120
+ ref: ${{ github.event.pull_request.base.sha }}
121
+ path: _base
122
+ - uses: delimit-ai/delimit@v1
123
+ with:
124
+ old_spec: _base/path/to/openapi.yaml
125
+ new_spec: path/to/openapi.yaml
126
+ mode: advisory # or 'enforce' to block PRs
77
127
  ```
78
128
 
129
+ The action posts a PR comment with:
130
+ - Semver badge (`MAJOR` / `MINOR` / `PATCH`)
131
+ - Violation table with severity
132
+ - Expandable migration guide for breaking changes
133
+
79
134
  See [Delimit API Governance](https://github.com/marketplace/actions/delimit-api-governance) on the GitHub Marketplace.
80
135
 
81
136
  ## Custom Policies
82
137
 
83
- Create `.delimit/policies.yml`:
138
+ Create `.delimit/policies.yml` or start from a preset:
84
139
 
85
140
  ```yaml
141
+ override_defaults: false
142
+
86
143
  rules:
87
- - id: no_endpoint_removal
88
- change_types: [endpoint_removed]
144
+ - id: protect_v1
145
+ name: Protect V1 API
146
+ change_types: [endpoint_removed, method_removed, field_removed]
89
147
  severity: error
90
148
  action: forbid
91
- message: "Endpoints cannot be removed without deprecation"
92
-
93
- - id: warn_type_change
94
- change_types: [type_changed]
95
- severity: warning
96
- action: warn
97
- message: "Type change may break clients"
149
+ conditions:
150
+ path_pattern: "^/v1/.*"
151
+ message: "V1 API is frozen. Make changes in V2."
98
152
  ```
99
153
 
100
154
  ## Supported Specs
@@ -105,7 +159,7 @@ rules:
105
159
 
106
160
  ## Links
107
161
 
108
- - [GitHub Action](https://github.com/marketplace/actions/delimit-api-governance) — CI/CD integration
162
+ - [GitHub Action](https://github.com/marketplace/actions/delimit-api-governance) — Automated PR checks
109
163
  - [GitHub](https://github.com/delimit-ai/delimit) — Source code
110
164
  - [Issues](https://github.com/delimit-ai/delimit/issues) — Bug reports and feature requests
111
165
 
@@ -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
 
@@ -741,11 +643,131 @@ program
741
643
 
742
644
  const apiEngine = require('../lib/api-engine');
743
645
 
646
+ // Policy preset templates
647
+ const POLICY_PRESETS = {
648
+ strict: `# Delimit Policy Preset: strict
649
+ # For public APIs, payment systems, and regulated environments.
650
+ # Zero tolerance for breaking changes.
651
+
652
+ override_defaults: true
653
+
654
+ rules:
655
+ - id: no_endpoint_removal
656
+ name: Forbid Endpoint Removal
657
+ change_types: [endpoint_removed]
658
+ severity: error
659
+ action: forbid
660
+ message: "Endpoint {path} cannot be removed. Deprecate with Sunset header first."
661
+
662
+ - id: no_method_removal
663
+ name: Forbid Method Removal
664
+ change_types: [method_removed]
665
+ severity: error
666
+ action: forbid
667
+ message: "HTTP method removed from {path}. This breaks all clients."
668
+
669
+ - id: no_required_param_addition
670
+ name: Forbid Required Parameter Addition
671
+ change_types: [required_param_added]
672
+ severity: error
673
+ action: forbid
674
+ message: "Cannot add required parameter to {path}. Make it optional."
675
+
676
+ - id: no_field_removal
677
+ name: Forbid Response Field Removal
678
+ change_types: [field_removed]
679
+ severity: error
680
+ action: forbid
681
+ message: "Cannot remove field from {path}. Deprecate it first."
682
+
683
+ - id: no_type_change
684
+ name: Forbid Type Changes
685
+ change_types: [type_changed]
686
+ severity: error
687
+ action: forbid
688
+ message: "Type change at {path} breaks client deserialization."
689
+
690
+ - id: no_enum_removal
691
+ name: Forbid Enum Value Removal
692
+ change_types: [enum_value_removed]
693
+ severity: error
694
+ action: forbid
695
+ message: "Enum value removed at {path}."
696
+
697
+ - id: no_param_removal
698
+ name: Forbid Parameter Removal
699
+ change_types: [param_removed]
700
+ severity: error
701
+ action: forbid
702
+ message: "Parameter removed from {path}."
703
+ `,
704
+ default: `# Delimit Policy Preset: default
705
+ # Balanced rules for most teams. Blocks destructive changes, warns on risky ones.
706
+ # Uses built-in defaults — customize by adding rules below.
707
+
708
+ override_defaults: false
709
+
710
+ rules: []
711
+ # Add custom rules here. Example:
712
+ # - id: protect_v1
713
+ # name: Protect V1 API
714
+ # change_types: [endpoint_removed, method_removed, field_removed]
715
+ # severity: error
716
+ # action: forbid
717
+ # conditions:
718
+ # path_pattern: "^/v1/.*"
719
+ # message: "V1 API is frozen. Make changes in V2."
720
+ `,
721
+ relaxed: `# Delimit Policy Preset: relaxed
722
+ # For internal APIs, early-stage startups, and rapid iteration.
723
+ # Only warns — never blocks CI.
724
+
725
+ override_defaults: true
726
+
727
+ rules:
728
+ - id: warn_endpoint_removal
729
+ name: Warn on Endpoint Removal
730
+ change_types: [endpoint_removed]
731
+ severity: warning
732
+ action: warn
733
+ message: "Endpoint {path} was removed. Check downstream consumers."
734
+
735
+ - id: warn_method_removal
736
+ name: Warn on Method Removal
737
+ change_types: [method_removed]
738
+ severity: warning
739
+ action: warn
740
+ message: "HTTP method removed from {path}."
741
+
742
+ - id: warn_required_param
743
+ name: Warn on Required Parameter Addition
744
+ change_types: [required_param_added]
745
+ severity: warning
746
+ action: warn
747
+ message: "New required parameter at {path}."
748
+
749
+ - id: warn_type_change
750
+ name: Warn on Type Changes
751
+ change_types: [type_changed]
752
+ severity: warning
753
+ action: warn
754
+ message: "Type changed at {path}."
755
+
756
+ - id: allow_field_removal
757
+ name: Allow Field Removal
758
+ change_types: [field_removed]
759
+ severity: info
760
+ action: allow
761
+ message: "Field removed from {path}."
762
+ `,
763
+ };
764
+
744
765
  // Init command — scaffold .delimit/ config
745
766
  program
746
767
  .command('init')
747
768
  .description('Initialize Delimit API governance in this project')
748
- .action(async () => {
769
+ .option('--preset <name>', 'Policy preset: strict, default, or relaxed', 'default')
770
+ .action(async (options) => {
749
771
  const configDir = path.join(process.cwd(), '.delimit');
750
772
  const policyFile = path.join(configDir, 'policies.yml');
751
773
 
@@ -754,34 +776,175 @@ program
754
776
  return;
755
777
  }
756
778
 
779
+ const preset = options.preset.toLowerCase();
780
+ if (!POLICY_PRESETS[preset]) {
781
+ console.log(chalk.red(`Unknown preset "${preset}". Choose: strict, default, or relaxed`));
782
+ return;
783
+ }
784
+
757
785
  fs.mkdirSync(configDir, { recursive: true });
786
+ fs.writeFileSync(policyFile, POLICY_PRESETS[preset]);
787
+ console.log(chalk.green(`\n Created .delimit/policies.yml (preset: ${preset})\n`));
758
788
 
759
- const template = `# Delimit API Governance Policy
760
- # https://github.com/delimit-ai/delimit
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)));
761
801
 
762
- # Override built-in rules (default: false)
763
- override_defaults: false
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
+ }
764
835
 
765
- rules: []
766
- # Example:
767
- # - id: protect_v1
768
- # name: Protect V1 API
769
- # change_types: [endpoint_removed, method_removed, field_removed]
770
- # severity: error
771
- # action: forbid
772
- # conditions:
773
- # path_pattern: "^/v1/.*"
774
- # message: "V1 API is frozen. Make changes in V2."
775
- `;
776
- fs.writeFileSync(policyFile, template);
777
- console.log(chalk.green('Created .delimit/policies.yml'));
778
- console.log('');
836
+ console.log(` ${chalk.bold('Presets')}: strict | default | relaxed`);
837
+ console.log(` Switch: ${chalk.bold('delimit init --preset strict')}\n`);
779
838
  console.log('Next steps:');
780
839
  console.log(` ${chalk.bold('delimit lint')} old.yaml new.yaml — check for breaking changes`);
781
840
  console.log(` ${chalk.bold('delimit diff')} old.yaml new.yaml — see all changes`);
782
841
  console.log(` ${chalk.bold('delimit explain')} old.yaml new.yaml — human-readable summary`);
783
842
  });
784
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
+
785
948
  // Lint command — diff + policy (primary command)
786
949
  // Supports zero-spec mode: `delimit lint` (no args) auto-extracts from FastAPI
787
950
  program
@@ -964,4 +1127,11 @@ program
964
1127
  }
965
1128
  });
966
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
+
967
1137
  program.parse();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "delimit-cli",
3
- "version": "2.2.0",
4
- "description": "ESLint for API contracts detect breaking changes, enforce semver, and generate migration guides for OpenAPI specs",
3
+ "version": "2.3.1",
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": {
7
7
  "delimit": "./bin/delimit-cli.js"
@@ -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
+ }