delimit-cli 2.3.2 → 2.4.0

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ ## [2.4.0] - 2026-03-15
4
+
5
+ ### Added
6
+ - 29 real CLI tests covering init, lint, diff, explain, doctor, presets, and error handling
7
+ - Auto-write GitHub Actions workflow file on `delimit init`
8
+
9
+ ### Improved
10
+ - Version now read from package.json instead of hardcoded
11
+ - Error handling across all commands
12
+
13
+ ## [2.3.2] - 2026-03-09
14
+
15
+ ### Fixed
16
+ - Clean --help output (legacy commands hidden)
17
+ - File existence checks before lint/diff operations
18
+ - --policy flag accepts preset names (strict, default, relaxed)
19
+
20
+ ## [2.3.0] - 2026-03-07
21
+
22
+ ### Added
23
+ - Policy presets: strict (all errors), default (balanced), relaxed (warnings only)
24
+ - `delimit doctor` command for environment diagnostics
25
+ - `delimit explain` command with 7 output templates
26
+
27
+ ## [2.0.0] - 2026-02-28
28
+
29
+ ### Added
30
+ - Deterministic diff engine (23 change types, 10 breaking)
31
+ - Policy enforcement with exit code 1 on violations
32
+ - Semver classification (MAJOR/MINOR/PATCH/NONE)
33
+ - Zero-Spec extraction for FastAPI, NestJS, Express
package/README.md CHANGED
@@ -1,141 +1,108 @@
1
- # delimit-cli
1
+ # delimit
2
2
 
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.
3
+ Catch breaking API changes before they ship.
6
4
 
