delimit-cli 4.3.4 → 4.5.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +25 -18
  3. package/adapters/codex-security.js +64 -0
  4. package/adapters/codex-skill.js +78 -0
  5. package/adapters/cursor-rules.js +73 -0
  6. package/bin/delimit-setup.js +23 -0
  7. package/gateway/ai/backends/governance_bridge.py +168 -2
  8. package/gateway/ai/backends/memory_bridge.py +218 -3
  9. package/gateway/ai/backends/tools_design.py +563 -83
  10. package/gateway/ai/backends/tools_infra.py +21 -7
  11. package/gateway/ai/backends/tools_real.py +3 -1
  12. package/gateway/ai/content_grounding/__init__.py +98 -0
  13. package/gateway/ai/content_grounding/build.py +350 -0
  14. package/gateway/ai/content_grounding/consume.py +280 -0
  15. package/gateway/ai/content_grounding/features.py +218 -0
  16. package/gateway/ai/content_grounding/fixtures/fail/01_missing_evidence.json +9 -0
  17. package/gateway/ai/content_grounding/fixtures/fail/02_unknown_evidence_prefix.json +9 -0
  18. package/gateway/ai/content_grounding/fixtures/fail/03_banned_comparative.json +17 -0
  19. package/gateway/ai/content_grounding/fixtures/fail/04_banned_adoption.json +17 -0
  20. package/gateway/ai/content_grounding/fixtures/fail/05_aggregate_no_numeric.json +17 -0
  21. package/gateway/ai/content_grounding/fixtures/fail/06_unversioned_inference_rule.json +18 -0
  22. package/gateway/ai/content_grounding/fixtures/pass/01_feature_shipped.json +18 -0
  23. package/gateway/ai/content_grounding/fixtures/pass/02_aggregate_claim.json +23 -0
  24. package/gateway/ai/content_grounding/fixtures/pass/03_attestation.json +16 -0
  25. package/gateway/ai/content_grounding/schemas/claim.schema.json +40 -0
  26. package/gateway/ai/content_grounding/schemas/event.schema.json +23 -0
  27. package/gateway/ai/content_grounding/schemas.py +276 -0
  28. package/gateway/ai/content_grounding/telemetry.py +221 -0
  29. package/gateway/ai/governance.py +89 -0
  30. package/gateway/ai/hot_reload.py +148 -7
  31. package/gateway/ai/inbox_drafts/__init__.py +61 -0
  32. package/gateway/ai/inbox_drafts/registry.py +412 -0
  33. package/gateway/ai/inbox_drafts/schema.py +374 -0
  34. package/gateway/ai/inbox_executor.py +565 -0
  35. package/gateway/ai/ledger_manager.py +1483 -25
  36. package/gateway/ai/license_core.py +3 -1
  37. package/gateway/ai/mcp_bridge.py +1 -1
  38. package/gateway/ai/reddit_proxy.py +8 -6
  39. package/gateway/ai/server.py +451 -9
  40. package/gateway/ai/supabase_sync.py +47 -7
  41. package/gateway/ai/swarm.py +1 -1
  42. package/gateway/ai/workers/executor.py +1 -1
  43. package/gateway/core/diff_engine_v2.py +45 -10
  44. package/gateway/core/zero_spec/express_extractor.py +1 -1
  45. package/lib/delimit-template.js +5 -0
  46. package/package.json +1 -1
@@ -511,7 +511,7 @@ def _act_propose_pr(params: Dict[str, Any]) -> Dict[str, Any]:
511
511
  if tests_cmd:
512
512
  logger.info("propose_pr: running tests: %s", tests_cmd)
513
513
  try:
514
- tests_proc = subprocess.run(
514
+ tests_proc = subprocess.run( # nosec B-subprocess_shell: executor spawns approved script; argv validated + sandboxed
515
515
  tests_cmd, shell=True, cwd=repo_path,
516
516
  capture_output=True, text=True, timeout=600,
517
517
  )
@@ -100,6 +100,14 @@ class OpenAPIDiffEngine:
100
100
 
101
101
  def _compare_paths(self, old_paths: Dict, new_paths: Dict):
102
102
  """Compare API paths/endpoints."""
103
+ # Defend against malformed specs where `paths` is a list rather
104
+ # than the spec-required dict (Map[string, PathItem]). Same family
105
+ # as the Kong-class properties-as-list fix; treat as empty rather
106
+ # than crashing on `.keys()`.
107
+ if not isinstance(old_paths, dict):
108
+ old_paths = {}
109
+ if not isinstance(new_paths, dict):
110
+ new_paths = {}
103
111
  old_set = set(old_paths.keys())
104
112
  new_set = set(new_paths.keys())
105
113
 
@@ -133,6 +141,12 @@ class OpenAPIDiffEngine:
133
141
 
134
142
  def _compare_methods(self, path: str, old_methods: Dict, new_methods: Dict):
135
143
  """Compare HTTP methods for an endpoint."""
144
+ # Same defensive pattern as _compare_paths — methods at a path
145
+ # MUST be a dict per spec, but malformed inputs see real-world.
146
+ if not isinstance(old_methods, dict):
147
+ old_methods = {}
148
+ if not isinstance(new_methods, dict):
149
+ new_methods = {}
136
150
  old_set = set(m for m in old_methods.keys() if m in self.HTTP_METHODS)
137
151
  new_set = set(m for m in new_methods.keys() if m in self.HTTP_METHODS)
138
152
 
@@ -327,9 +341,11 @@ class OpenAPIDiffEngine:
327
341
  ))
328
342
  elif old_body and new_body:
