delimit-cli 3.6.4 → 3.6.6

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 (39) hide show
  1. package/README.md +1 -1
  2. package/bin/delimit-setup.js +14 -3
  3. package/package.json +1 -1
  4. package/gateway/core/__pycache__/__init__.cpython-310.pyc +0 -0
  5. package/gateway/core/__pycache__/auto_baseline.cpython-310.pyc +0 -0
  6. package/gateway/core/__pycache__/ci_formatter.cpython-310.pyc +0 -0
  7. package/gateway/core/__pycache__/contract_ledger.cpython-310.pyc +0 -0
  8. package/gateway/core/__pycache__/dependency_graph.cpython-310.pyc +0 -0
  9. package/gateway/core/__pycache__/dependency_manifest.cpython-310.pyc +0 -0
  10. package/gateway/core/__pycache__/diff_engine_v2.cpython-310.pyc +0 -0
  11. package/gateway/core/__pycache__/event_backbone.cpython-310.pyc +0 -0
  12. package/gateway/core/__pycache__/event_schema.cpython-310.pyc +0 -0
  13. package/gateway/core/__pycache__/explainer.cpython-310.pyc +0 -0
  14. package/gateway/core/__pycache__/gateway.cpython-310.pyc +0 -0
  15. package/gateway/core/__pycache__/gateway_v2.cpython-310.pyc +0 -0
  16. package/gateway/core/__pycache__/gateway_v3.cpython-310.pyc +0 -0
  17. package/gateway/core/__pycache__/impact_analyzer.cpython-310.pyc +0 -0
  18. package/gateway/core/__pycache__/policy_engine.cpython-310.pyc +0 -0
  19. package/gateway/core/__pycache__/registry.cpython-310.pyc +0 -0
  20. package/gateway/core/__pycache__/registry_v2.cpython-310.pyc +0 -0
  21. package/gateway/core/__pycache__/registry_v3.cpython-310.pyc +0 -0
  22. package/gateway/core/__pycache__/semver_classifier.cpython-310.pyc +0 -0
  23. package/gateway/core/__pycache__/spec_detector.cpython-310.pyc +0 -0
  24. package/gateway/core/__pycache__/surface_bridge.cpython-310.pyc +0 -0
  25. package/gateway/core/diff_engine_v2.py.bak +0 -426
  26. package/gateway/core/zero_spec/__pycache__/__init__.cpython-310.pyc +0 -0
  27. package/gateway/core/zero_spec/__pycache__/detector.cpython-310.pyc +0 -0
  28. package/gateway/core/zero_spec/__pycache__/express_extractor.cpython-310.pyc +0 -0
  29. package/gateway/core/zero_spec/__pycache__/fastapi_extractor.cpython-310.pyc +0 -0
  30. package/gateway/core/zero_spec/__pycache__/nestjs_extractor.cpython-310.pyc +0 -0
  31. package/gateway/tasks/__pycache__/__init__.cpython-310.pyc +0 -0
  32. package/gateway/tasks/__pycache__/check_policy.cpython-310.pyc +0 -0
  33. package/gateway/tasks/__pycache__/check_policy_v2.cpython-310.pyc +0 -0
  34. package/gateway/tasks/__pycache__/check_policy_v3.cpython-310.pyc +0 -0
  35. package/gateway/tasks/__pycache__/explain_diff.cpython-310.pyc +0 -0
  36. package/gateway/tasks/__pycache__/explain_diff_v2.cpython-310.pyc +0 -0
  37. package/gateway/tasks/__pycache__/validate_api.cpython-310.pyc +0 -0
  38. package/gateway/tasks/__pycache__/validate_api_v2.cpython-310.pyc +0 -0
  39. package/gateway/tasks/__pycache__/validate_api_v3.cpython-310.pyc +0 -0
package/README.md CHANGED
@@ -39,7 +39,7 @@ That's it. Delimit auto-fetches the base branch spec, diffs it, and posts a PR c
39
39
  - Step-by-step migration guide