7
5
  [![npm](https://img.shields.io/npm/v/delimit-cli)](https://www.npmjs.com/package/delimit-cli)
6
+ [![GitHub Action](https://img.shields.io/badge/Marketplace-Delimit-blue)](https://github.com/marketplace/actions/delimit-api-governance)
8
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
8
+ [![Tests](https://img.shields.io/badge/tests-299%20passing-brightgreen)](#)
9
9
 
10
- ## Install
10
+ Deterministic diff engine for OpenAPI specs. Detects breaking changes, classifies semver, enforces policy, and posts PR comments with migration guides. No API keys, no external services.
11
11
 
12
- ```bash
13
- npm install -g delimit-cli
14
- ```
12
+ ---
15
13
 
16
- ## Quick Start (Under 5 Minutes)
14
+ ## GitHub Action (recommended)
17
15
 
18
- ```bash
19
- # 1. Initialize with a policy preset
20
- delimit init --preset default
16
+ Add `.github/workflows/api-check.yml`:
21
17
 
22
- # 2. Detect breaking changes
23
- delimit lint api/openapi-old.yaml api/openapi-new.yaml
18
+ ```yaml
19
+ name: API Contract Check
20
+ on: pull_request
24
21
 
25
- # 3. Add the GitHub Action for automated PR checks
26
- # Copy .github/workflows/api-governance.yml (see CI section below)
22
+ jobs:
23
+ delimit:
24
+ runs-on: ubuntu-latest
25
+ permissions:
26
+ pull-requests: write
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ - uses: actions/checkout@v4
30
+ with:
31
+ ref: ${{ github.event.pull_request.base.sha }}
32
+ path: base
33
+ - uses: delimit-ai/delimit-action@v1
34
+ with:
35
+ old_spec: base/api/openapi.yaml
36
+ new_spec: api/openapi.yaml
27
37
  ```
28
38
 
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
-
42
- ## Commands
43
-
44
- | Command | Description |
45
- |---------|-------------|
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 |
48
- | `delimit diff <old> <new>` | Raw diff with `[BREAKING]`/`[safe]` tags |
49
- | `delimit explain <old> <new>` | Human-readable change explanation |
39
+ Runs in **advisory mode** by default -- posts a PR comment but never fails your build. Set `mode: enforce` when you are ready to block merges on breaking changes.
50
40
 
51
- ## Policy Presets
41
+ ---
52
42
 
53
- Choose a preset that fits your team:
43
+ ## CLI
54
44
 
55
45
  ```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
46
+ npx delimit-cli lint api/openapi.yaml
47
+ npx delimit-cli diff old.yaml new.yaml
48
+ npx delimit-cli explain old.yaml new.yaml --template migration
59
49
  ```
60
50
 
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:
51
+ Or install globally:
68
52
 
69
53
  ```bash
70
- delimit lint --policy strict old.yaml new.yaml
54
+ npm install -g delimit-cli
55
+ delimit init --preset default
56
+ delimit lint old.yaml new.yaml
71
57
  ```
72
58
 
73
- ## Options
59
+ ### Commands
74
60
 
75
- ```bash
76
- # Semver classification with version bump
77
- delimit lint old.yaml new.yaml --current-version 1.0.0
61
+ | Command | What it does |
62
+ |---------|-------------|
63
+ | `delimit init [--preset]` | Create `.delimit/policies.yml` |
64
+ | `delimit lint <old> <new>` | Diff + policy check. Exit 1 on violations. |
65
+ | `delimit diff <old> <new>` | Raw diff with `[BREAKING]` / `[safe]` tags |
66
+ | `delimit explain <old> <new>` | Human-readable explanation (7 templates) |
78
67
 
79
- # Explainer templates
80
- delimit explain old.yaml new.yaml -t migration
81
- delimit explain old.yaml new.yaml -t pr_comment
82
- delimit explain old.yaml new.yaml -t changelog
68
+ ---
83
69
 
84
- # JSON output for scripting
85
- delimit lint old.yaml new.yaml --json
86
- ```
70
+ ## What it catches
87
71
 
88
- ### Explainer Templates
72
+ 10 breaking change types, detected deterministically:
89
73
 
90
- | Template | Audience |
91
- |----------|----------|
92
- | `developer` | Technical details for engineers |
93
- | `team_lead` | Summary for engineering managers |
94
- | `product` | Non-technical overview for PMs |
95
- | `migration` | Step-by-step migration guide |
96
- | `changelog` | Ready-to-paste changelog entry |
97
- | `pr_comment` | GitHub PR comment format |
98
- | `slack` | Slack message format |
74
+ | Breaking change | Example |
75
+ |----------------|---------|
76
+ | Endpoint removed | `DELETE /users/{id}` path deleted |
77
+ | Method removed | `PATCH` dropped from `/orders` |
78
+ | Required parameter added | New required query param on existing endpoint |
79
+ | Parameter removed | `?filter` param deleted |
80
+ | Response removed | `200` response code dropped |
81
+ | Required field added | New required field in request body |
82
+ | Response field removed | `email` field removed from response |
83
+ | Type changed | `age` changed from `string` to `integer` |
84
+ | Format changed | `date` changed to `date-time` |
85
+ | Enum value removed | `status: "pending"` no longer allowed |
99
86
 
100
- ## CI/CD Integration
87
+ Plus 7 non-breaking types (endpoint added, optional field added, etc.) for full change visibility. Every change is classified as `MAJOR`, `MINOR`, `PATCH`, or `NONE`.
101
88
 
102
- Add this workflow to `.github/workflows/api-governance.yml`:
89
+ ---
103
90
 
104
- ```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
91
+ ## Policy presets
92
+
93
+ ```bash
94
+ delimit init --preset strict # All breaking changes are errors. For public/payment APIs.
95
+ delimit init --preset default # Breaking changes error, type changes warn. For most teams.
96
+ delimit init --preset relaxed # Everything is a warning. For internal APIs and prototyping.
127
97
  ```
128
98
 
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
99
+ Or pass inline: `delimit lint --policy strict old.yaml new.yaml`
133
100
 
134
- See [Delimit API Governance](https://github.com/marketplace/actions/delimit-api-governance) on the GitHub Marketplace.
101
+ ---
135
102
 
136
- ## Custom Policies
103
+ ## Custom policies
137
104
 
138
- Create `.delimit/policies.yml` or start from a preset:
105
+ Create `.delimit/policies.yml`:
139
106
 
140
107
  ```yaml
141
108
  override_defaults: false
@@ -151,17 +118,22 @@ rules:
151
118
  message: "V1 API is frozen. Make changes in V2."
152
119
  ```
153
120
 
154
- ## Supported Specs
121
+ ---
155
122
 
156
- - OpenAPI 3.0.x and 3.1.x
123
+ ## Supported formats
124
+
125
+ - OpenAPI 3.0 and 3.1
157
126
  - Swagger 2.0
158
- - YAML and JSON formats
127
+ - YAML and JSON
128
+
129
+ ---
159
130
 
160
131
  ## Links
161
132
 
162
- - [GitHub Action](https://github.com/marketplace/actions/delimit-api-governance) Automated PR checks
163
- - [GitHub](https://github.com/delimit-ai/delimit) Source code
164
- - [Issues](https://github.com/delimit-ai/delimit/issues) Bug reports and feature requests
133
+ - [delimit.ai](https://delimit.ai) -- Project home
134
+ - [GitHub Action on Marketplace](https://github.com/marketplace/actions/delimit-api-governance) -- Install in one click
135
+ - [delimit-cli on npm](https://www.npmjs.com/package/delimit-cli) -- CLI package
136
+ - [Quickstart repo](https://github.com/delimit-ai/delimit-quickstart) -- Try it in 2 minutes
165
137
 
166
138
  ## License
167
139
 
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Delimit™ Codex Forge Adapter
4
+ * Layer: Forge (execution governance)
5
+ * Surfaces test failures, deploy state, and release gates before accepting code.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const axios = require('axios');
11
+ const AGENT_URL = `http://127.0.0.1:${process.env.DELIMIT_AGENT_PORT || 7823}`;
12
+
13
+ class DelimitCodexForge {
14
+ constructor() {
15
+ this.name = 'delimit-forge';
16
+ this.version = '1.0.0';
17
+ }
18
+
19
+ /**
20
+ * Before accepting code: check test gate and deploy state.
21
+ */
22
+ async onBeforeSuggestion(context) {
23
+ const [testState, deployState] = await Promise.allSettled([
24
+ this._getTestGate(),
25
+ this._getDeployState(),
26
+ ]);
27
+
28
+ const warnings = [];
29
+
30
+ if (testState.status === 'fulfilled' && testState.value.failing > 0) {
31
+ warnings.push(`[FORGE] ${testState.value.failing} test(s) failing — fix before accepting`);
32
+ }
33
+
34
+ if (deployState.status === 'fulfilled' && deployState.value.locked) {
35
+ warnings.push(`[FORGE] Deploy locked: ${deployState.value.reason || 'release in progress'}`);
36
+ }
37
+
38
+ if (warnings.length > 0) {
39
+ return { allow: true, warning: warnings.join(' | ') };
40
+ }
41
+
42
+ return { allow: true };
43
+ }
44
+
45
+ async onAfterAccept(context) {
46
+ try {
47
+ await axios.post(`${AGENT_URL}/audit`, {
48
+ action: 'forge_accept',
49
+ context,
50
+ timestamp: new Date().toISOString(),
51
+ }, { timeout: 2000 });
52
+ } catch (_) { /* silent */ }
53
+ }
54
+
55
+ async _getTestGate() {
56
+ const r = await axios.get(`${AGENT_URL}/test/status`, { timeout: 3000 });
57
+ return r.data;
58
+ }
59
+
60
+ async _getDeployState() {
61
+ const r = await axios.get(`${AGENT_URL}/deploy/status`, { timeout: 3000 });
62
+ return r.data;
63
+ }
64
+
65
+ async handleCommand(command, _args) {
66
+ const { execSync } = require('child_process');
67
+ const cmds = {
68
+ 'forge': 'delimit status --layer=forge',
69
+ 'tests': 'delimit test --summary',
70
+ 'deploy': 'delimit deploy --status',
71
+ 'release': 'delimit release --status',
72
+ };
73
+ if (cmds[command]) {
74
+ try {
75
+ return execSync(cmds[command], { timeout: 10000 }).toString();
76
+ } catch (e) {
77
+ return `[FORGE] Command failed: ${e.message}`;
78
+ }
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Returns structured Forge context for consensus use.
84
+ */
85
+ async getContext() {
86
+ const [tests, deploy, release] = await Promise.allSettled([
87
+ axios.get(`${AGENT_URL}/test/status`, { timeout: 3000 }),
88
+ axios.get(`${AGENT_URL}/deploy/status`, { timeout: 3000 }),
89
+ axios.get(`${AGENT_URL}/release/status`, { timeout: 3000 }),
90
+ ]);
91
+
92
+ return {
93
+ layer: 'forge',
94
+ tests: tests.status === 'fulfilled' ? tests.value.data : null,
95
+ deploy: deploy.status === 'fulfilled' ? deploy.value.data : null,
96
+ release: release.status === 'fulfilled' ? release.value.data : null,
97
+ };
98
+ }
99
+ }
100
+
101
+ if (typeof module !== 'undefined' && module.exports) {
102
+ module.exports = new DelimitCodexForge();
103
+ }
104
+
105
+ if (typeof registerSkill === 'function') {
106
+ registerSkill(new DelimitCodexForge());
107
+ }
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Delimit™ Codex Jamsons Adapter
4
+ * Layer: Jamsons OS (strategy + operational governance)
5
+ * Injects portfolio context, logs decisions, surfaces venture/priority state.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const axios = require('axios');
11
+ const AGENT_URL = `http://127.0.0.1:${process.env.DELIMIT_AGENT_PORT || 7823}`;
12
+ const JAMSONS_URL = `http://localhost:${process.env.JAMSONS_PORT || 8091}`;
13
+
14
+ const VENTURES = {
15
+ delimit: { priority: 'P0', status: 'active' },
16
+ domainvested: { priority: 'P2', status: 'maintenance' },
17
+ 'wire.report': { priority: 'held', status: 'held' },
18
+ 'livetube.ai': { priority: 'held', status: 'held' },
19
+ };
20
+
21
+ class DelimitCodexJamsons {
22
+ constructor() {
23
+ this.name = 'delimit-jamsons';
24
+ this.version = '1.0.0';
25
+ }
26
+
27
+ /**
28
+ * Before suggestion: inject portfolio context so the model has strategic awareness.
29
+ */
30
+ async onBeforeSuggestion(context) {
31
+ try {
32
+ const portfolioCtx = await this._getPortfolioContext(context);
33
+ // Attach context metadata — Codex passes this through to the model
34
+ context._jamsons = portfolioCtx;
35
+ } catch (_) { /* fail open */ }
36
+ return { allow: true };
37
+ }
38
+
39
+ async onAfterAccept(context) {
40
+ // Log accepted suggestion to Jamsons decision ledger
41
+ try {
42
+ await axios.post(`${JAMSONS_URL}/api/chatops/decisions`, {
43
+ type: 'task',
44
+ title: `Codex: accepted suggestion in ${context.file || 'unknown file'}`,
45
+ detail: `Language: ${context.language || 'unknown'} | Tool: codex`,
46
+ user_id: 'codex-adapter',
47
+ tags: ['delimit', 'chatops'],
48
+ }, {
49
+ headers: { Authorization: `Bearer ${process.env.CHATOPS_AUTH_TOKEN}` },
50
+ timeout: 2000,
51
+ });
52
+ } catch (_) { /* silent */ }
53
+ }
54
+
55
+ async _getPortfolioContext(context) {
56
+ const [memory, decisions] = await Promise.allSettled([
57
+ this._searchMemory(context.file || context.language || 'delimit'),
58
+ this._getPendingDecisions(),
59
+ ]);
60
+
61
+ return {
62
+ ventures: VENTURES,
63
+ activeVenture: this._detectVenture(context),
64
+ memory: memory.status === 'fulfilled' ? memory.value : [],
65
+ pendingDecisions: decisions.status === 'fulfilled' ? decisions.value : [],
66
+ timestamp: new Date().toISOString(),
67
+ };
68
+ }
69
+
70
+ _detectVenture(context) {
71
+ const path = (context.file || '').toLowerCase();
72
+ if (path.includes('delimit')) return 'delimit';
73
+ if (path.includes('domainvested')) return 'domainvested';
74
+ return 'delimit'; // default active venture
75
+ }
76
+
77
+ async _searchMemory(query) {
78
+ const r = await axios.get(`${JAMSONS_URL}/api/memory/search`, {
79
+ params: { q: query, limit: 5 },
80
+ timeout: 2000,
81
+ });
82
+ return r.data?.results || [];
83
+ }
84
+
85
+ async _getPendingDecisions() {
86
+ const r = await axios.get(`${JAMSONS_URL}/api/chatops/decisions`, {
87
+ params: { status: 'pending', limit: 5 },
88
+ headers: { Authorization: `Bearer ${process.env.CHATOPS_AUTH_TOKEN}` },
89
+ timeout: 2000,
90
+ });
91
+ return r.data?.decisions || r.data || [];
92
+ }
93
+
94
+ async handleCommand(command, _args) {
95
+ const { execSync } = require('child_process');
96
+ const cmds = {
97
+ 'jamsons': 'delimit status --layer=jamsons',
98
+ 'portfolio': 'delimit portfolio --summary',
99
+ 'decisions': 'delimit decisions --pending',
100
+ 'memory': 'delimit memory --recent',
101
+ };
102
+ if (cmds[command]) {
103
+ try {
104
+ return execSync(cmds[command], { timeout: 10000 }).toString();
105
+ } catch (e) {
106
+ return `[JAMSONS] Command failed: ${e.message}`;
107
+ }
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Returns structured Jamsons context for consensus use.
113
+ */
114
+ async getContext() {
115
+ const [memory, decisions] = await Promise.allSettled([
116
+ axios.get(`${JAMSONS_URL}/api/memory/search`, {
117
+ params: { q: 'delimit strategy', limit: 5 },
118
+ timeout: 3000,
119
+ }),
120
+ axios.get(`${JAMSONS_URL}/api/chatops/decisions`, {
121
+ params: { status: 'pending' },
122
+ headers: { Authorization: `Bearer ${process.env.CHATOPS_AUTH_TOKEN}` },
123
+ timeout: 3000,
124
+ }),
125
+ ]);
126
+
127
+ return {
128
+ layer: 'jamsons',
129
+ ventures: VENTURES,
130
+ memory: memory.status === 'fulfilled' ? (memory.value.data?.results || []) : null,
131
+ pendingDecisions: decisions.status === 'fulfilled' ? (decisions.value.data || []) : null,
132
+ };
133
+ }
134
+ }
135
+
136
+ if (typeof module !== 'undefined' && module.exports) {
137
+ module.exports = new DelimitCodexJamsons();
138
+ }
139
+
140
+ if (typeof registerSkill === 'function') {
141
+ registerSkill(new DelimitCodexJamsons());
142
+ }
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Delimit™ Codex Security Skill Adapter
4
+ * Layer: Delimit (code governance) — Security surface
5
+ * Triggered on pre-code-generation and pre-suggestion events
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const axios = require('axios');
11
+ const AGENT_URL = `http://127.0.0.1:${process.env.DELIMIT_AGENT_PORT || 7823}`;
12
+
13
+ class DelimitCodexSecurity {
14
+ constructor() {
15
+ this.name = 'delimit-security';
16
+ this.version = '1.0.0';
17
+ }
18
+
19
+ async onBeforeSuggestion(context) {
20
+ try {
21
+ const { code, language, file } = context;
22
+ const response = await axios.post(`${AGENT_URL}/security`, {
23
+ action: 'codex_suggestion',
24
+ code,
25
+ language,
26
+ file,
27
+ tool: 'codex',
28
+ }, { timeout: 3000 });
29
+
30
+ const { severity, findings } = response.data;
31
+
32
+ if (severity === 'critical') {
33
+ return {
34
+ allow: false,
35
+ message: `[DELIMIT SECURITY] Blocked — critical finding: ${findings?.[0]?.message || 'see audit log'}`,
36
+ };
37
+ }
38
+
39
+ if (severity === 'high') {
40
+ return {
41
+ allow: true,
42
+ warning: `[DELIMIT SECURITY] High-severity finding: ${findings?.[0]?.message || 'review before accepting'}`,
43
+ };
44
+ }
45
+
46
+ return { allow: true };
47
+ } catch (err) {
48
+ if (err.response?.data?.action === 'block') {
49
+ return { allow: false, message: `[DELIMIT SECURITY] ${err.response.data.reason}` };
50
+ }
51
+ // Fail open — security service unavailable
52
+ console.warn('[DELIMIT SECURITY] Scan unavailable:', err.message);
53
+ return { allow: true };
54
+ }
55
+ }
56
+
57
+ async onAfterAccept(context) {
58
+ try {
59
+ await axios.post(`${AGENT_URL}/audit`, {
60
+ action: 'codex_security_accept',
61
+ context,
62
+ timestamp: new Date().toISOString(),
63
+ }, { timeout: 2000 });
64
+ } catch (_) { /* silent */ }
65
+ }
66
+
67
+ async handleCommand(command, _args) {
68
+ if (command === 'security') {
69
+ const { execSync } = require('child_process');
70
+ try {
71
+ return execSync('delimit security --verbose', { timeout: 10000 }).toString();
72
+ } catch (e) {
73
+ return `[DELIMIT SECURITY] Command failed: ${e.message}`;
74
+ }
75
+ }
76
+ }
77
+
78
+ async getContext() {
79
+ try {
80
+ const r = await axios.get(`${AGENT_URL}/security/status`, { timeout: 3000 });
81
+ return { layer: 'delimit-security', status: 'ok', data: r.data };
82
+ } catch (_) {
83
+ return { layer: 'delimit-security', status: 'unavailable' };
84
+ }
85
+ }
86
+ }
87
+
88
+ if (typeof module !== 'undefined' && module.exports) {
89
+ module.exports = new DelimitCodexSecurity();
90
+ }
91
+
92
+ if (typeof registerSkill === 'function') {
93
+ registerSkill(new DelimitCodexSecurity());
94
+ }
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Delimit™ Gemini CLI Forge Adapter
4
+ * Layer: Forge (execution governance)
5
+ * Used as: command handler called from Gemini CLI hooks and @commands
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const axios = require('axios');
11
+ const AGENT_URL = `http://127.0.0.1:${process.env.DELIMIT_AGENT_PORT || 7823}`;
12
+
13
+ class DelimitGeminiForge {
14
+ constructor() {
15
+ this.id = 'delimit-forge';
16
+ this.version = '1.0.0';
17
+ }
18
+
19
+ /**
20
+ * Pre-generation: surface test failures and deploy locks to Gemini before it generates code.
21
+ */
22
+ async beforeCodeGeneration(request) {
23
+ const [testState, deployState, releaseState] = await Promise.allSettled([
24
+ axios.get(`${AGENT_URL}/test/status`, { timeout: 3000 }),
25
+ axios.get(`${AGENT_URL}/deploy/status`, { timeout: 3000 }),
26
+ axios.get(`${AGENT_URL}/release/status`, { timeout: 3000 }),
27
+ ]);
28
+
29
+ const warnings = [];
30
+
31
+ if (testState.status === 'fulfilled') {
32
+ const t = testState.value.data;
33
+ if (t?.failing > 0) warnings.push(`[FORGE] ${t.failing} test(s) failing`);
34
+ }
35
+
36
+ if (deployState.status === 'fulfilled') {
37
+ const d = deployState.value.data;
38
+ if (d?.locked) warnings.push(`[FORGE] Deploy locked: ${d.reason || 'release in progress'}`);
39
+ }
40
+
41
+ if (releaseState.status === 'fulfilled') {
42
+ const rel = releaseState.value.data;
43
+ if (rel?.in_progress) warnings.push(`[FORGE] Release in progress: ${rel.version || 'unknown'}`);
44
+ }
45
+
46
+ if (warnings.length > 0) {
47
+ console.warn(warnings.join('\n'));
48
+ // Inject warnings into system prompt context
49
+ request._forgeWarnings = warnings;
50
+ }
51
+
52
+ return request;
53
+ }
54
+
55
+ async afterResponse(response) {
56
+ try {
57
+ await axios.post(`${AGENT_URL}/audit`, {
58
+ action: 'gemini_forge_response',
59
+ response: {
60
+ model: response.model,
61
+ tokens: response.usage,
62
+ timestamp: new Date().toISOString(),
63
+ },
64
+ }, { timeout: 2000 });
65
+ } catch (_) { /* silent */ }
66
+ return response;
67
+ }
68
+
69
+ async handleCommand(command, _args) {
70
+ const { execSync } = require('child_process');
71
+ const commands = {
72
+ '@forge': 'delimit status --layer=forge',
73
+ '@tests': 'delimit test --summary',
74
+ '@deploy': 'delimit deploy --status',
75
+ '@release': 'delimit release --status',
76
+ };
77
+ if (commands[command]) {
78
+ try {
79
+ return execSync(commands[command], { timeout: 10000 }).toString();
80
+ } catch (e) {
81
+ return `[FORGE] Command failed: ${e.message}`;
82
+ }
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Structured context for consensus — call this from hooks or consensus runners.
88
+ */
89
+ async getContext() {
90
+ const [tests, deploy, release] = await Promise.allSettled([
91
+ axios.get(`${AGENT_URL}/test/status`, { timeout: 3000 }),
92
+ axios.get(`${AGENT_URL}/deploy/status`, { timeout: 3000 }),
93
+ axios.get(`${AGENT_URL}/release/status`, { timeout: 3000 }),
94
+ ]);
95
+ return {
96
+ layer: 'forge',
97
+ tool: 'gemini',
98
+ tests: tests.status === 'fulfilled' ? tests.value.data : null,
99
+ deploy: deploy.status === 'fulfilled' ? deploy.value.data : null,
100
+ release: release.status === 'fulfilled' ? release.value.data : null,
101
+ };
102
+ }
103
+ }
104
+
105
+ module.exports = new DelimitGeminiForge();
106
+
107
+ if (typeof registerExtension === 'function') {
108
+ registerExtension(new DelimitGeminiForge());
109
+ }
@@ -50,7 +50,7 @@ async function ensureAgent() {
50
50
  program
51
51
  .name('delimit')
52
52
  .description('Prevent breaking API changes before they reach production')
53
- .version('2.3.0');
53
+ .version(require('../package.json').version);
54
54
 
55
55
  // Install command with modes
56
56
  program
@@ -803,7 +803,7 @@ program
803
803
  const specPath = foundSpecs[0];
804
804
  console.log(` Detected spec: ${chalk.bold(specPath)}`);
805
805
  console.log('');
806
- console.log(chalk.bold(' Add this to .github/workflows/api-governance.yml:\n'));
806
+ console.log(chalk.bold(' Workflow template:\n'));
807
807
  console.log(chalk.gray(` name: API Governance
808
808
  on:
809
809
  pull_request:
@@ -827,6 +827,48 @@ program
827
827
  new_spec: ${specPath}
828
828
  mode: advisory`));
829
829
  console.log('');
830
+
831
+ // Auto-write the workflow file
832
+ const workflowDir = path.join(process.cwd(), '.github', 'workflows');
833
+ const workflowFile = path.join(workflowDir, 'api-governance.yml');
834
+
835
+ if (!fs.existsSync(workflowFile)) {
836
+ try {
837
+ fs.mkdirSync(workflowDir, { recursive: true });
838
+ const workflowContent = `name: API Governance
839
+ on:
840
+ pull_request:
841
+ paths:
842
+ - '${specPath}'
843
+
844
+ permissions:
845
+ contents: read
846
+ pull-requests: write
847
+
848
+ jobs:
849
+ api-governance:
850
+ runs-on: ubuntu-latest
851
+ steps:
852
+ - uses: actions/checkout@v4
853
+ - uses: actions/checkout@v4
854
+ with:
855
+ ref: \${{ github.event.pull_request.base.sha }}
856
+ path: _base
857
+ - uses: delimit-ai/delimit@v1
858
+ with:
859
+ old_spec: _base/${specPath}
860
+ new_spec: ${specPath}
861
+ mode: advisory
862
+ `;
863
+ fs.writeFileSync(workflowFile, workflowContent);
864
+ console.log(chalk.green(` Created .github/workflows/api-governance.yml\n`));
865
+ } catch (err) {
866
+ console.log(chalk.yellow(` Could not write workflow file: ${err.message}`));
867
+ console.log(chalk.bold(' Add this to .github/workflows/api-governance.yml manually (shown above)\n'));
868
+ }
869
+ } else {
870
+ console.log(chalk.yellow(' .github/workflows/api-governance.yml already exists — skipped\n'));
871
+ }
830
872
  } else {
831
873
  console.log(' No OpenAPI spec file detected.');
832
874
  console.log(` Delimit also supports ${chalk.bold('Zero-Spec Mode')} — run ${chalk.bold('delimit lint')} in a FastAPI/NestJS/Express project.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "delimit-cli",
3
- "version": "2.3.2",
3
+ "version": "2.4.0",
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": {
@@ -12,7 +12,7 @@
12
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
- "test": "echo 'Governance is context-aware' && exit 0"
15
+ "test": "node --test tests/cli.test.js"
16
16
  },
17
17
  "keywords": [
18
18
  "openapi",
@@ -0,0 +1,359 @@
1
+ /**
2
+ * Delimit CLI Tests
3
+ *
4
+ * Uses Node.js built-in test runner (node:test).
5
+ * Run with: node --test tests/cli.test.js
6
+ */
7
+
8
+ const { describe, it, before, after } = require('node:test');
9
+ const assert = require('node:assert/strict');
10
+ const { execSync } = require('node:child_process');
11
+ const path = require('node:path');
12
+ const fs = require('node:fs');
13
+ const os = require('node:os');
14
+
15
+ const CLI = path.join(__dirname, '..', 'bin', 'delimit-cli.js');
16
+ const FIXTURES = path.join(__dirname, 'fixtures');
17
+ const SPEC_CLEAN = path.join(FIXTURES, 'openapi.yaml');
18
+ const SPEC_BREAKING = path.join(FIXTURES, 'openapi-changed.yaml');
19
+
20
+ /**
21
+ * Run the CLI and return { stdout, stderr, exitCode }.
22
+ * Does not throw on non-zero exit.
23
+ */
24
+ function run(args, opts = {}) {
25
+ const cwd = opts.cwd || os.tmpdir();
26
+ try {
27
+ const stdout = execSync(`node "${CLI}" ${args}`, {
28
+ cwd,
29
+ encoding: 'utf-8',
30
+ timeout: 30000,
31
+ env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' },
32
+ });
33
+ return { stdout, stderr: '', exitCode: 0 };
34
+ } catch (err) {
35
+ return {
36
+ stdout: err.stdout || '',
37
+ stderr: err.stderr || '',
38
+ exitCode: err.status ?? 1,
39
+ };
40
+ }
41
+ }
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // 1. CLI entry point loads without error
45
+ // ---------------------------------------------------------------------------
46
+ describe('CLI entry point', () => {
47
+ it('loads the module without throwing', () => {
48
+ assert.doesNotThrow(() => {
49
+ execSync(`node --check "${CLI}"`, { encoding: 'utf-8' });
50
+ });
51
+ });
52
+ });
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // 2. --version returns the version from package.json
56
+ // ---------------------------------------------------------------------------
57
+ describe('--version', () => {
58
+ it('prints the version from package.json', () => {
59
+ const pkg = require(path.join(__dirname, '..', 'package.json'));
60
+ const { stdout } = run('--version');
61
+ assert.equal(stdout.trim(), pkg.version);
62
+ });
63
+ });
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // 3. --help works at top level
67
+ // ---------------------------------------------------------------------------
68
+ describe('--help (top level)', () => {
69
+ it('shows usage and lists commands', () => {
70
+ const { stdout } = run('--help');
71
+ assert.match(stdout, /Usage:/);
72
+ assert.match(stdout, /init/);
73
+ assert.match(stdout, /lint/);
74
+ assert.match(stdout, /diff/);
75
+ assert.match(stdout, /explain/);
76
+ assert.match(stdout, /doctor/);
77
+ });
78
+ });
79
+
80
+ // ---------------------------------------------------------------------------
81
+ // 4. Each command has help text
82
+ // ---------------------------------------------------------------------------
83
+ describe('subcommand --help', () => {
84
+ for (const cmd of ['init', 'lint', 'diff', 'explain', 'doctor']) {
85
+ it(`"help ${cmd}" shows description`, () => {
86
+ const { stdout } = run(`help ${cmd}`);
87
+ assert.match(stdout, /Usage:/);
88
+ assert.match(stdout, new RegExp(cmd));
89
+ });
90
+ }
91
+ });
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // 5. init creates .delimit/policies.yml with default preset
95
+ // ---------------------------------------------------------------------------
96
+ describe('init command', () => {
97
+ let tmpDir;
98
+
99
+ before(() => {
100
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'delimit-test-init-'));
101
+ });
102
+
103
+ after(() => {
104
+ fs.rmSync(tmpDir, { recursive: true, force: true });
105
+ });
106
+
107
+ it('creates .delimit/policies.yml with default preset', () => {
108
+ const { stdout, exitCode } = run('init', { cwd: tmpDir });
109
+ assert.equal(exitCode, 0);
110
+ const policyPath = path.join(tmpDir, '.delimit', 'policies.yml');
111
+ assert.ok(fs.existsSync(policyPath), 'policies.yml should exist');
112
+ const content = fs.readFileSync(policyPath, 'utf-8');
113
+ assert.match(content, /Delimit Policy Preset: default/);
114
+ assert.match(content, /override_defaults: false/);
115
+ });
116
+
117
+ it('reports already initialized on second run', () => {
118
+ const { stdout, exitCode } = run('init', { cwd: tmpDir });
119
+ assert.equal(exitCode, 0);
120
+ assert.match(stdout, /Already initialized/);
121
+ });
122
+ });
123
+
124
+ describe('init --preset strict', () => {
125
+ let tmpDir;
126
+
127
+ before(() => {
128
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'delimit-test-init-strict-'));
129
+ });
130
+
131
+ after(() => {
132
+ fs.rmSync(tmpDir, { recursive: true, force: true });
133
+ });
134
+
135
+ it('creates policies.yml with strict preset', () => {
136
+ const { stdout, exitCode } = run('init --preset strict', { cwd: tmpDir });
137
+ assert.equal(exitCode, 0);
138
+ const content = fs.readFileSync(
139
+ path.join(tmpDir, '.delimit', 'policies.yml'),
140
+ 'utf-8'
141
+ );
142
+ assert.match(content, /Delimit Policy Preset: strict/);
143
+ assert.match(content, /no_endpoint_removal/);
144
+ });
145
+ });
146
+
147
+ describe('init --preset relaxed', () => {
148
+ let tmpDir;
149
+
150
+ before(() => {
151
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'delimit-test-init-relaxed-'));
152
+ });
153
+
154
+ after(() => {
155
+ fs.rmSync(tmpDir, { recursive: true, force: true });
156
+ });
157
+
158
+ it('creates policies.yml with relaxed preset', () => {
159
+ const { stdout, exitCode } = run('init --preset relaxed', { cwd: tmpDir });
160
+ assert.equal(exitCode, 0);
161
+ const content = fs.readFileSync(
162
+ path.join(tmpDir, '.delimit', 'policies.yml'),
163
+ 'utf-8'
164
+ );
165
+ assert.match(content, /Delimit Policy Preset: relaxed/);
166
+ assert.match(content, /warn_endpoint_removal/);
167
+ });
168
+ });
169
+
170
+ // ---------------------------------------------------------------------------
171
+ // 6. lint with identical specs returns clean (exit 0)
172
+ // ---------------------------------------------------------------------------
173
+ describe('lint (clean)', () => {
174
+ it('returns exit 0 with no violations for identical specs', () => {
175
+ const { stdout, exitCode } = run(
176
+ `lint "${SPEC_CLEAN}" "${SPEC_CLEAN}" --json`
177
+ );
178
+ assert.equal(exitCode, 0);
179
+ const result = JSON.parse(stdout);
180
+ assert.equal(result.decision, 'pass');
181
+ assert.equal(result.exit_code, 0);
182
+ assert.equal(result.summary.breaking_changes, 0);
183
+ assert.equal(result.violations.length, 0);
184
+ });
185
+ });
186
+
187
+ // ---------------------------------------------------------------------------
188
+ // 7. lint with breaking change returns exit 1
189
+ // ---------------------------------------------------------------------------
190
+ describe('lint (breaking)', () => {
191
+ it('returns exit 1 when breaking changes are detected', () => {
192
+ const { stdout, exitCode } = run(
193
+ `lint "${SPEC_CLEAN}" "${SPEC_BREAKING}" --json`
194
+ );
195
+ assert.equal(exitCode, 1);
196
+ const result = JSON.parse(stdout);
197
+ assert.equal(result.decision, 'fail');
198
+ assert.equal(result.exit_code, 1);
199
+ assert.ok(result.summary.breaking_changes > 0, 'should have breaking changes');
200
+ assert.ok(result.violations.length > 0, 'should have violations');
201
+ });
202
+
203
+ it('includes endpoint_removed in changes', () => {
204
+ const { stdout } = run(
205
+ `lint "${SPEC_CLEAN}" "${SPEC_BREAKING}" --json`
206
+ );
207
+ const result = JSON.parse(stdout);
208
+ const types = result.all_changes.map(c => c.type);
209
+ assert.ok(types.includes('endpoint_removed'), 'should detect endpoint removal');
210
+ });
211
+
212
+ it('includes semver bump classification', () => {
213
+ const { stdout } = run(
214
+ `lint "${SPEC_CLEAN}" "${SPEC_BREAKING}" --json`
215
+ );
216
+ const result = JSON.parse(stdout);
217
+ assert.ok(result.semver, 'should have semver field');
218
+ assert.equal(result.semver.bump, 'major');
219
+ });
220
+ });
221
+
222
+ // ---------------------------------------------------------------------------
223
+ // 8. diff outputs change types
224
+ // ---------------------------------------------------------------------------
225
+ describe('diff command', () => {
226
+ it('outputs changes between two specs', () => {
227
+ const { stdout, exitCode } = run(
228
+ `diff "${SPEC_CLEAN}" "${SPEC_BREAKING}" --json`
229
+ );
230
+ assert.equal(exitCode, 0);
231
+ const result = JSON.parse(stdout);
232
+ assert.ok(result.total_changes > 0, 'should have changes');
233
+ assert.ok(result.breaking_changes > 0, 'should have breaking changes');
234
+ assert.ok(Array.isArray(result.changes), 'changes should be an array');
235
+ });
236
+
237
+ it('reports change types correctly', () => {
238
+ const { stdout } = run(
239
+ `diff "${SPEC_CLEAN}" "${SPEC_BREAKING}" --json`
240
+ );
241
+ const result = JSON.parse(stdout);
242
+ const types = result.changes.map(c => c.type);
243
+ assert.ok(types.includes('endpoint_removed'));
244
+ assert.ok(types.includes('type_changed'));
245
+ assert.ok(types.includes('enum_value_removed'));
246
+ });
247
+
248
+ it('marks breaking changes with is_breaking flag', () => {
249
+ const { stdout } = run(
250
+ `diff "${SPEC_CLEAN}" "${SPEC_BREAKING}" --json`
251
+ );
252
+ const result = JSON.parse(stdout);
253
+ const breakingChanges = result.changes.filter(c => c.is_breaking);
254
+ assert.equal(breakingChanges.length, result.breaking_changes);
255
+ });
256
+
257
+ it('returns no changes for identical specs', () => {
258
+ const { stdout, exitCode } = run(
259
+ `diff "${SPEC_CLEAN}" "${SPEC_CLEAN}" --json`
260
+ );
261
+ assert.equal(exitCode, 0);
262
+ const result = JSON.parse(stdout);
263
+ assert.equal(result.total_changes, 0);
264
+ assert.equal(result.breaking_changes, 0);
265
+ });
266
+ });
267
+
268
+ // ---------------------------------------------------------------------------
269
+ // 9. explain command
270
+ // ---------------------------------------------------------------------------
271
+ describe('explain command', () => {
272
+ it('generates human-readable explanation', () => {
273
+ const { stdout, exitCode } = run(
274
+ `explain "${SPEC_CLEAN}" "${SPEC_BREAKING}" --json`
275
+ );
276
+ assert.equal(exitCode, 0);
277
+ const result = JSON.parse(stdout);
278
+ assert.ok(result.output, 'should have output text');
279
+ assert.ok(result.output.length > 0, 'output should not be empty');
280
+ assert.ok(result.template, 'should report template used');
281
+ });
282
+
283
+ it('supports --template flag', () => {
284
+ const { stdout, exitCode } = run(
285
+ `explain "${SPEC_CLEAN}" "${SPEC_BREAKING}" --template migration --json`
286
+ );
287
+ assert.equal(exitCode, 0);
288
+ const result = JSON.parse(stdout);
289
+ assert.equal(result.template, 'migration');
290
+ });
291
+ });
292
+
293
+ // ---------------------------------------------------------------------------
294
+ // 10. lint with --policy preset
295
+ // ---------------------------------------------------------------------------
296
+ describe('lint --policy', () => {
297
+ it('accepts relaxed preset and does not fail on breaking changes', () => {
298
+ // relaxed preset uses action:warn, so decision is "warn" not "fail"
299
+ const { stdout, exitCode } = run(
300
+ `lint "${SPEC_CLEAN}" "${SPEC_BREAKING}" --policy relaxed --json`
301
+ );
302
+ assert.equal(exitCode, 0, 'relaxed preset should exit 0');
303
+ const result = JSON.parse(stdout);
304
+ assert.notEqual(result.decision, 'fail', 'relaxed should not produce fail decision');
305
+ });
306
+
307
+ it('accepts strict preset and fails on breaking changes', () => {
308
+ const { stdout, exitCode } = run(
309
+ `lint "${SPEC_CLEAN}" "${SPEC_BREAKING}" --policy strict --json`
310
+ );
311
+ assert.equal(exitCode, 1);
312
+ const result = JSON.parse(stdout);
313
+ assert.equal(result.decision, 'fail');
314
+ assert.ok(result.violations.length > 0);
315
+ });
316
+ });
317
+
318
+ // ---------------------------------------------------------------------------
319
+ // 11. Error handling -- missing files
320
+ // ---------------------------------------------------------------------------
321
+ describe('error handling', () => {
322
+ it('lint with nonexistent spec file reports error', () => {
323
+ const { exitCode } = run('lint /nonexistent/old.yaml /nonexistent/new.yaml --json');
324
+ assert.notEqual(exitCode, 0);
325
+ });
326
+
327
+ it('diff with nonexistent spec file reports error', () => {
328
+ const { exitCode } = run('diff /nonexistent/old.yaml /nonexistent/new.yaml --json');
329
+ assert.notEqual(exitCode, 0);
330
+ });
331
+ });
332
+
333
+ // ---------------------------------------------------------------------------
334
+ // 12. api-engine module exports
335
+ // ---------------------------------------------------------------------------
336
+ describe('api-engine module', () => {
337
+ it('exports lint, diff, explain, semver, zeroSpec functions', () => {
338
+ const engine = require(path.join(__dirname, '..', 'lib', 'api-engine.js'));
339
+ assert.equal(typeof engine.lint, 'function');
340
+ assert.equal(typeof engine.diff, 'function');
341
+ assert.equal(typeof engine.explain, 'function');
342
+ assert.equal(typeof engine.semver, 'function');
343
+ assert.equal(typeof engine.zeroSpec, 'function');
344
+ });
345
+
346
+ it('lint returns parsed JSON with decision field', () => {
347
+ const engine = require(path.join(__dirname, '..', 'lib', 'api-engine.js'));
348
+ const result = engine.lint(SPEC_CLEAN, SPEC_CLEAN);
349
+ assert.ok(result.decision, 'should have decision field');
350
+ assert.equal(result.decision, 'pass');
351
+ });
352
+
353
+ it('diff returns parsed JSON with changes array', () => {
354
+ const engine = require(path.join(__dirname, '..', 'lib', 'api-engine.js'));
355
+ const result = engine.diff(SPEC_CLEAN, SPEC_BREAKING);
356
+ assert.ok(Array.isArray(result.changes), 'should have changes array');
357
+ assert.ok(result.total_changes > 0);
358
+ });
359
+ });
@@ -0,0 +1,56 @@
1
+ openapi: "3.0.3"
2
+ info:
3
+ title: Pet Store API
4
+ version: "2.0.0"
5
+ description: Sample API for Delimit quickstart
6
+ paths:
7
+ /pets:
8
+ get:
9
+ summary: List all pets
10
+ operationId: listPets
11
+ parameters:
12
+ - name: limit
13
+ in: query
14
+ required: false
15
+ schema:
16
+ type: integer
17
+ format: int32
18
+ responses:
19
+ "200":
20
+ description: A list of pets
21
+ content:
22
+ application/json:
23
+ schema:
24
+ type: array
25
+ items:
26
+ $ref: "#/components/schemas/Pet"
27
+ post:
28
+ summary: Create a pet
29
+ operationId: createPet
30
+ requestBody:
31
+ required: true
32
+ content:
33
+ application/json:
34
+ schema:
35
+ $ref: "#/components/schemas/Pet"
36
+ responses:
37
+ "201":
38
+ description: Pet created
39
+ # /pets/{petId} removed -- this is the breaking change
40
+ components:
41
+ schemas:
42
+ Pet:
43
+ type: object
44
+ required:
45
+ - id
46
+ - name
47
+ properties:
48
+ id:
49
+ type: integer
50
+ name:
51
+ type: string
52
+ status:
53
+ type: string
54
+ enum:
55
+ - available
56
+ - adopted
@@ -0,0 +1,87 @@
1
+ openapi: "3.0.3"
2
+ info:
3
+ title: Pet Store API
4
+ version: "1.0.0"
5
+ description: Sample API for Delimit quickstart
6
+ paths:
7
+ /pets:
8
+ get:
9
+ summary: List all pets
10
+ operationId: listPets
11
+ parameters:
12
+ - name: limit
13
+ in: query
14
+ required: false
15
+ schema:
16
+ type: integer
17
+ format: int32
18
+ responses:
19
+ "200":
20
+ description: A list of pets
21
+ content:
22
+ application/json:
23
+ schema:
24
+ type: array
25
+ items:
26
+ $ref: "#/components/schemas/Pet"
27
+ post:
28
+ summary: Create a pet
29
+ operationId: createPet
30
+ requestBody:
31
+ required: true
32
+ content:
33
+ application/json:
34
+ schema:
35
+ $ref: "#/components/schemas/Pet"
36
+ responses:
37
+ "201":
38
+ description: Pet created
39
+ /pets/{petId}:
40
+ get:
41
+ summary: Get a pet by ID
42
+ operationId: getPetById
43
+ parameters:
44
+ - name: petId
45
+ in: path
46
+ required: true
47
+ schema:
48
+ type: string
49
+ responses:
50
+ "200":
51
+ description: A single pet
52
+ content:
53
+ application/json:
54
+ schema:
55
+ $ref: "#/components/schemas/Pet"
56
+ delete:
57
+ summary: Delete a pet
58
+ operationId: deletePet
59
+ parameters:
60
+ - name: petId
61
+ in: path
62
+ required: true
63
+ schema:
64
+ type: string
65
+ responses:
66
+ "204":
67
+ description: Pet deleted
68
+ components:
69
+ schemas:
70
+ Pet:
71
+ type: object
72
+ required:
73
+ - id
74
+ - name
75
+ properties:
76
+ id:
77
+ type: string
78
+ name:
79
+ type: string
80
+ tag:
81
+ type: string
82
+ status:
83
+ type: string
84
+ enum:
85
+ - available
86
+ - pending
87
+ - adopted