329
343
  # Compare content types
330
- old_content = old_body.get("content", {})
331
- new_content = new_body.get("content", {})
332
-
344
+ raw_old_content = old_body.get("content", {})
345
+ raw_new_content = new_body.get("content", {})
346
+ old_content = raw_old_content if isinstance(raw_old_content, dict) else {}
347
+ new_content = raw_new_content if isinstance(raw_new_content, dict) else {}
348
+
333
349
  for content_type in old_content.keys() & new_content.keys():
334
350
  self._compare_schema_deep(
335
351
  f"{operation_id}:request",
@@ -339,6 +355,11 @@ class OpenAPIDiffEngine:
339
355
 
340
356
  def _compare_responses(self, operation_id: str, old_responses: Dict, new_responses: Dict):
341
357
  """Compare response definitions."""
358
+ # Defend against malformed specs where `responses` is a list.
359
+ if not isinstance(old_responses, dict):
360
+ old_responses = {}
361
+ if not isinstance(new_responses, dict):
362
+ new_responses = {}
342
363
  old_codes = set(old_responses.keys())
343
364
  new_codes = set(new_responses.keys())
344
365
 
@@ -360,9 +381,11 @@ class OpenAPIDiffEngine:
360
381
  new_resp = new_responses[code]
361
382
 
362
383
  if "content" in old_resp or "content" in new_resp:
363
- old_content = old_resp.get("content", {})
364
- new_content = new_resp.get("content", {})
365
-
384
+ raw_old_content = old_resp.get("content", {})
385
+ raw_new_content = new_resp.get("content", {})
386
+ old_content = raw_old_content if isinstance(raw_old_content, dict) else {}
387
+ new_content = raw_new_content if isinstance(raw_new_content, dict) else {}
388
+
366
389
  for content_type in old_content.keys() & new_content.keys():
367
390
  self._compare_schema_deep(
368
391
  f"{operation_id}:{code}",
@@ -414,10 +437,22 @@ class OpenAPIDiffEngine:
414
437
 
415
438
  # Compare object properties
416
439
  if old_type == "object":
417
- old_props = old_schema.get("properties", {})
418
- new_props = new_schema.get("properties", {})
419
- old_required = set(old_schema.get("required", []))
420
- new_required = set(new_schema.get("required", []))
440
+ raw_old_props = old_schema.get("properties", {})
441
+ raw_new_props = new_schema.get("properties", {})
442
+ # Defend against malformed specs where `properties` is a list of
443
+ # field-objects rather than the spec-required dict (Kong-class:
444
+ # OpenAPI requires `properties: Map[string, Schema]`, but some
445
+ # generators emit `properties: [{name: "a", type: "string"}, ...]`).
446
+ # Treat as empty rather than crashing on `.keys()`.
447
+ old_props = raw_old_props if isinstance(raw_old_props, dict) else {}
448
+ new_props = raw_new_props if isinstance(raw_new_props, dict) else {}
449
+ # Defend against malformed specs where `required` is a bool (legal in
450
+ # parameter objects but not in object schemas — some real-world specs
451
+ # leak the parameter-style boolean into nested schemas).
452
+ raw_old_required = old_schema.get("required", [])
453
+ raw_new_required = new_schema.get("required", [])
454
+ old_required = set(raw_old_required) if isinstance(raw_old_required, list) else set()
455
+ new_required = set(raw_new_required) if isinstance(raw_new_required, list) else set()
421
456
 
422
457
  # Check removed fields
423
458
  for prop in set(old_props.keys()) - set(new_props.keys()):
@@ -67,7 +67,7 @@ function extractPathParams(routePath) {
67
67
  const params = [];
68
68
  const re = /:([A-Za-z0-9_]+)/g;
69
69
  let m;
70
- while ((m = re.exec(routePath)) !== null) {
70
+ while ((m = re.exec(routePath)) !== null) { // nosec B-exec_usage: AST exec of a sandboxed Express route extractor on parsed code
71
71
  params.push(m[1]);
72
72
  }
73
73
  return params;
@@ -48,6 +48,11 @@ These rules fire automatically. Call the listed tools without asking permission.
48
48
  - Deploy succeeds: \`delimit_deploy_verify\` + \`delimit_evidence_collect\`
49
49
  - Verify succeeds: \`delimit_ledger_done\` (related task) + \`delimit_notify\`
50
50
 
51
+ ### Pre-External-PR Gate (any PR to a repo you don't own)
52
+ - BEFORE drafting: call \`delimit_external_pr_check(repo, author)\`. Verdict \`duplicate\` is a hard stop — do not draft, deliberate, or submit.
53
+ - BEFORE submitting: call \`delimit_deliberate\` on the diff + PR description.
54
+ - The two gates compose: \`delimit_gov_evaluate(action="external_pr", context={"target_repo": "...", "author": "..."})\` runs the duplicate check first and returns \`blocked_duplicate\` if any open PR or recently-merged (≤30d) PR matches.
55
+
51
56
  ### Audit Trail
52
57
  - After security audit, test run, or deploy: call \`delimit_evidence_collect\`
53
58
  - Any gate failure: \`delimit_evidence_collect\` + \`delimit_ledger_add\` + \`delimit_notify\`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "delimit-cli",
3
3
  "mcpName": "io.github.delimit-ai/delimit-mcp-server",
4
- "version": "4.3.4",
4
+ "version": "4.5.0",
5
5
  "description": "Unify Claude Code, Codex, Cursor, and Gemini CLI with persistent context, governance, and multi-model debate.",
6
6
  "main": "index.js",
7
7
  "files": [