40
40
  - Policy violations
41
41
 
42
- [View on GitHub Marketplace →](https://github.com/marketplace/actions/delimit-api-governance)
42
+ [View on GitHub Marketplace →](https://github.com/marketplace/actions/delimit-api-governance) · [See a live PR comment →](https://github.com/delimit-ai/delimit-quickstart/pull/1)
43
43
 
44
44
  ### Example PR comment
45
45
 
@@ -184,10 +184,14 @@ async function main() {
184
184
  }
185
185
 
186
186
  // Step 3d: Configure Gemini CLI (if installed)
187
- const GEMINI_CONFIG = path.join(os.homedir(), '.gemini', 'settings.json');
188
- if (fs.existsSync(GEMINI_CONFIG)) {
187
+ const GEMINI_DIR = path.join(os.homedir(), '.gemini');
188
+ const GEMINI_CONFIG = path.join(GEMINI_DIR, 'settings.json');
189
+ if (fs.existsSync(GEMINI_DIR)) {
189
190
  try {
190
- let geminiConfig = JSON.parse(fs.readFileSync(GEMINI_CONFIG, 'utf-8'));
191
+ let geminiConfig = {};
192
+ if (fs.existsSync(GEMINI_CONFIG)) {
193
+ geminiConfig = JSON.parse(fs.readFileSync(GEMINI_CONFIG, 'utf-8'));
194
+ }
191
195
  if (!geminiConfig.mcpServers) geminiConfig.mcpServers = {};
192
196
  if (geminiConfig.mcpServers.delimit) {
193
197
  log(` ${green('✓')} Delimit already in Gemini CLI config`);
@@ -308,6 +312,13 @@ Run full governance compliance checks. Verify security, policy compliance, evide
308
312
  log('');
309
313
  log(` ${green('Delimit is installed.')} Your AI now has persistent memory and governance.`);
310
314
  log('');
315
+ log(' Configured for:');
316
+ const tools = ['Claude Code'];
317
+ if (fs.existsSync(CODEX_CONFIG)) tools.push('Codex');
318
+ if (fs.existsSync(path.join(os.homedir(), '.cursor'))) tools.push('Cursor');
319
+ if (fs.existsSync(GEMINI_DIR)) tools.push('Gemini CLI');
320
+ log(` ${green('✓')} ${tools.join(', ')}`);
321
+ log('');
311
322
  log(' Try it now:');
312
323
  log(` ${bold('$ claude')}`);
313
324
  log('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "delimit-cli",
3
- "version": "3.6.4",
3
+ "version": "3.6.6",
4
4
  "description": "Catch breaking API changes before they ship. GitHub Action + CLI for OpenAPI specs.",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -1,426 +0,0 @@
1
- """
2
- Enhanced OpenAPI diff engine with deep schema comparison.
3
- Handles nested objects, response schemas, enums, and edge cases.
4
- """
5
-
6
- from typing import Dict, List, Any, Optional, Set, Tuple
7
- from dataclasses import dataclass
8
- from enum import Enum
9
-
10
- class ChangeType(Enum):
11
- # Breaking changes
12
- ENDPOINT_REMOVED = "endpoint_removed"
13
- METHOD_REMOVED = "method_removed"
14
- REQUIRED_PARAM_ADDED = "required_param_added"
15
- PARAM_REMOVED = "param_removed"
16
- RESPONSE_REMOVED = "response_removed"
17
- REQUIRED_FIELD_ADDED = "required_field_added"
18
- FIELD_REMOVED = "field_removed"
19
- TYPE_CHANGED = "type_changed"
20
- FORMAT_CHANGED = "format_changed"
21
- ENUM_VALUE_REMOVED = "enum_value_removed"
22
-
23
- # Non-breaking changes
24
- ENDPOINT_ADDED = "endpoint_added"
25
- METHOD_ADDED = "method_added"
26
- OPTIONAL_PARAM_ADDED = "optional_param_added"
27
- RESPONSE_ADDED = "response_added"
28
- OPTIONAL_FIELD_ADDED = "optional_field_added"
29
- ENUM_VALUE_ADDED = "enum_value_added"
30
- DESCRIPTION_CHANGED = "description_changed"
31
-
32
- @dataclass
33
- class Change:
34
- type: ChangeType
35
- path: str
36
- details: Dict[str, Any]
37
- severity: str # high, medium, low
38
- message: str
39
-
40
- @property
41
- def is_breaking(self) -> bool:
42
- return self.type in [
43
- ChangeType.ENDPOINT_REMOVED,
44
- ChangeType.METHOD_REMOVED,
45
- ChangeType.REQUIRED_PARAM_ADDED,
46
- ChangeType.PARAM_REMOVED,
47
- ChangeType.RESPONSE_REMOVED,
48
- ChangeType.REQUIRED_FIELD_ADDED,
49
- ChangeType.FIELD_REMOVED,
50
- ChangeType.TYPE_CHANGED,
51
- ChangeType.FORMAT_CHANGED,
52
- ChangeType.ENUM_VALUE_REMOVED,
53
- ]
54
-
55
- class OpenAPIDiffEngine:
56
- """Advanced diff engine for OpenAPI specifications."""
57
-
58
- def __init__(self):
59
- self.changes: List[Change] = []
60
-
61
- def compare(self, old_spec: Dict, new_spec: Dict) -> List[Change]:
62
- """Compare two OpenAPI specifications and return all changes."""
63
- self.changes = []
64
-
65
- # Compare paths
66
- self._compare_paths(old_spec.get("paths", {}), new_spec.get("paths", {}))
67
-
68
- # Compare components/schemas
69
- self._compare_schemas(
70
- old_spec.get("components", {}).get("schemas", {}),
71
- new_spec.get("components", {}).get("schemas", {})
72
- )
73
-
74
- # Compare security schemes
75
- self._compare_security(
76
- old_spec.get("components", {}).get("securitySchemes", {}),
77
- new_spec.get("components", {}).get("securitySchemes", {})
78
- )
79
-
80
- return self.changes
81
-
82
- def _compare_paths(self, old_paths: Dict, new_paths: Dict):
83
- """Compare API paths/endpoints."""
84
- old_set = set(old_paths.keys())
85
- new_set = set(new_paths.keys())
86
-
87
- # Check removed endpoints
88
- for path in old_set - new_set:
89
- self.changes.append(Change(
90
- type=ChangeType.ENDPOINT_REMOVED,
91
- path=path,
92
- details={"endpoint": path},
93
- severity="high",
94
- message=f"Endpoint removed: {path}"
95
- ))
96
-
97
- # Check added endpoints
98
- for path in new_set - old_set:
99
- self.changes.append(Change(
100
- type=ChangeType.ENDPOINT_ADDED,
101
- path=path,
102
- details={"endpoint": path},
103
- severity="low",
104
- message=f"New endpoint added: {path}"
105
- ))
106
-
107
- # Check modified endpoints
108
- for path in old_set & new_set:
109
- self._compare_methods(path, old_paths[path], new_paths[path])
110
-
111
- def _compare_methods(self, path: str, old_methods: Dict, new_methods: Dict):
112
- """Compare HTTP methods for an endpoint."""
113
- old_set = set(m for m in old_methods.keys() if m in ["get", "post", "put", "delete", "patch", "head", "options"])
114
- new_set = set(m for m in new_methods.keys() if m in ["get", "post", "put", "delete", "patch", "head", "options"])
115
-
116
- # Check removed methods
117
- for method in old_set - new_set:
118
- self.changes.append(Change(
119
- type=ChangeType.METHOD_REMOVED,
120
- path=f"{path}:{method.upper()}",
121
- details={"endpoint": path, "method": method.upper()},
122
- severity="high",
123
- message=f"Method removed: {method.upper()} {path}"
124
- ))
125
-
126
- # Check modified methods
127
- for method in old_set & new_set:
128
- self._compare_operation(
129
- f"{path}:{method.upper()}",
130
- old_methods[method],
131
- new_methods[method]
132
- )
133
-
134
- def _compare_operation(self, operation_id: str, old_op: Dict, new_op: Dict):
135
- """Compare operation details (parameters, responses, etc.)."""
136
-
137
- # Compare parameters
138
- old_params = {self._param_key(p): p for p in old_op.get("parameters", [])}
139
- new_params = {self._param_key(p): p for p in new_op.get("parameters", [])}
140
-
141
- # Check removed parameters
142
- for param_key in set(old_params.keys()) - set(new_params.keys()):
143
- param = old_params[param_key]
144
- self.changes.append(Change(
145
- type=ChangeType.PARAM_REMOVED,
146
- path=operation_id,
147
- details={"parameter": param["name"], "in": param["in"]},
148
- severity="high",
149
- message=f"Parameter removed: {param['name']} from {operation_id}"
150
- ))
151
-
152
- # Check added required parameters
153
- for param_key in set(new_params.keys()) - set(old_params.keys()):
154
- param = new_params[param_key]
155
- if param.get("required", False):
156
- self.changes.append(Change(
157
- type=ChangeType.REQUIRED_PARAM_ADDED,
158
- path=operation_id,
159
- details={"parameter": param["name"], "in": param["in"]},
160
- severity="high",
161
- message=f"Required parameter added: {param['name']} to {operation_id}"
162
- ))
163
-
164
- # Check parameter schema changes
165
- for param_key in set(old_params.keys()) & set(new_params.keys()):
166
- self._compare_parameter_schemas(
167
- operation_id,
168
- old_params[param_key],
169
- new_params[param_key]
170
- )
171
-
172
- # Compare request body
173
- if "requestBody" in old_op or "requestBody" in new_op:
174
- self._compare_request_body(
175
- operation_id,
176
- old_op.get("requestBody"),
177
- new_op.get("requestBody")
178
- )
179
-
180
- # Compare responses
181
- self._compare_responses(
182
- operation_id,
183
- old_op.get("responses", {}),
184
- new_op.get("responses", {})
185
- )
186
-
187
- def _compare_parameter_schemas(self, operation_id: str, old_param: Dict, new_param: Dict):
188
- """Compare parameter schemas for type changes."""
189
- old_schema = old_param.get("schema", {})
190
- new_schema = new_param.get("schema", {})
191
-
192
- # Check type changes
193
- if old_schema.get("type") != new_schema.get("type"):
194
- self.changes.append(Change(
195
- type=ChangeType.TYPE_CHANGED,
196
- path=operation_id,
197
- details={
198
- "parameter": old_param["name"],
199
- "old_type": old_schema.get("type"),
200
- "new_type": new_schema.get("type")
201
- },
202
- severity="high",
203
- message=f"Parameter type changed: {old_param['name']} from {old_schema.get('type')} to {new_schema.get('type')}"
204
- ))
205
-
206
- # Check enum changes
207
- if "enum" in old_schema or "enum" in new_schema:
208
- self._compare_enums(
209
- f"{operation_id}:{old_param['name']}",
210
- old_schema.get("enum", []),
211
- new_schema.get("enum", [])
212
- )
213
-
214
- def _compare_request_body(self, operation_id: str, old_body: Optional[Dict], new_body: Optional[Dict]):
215
- """Compare request body schemas."""
216
- if old_body and not new_body:
217
- self.changes.append(Change(
218
- type=ChangeType.FIELD_REMOVED,
219
- path=operation_id,
220
- details={"field": "request_body"},
221
- severity="high",
222
- message=f"Request body removed from {operation_id}"
223
- ))
224
- elif not old_body and new_body and new_body.get("required", False):
225
- self.changes.append(Change(
226
- type=ChangeType.REQUIRED_FIELD_ADDED,
227
- path=operation_id,
228
- details={"field": "request_body"},
229
- severity="high",
230
- message=f"Required request body added to {operation_id}"
231
- ))
232
- elif old_body and new_body:
233
- # Compare content types
234
- old_content = old_body.get("content", {})
235
- new_content = new_body.get("content", {})
236
-
237
- for content_type in old_content.keys() & new_content.keys():
238
- self._compare_schema_deep(
239
- f"{operation_id}:request",
240
- old_content[content_type].get("schema", {}),
241
- new_content[content_type].get("schema", {})
242
- )
243
-
244
- def _compare_responses(self, operation_id: str, old_responses: Dict, new_responses: Dict):
245
- """Compare response definitions."""
246
- old_codes = set(old_responses.keys())
247
- new_codes = set(new_responses.keys())
248
-
249
- # Check removed responses
250
- for code in old_codes - new_codes:
251
- # Only flag 2xx responses as breaking
252
- if code.startswith("2"):
253
- self.changes.append(Change(
254
- type=ChangeType.RESPONSE_REMOVED,
255
- path=operation_id,
256
- details={"response_code": code},
257
- severity="high",
258
- message=f"Success response {code} removed from {operation_id}"
259
- ))
260
-
261
- # Compare response schemas
262
- for code in old_codes & new_codes:
263
- old_resp = old_responses[code]
264
- new_resp = new_responses[code]
265
-
266
- if "content" in old_resp or "content" in new_resp:
267
- old_content = old_resp.get("content", {})
268
- new_content = new_resp.get("content", {})
269
-
270
- for content_type in old_content.keys() & new_content.keys():
271
- self._compare_schema_deep(
272
- f"{operation_id}:{code}",
273
- old_content[content_type].get("schema", {}),
274
- new_content[content_type].get("schema", {})
275
- )
276
-
277
- def _compare_schema_deep(self, path: str, old_schema: Dict, new_schema: Dict, required_fields: Optional[Set[str]] = None):
278
- """Deep comparison of schemas including nested objects."""
279
-
280
- # Handle references
281
- if "$ref" in old_schema or "$ref" in new_schema:
282
- # TODO: Resolve references properly
283
- return
284
-
285
- # Compare types
286
- old_type = old_schema.get("type")
287
- new_type = new_schema.get("type")
288
-
289
- if old_type != new_type and old_type is not None:
290
- self.changes.append(Change(
291
- type=ChangeType.TYPE_CHANGED,
292
- path=path,
293
- details={"old_type": old_type, "new_type": new_type},
294
- severity="high",
295
- message=f"Type changed from {old_type} to {new_type} at {path}"
296
- ))
297
- return
298
-
299
- # Compare object properties
300
- if old_type == "object":
301
- old_props = old_schema.get("properties", {})
302
- new_props = new_schema.get("properties", {})
303
- old_required = set(old_schema.get("required", []))
304
- new_required = set(new_schema.get("required", []))
305
-
306
- # Check removed fields
307
- for prop in set(old_props.keys()) - set(new_props.keys()):
308
- if prop in old_required:
309
- self.changes.append(Change(
310
- type=ChangeType.FIELD_REMOVED,
311
- path=f"{path}.{prop}",
312
- details={"field": prop},
313
- severity="high",
314
- message=f"Required field '{prop}' removed at {path}"
315
- ))
316
-
317
- # Check new required fields
318
- for prop in new_required - old_required:
319
- if prop not in old_props:
320
- self.changes.append(Change(
321
- type=ChangeType.REQUIRED_FIELD_ADDED,
322
- path=f"{path}.{prop}",
323
- details={"field": prop},
324
- severity="high",
325
- message=f"New required field '{prop}' added at {path}"
326
- ))
327
-
328
- # Recursively compare nested properties
329
- for prop in set(old_props.keys()) & set(new_props.keys()):
330
- self._compare_schema_deep(
331
- f"{path}.{prop}",
332
- old_props[prop],
333
- new_props[prop],
334
- old_required if prop in old_required else None
335
- )
336
-
337
- # Compare arrays
338
- elif old_type == "array":
339
- if "items" in old_schema and "items" in new_schema:
340
- self._compare_schema_deep(
341
- f"{path}[]",
342
- old_schema["items"],
343
- new_schema["items"]
344
- )
345
-
346
- # Compare enums
347
- if "enum" in old_schema or "enum" in new_schema:
348
- self._compare_enums(path, old_schema.get("enum", []), new_schema.get("enum", []))
349
-
350
- def _compare_enums(self, path: str, old_enum: List, new_enum: List):
351
- """Compare enum values."""
352
- old_set = set(old_enum)
353
- new_set = set(new_enum)
354
-
355
- # Removed enum values are breaking
356
- for value in old_set - new_set:
357
- self.changes.append(Change(
358
- type=ChangeType.ENUM_VALUE_REMOVED,
359
- path=path,
360
- details={"value": value},
361
- severity="high",
362
- message=f"Enum value '{value}' removed at {path}"
363
- ))
364
-
365
- # Added enum values are non-breaking
366
- for value in new_set - old_set:
367
- self.changes.append(Change(
368
- type=ChangeType.ENUM_VALUE_ADDED,
369
- path=path,
370
- details={"value": value},
371
- severity="low",
372
- message=f"Enum value '{value}' added at {path}"
373
- ))
374
-
375
- def _compare_schemas(self, old_schemas: Dict, new_schemas: Dict):
376
- """Compare component schemas."""
377
- # Schema removal is breaking if referenced
378
- for schema_name in set(old_schemas.keys()) - set(new_schemas.keys()):
379
- self.changes.append(Change(
380
- type=ChangeType.FIELD_REMOVED,
381
- path=f"#/components/schemas/{schema_name}",
382
- details={"schema": schema_name},
383
- severity="medium",
384
- message=f"Schema '{schema_name}' removed"
385
- ))
386
-
387
- # Compare existing schemas
388
- for schema_name in set(old_schemas.keys()) & set(new_schemas.keys()):
389
- self._compare_schema_deep(
390
- f"#/components/schemas/{schema_name}",
391
- old_schemas[schema_name],
392
- new_schemas[schema_name]
393
- )
394
-
395
- def _compare_security(self, old_security: Dict, new_security: Dict):
396
- """Compare security schemes."""
397
- # Security scheme changes are usually breaking
398
- for scheme in set(old_security.keys()) - set(new_security.keys()):
399
- self.changes.append(Change(
400
- type=ChangeType.FIELD_REMOVED,
401
- path=f"#/components/securitySchemes/{scheme}",
402
- details={"scheme": scheme},
403
- severity="high",
404
- message=f"Security scheme '{scheme}' removed"
405
- ))
406
-
407
- def _param_key(self, param: Dict) -> str:
408
- """Generate unique key for parameter."""
409
- return f"{param.get('in', 'query')}:{param.get('name', '')}"
410
-
411
- def get_breaking_changes(self) -> List[Change]:
412
- """Get only breaking changes."""
413
- return [c for c in self.changes if c.is_breaking]
414
-
415
- def get_summary(self) -> Dict[str, Any]:
416
- """Get summary of all changes."""
417
- breaking = self.get_breaking_changes()
418
- return {
419
- "total_changes": len(self.changes),
420
- "breaking_changes": len(breaking),
421
- "endpoints_removed": len([c for c in breaking if c.type == ChangeType.ENDPOINT_REMOVED]),
422
- "methods_removed": len([c for c in breaking if c.type == ChangeType.METHOD_REMOVED]),
423
- "parameters_changed": len([c for c in breaking if c.type in [ChangeType.PARAM_REMOVED, ChangeType.REQUIRED_PARAM_ADDED]]),
424
- "schemas_changed": len([c for c in breaking if c.type in [ChangeType.FIELD_REMOVED, ChangeType.REQUIRED_FIELD_ADDED, ChangeType.TYPE_CHANGED]]),
425
- "is_breaking": len(breaking) > 0
426
- }