delimit-cli 3.1.1 → 3.3.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.
@@ -1194,6 +1194,39 @@ program
1194
1194
  require('./delimit-setup.js');
1195
1195
  });
1196
1196
 
1197
+ // Activate license key
1198
+ program
1199
+ .command('activate <key>')
1200
+ .description('Activate a Delimit Pro license key')
1201
+ .action(async (key) => {
1202
+ const os = require('os');
1203
+ const licenseDir = path.join(os.homedir(), '.delimit');
1204
+ const licensePath = path.join(licenseDir, 'license.json');
1205
+
1206
+ if (!key || key.length < 10) {
1207
+ console.error(chalk.red('Invalid license key format. Keys are at least 10 characters.'));
1208
+ process.exit(1);
1209
+ }
1210
+
1211
+ // Write license file
1212
+ const crypto = require('crypto');
1213
+ const machineHash = crypto.createHash('sha256').update(os.homedir()).digest('hex').slice(0, 16);
1214
+ const licenseData = {
1215
+ key: key,
1216
+ tier: 'pro',
1217
+ valid: true,
1218
+ activated_at: Date.now() / 1000,
1219
+ machine_hash: machineHash,
1220
+ };
1221
+
1222
+ if (!fs.existsSync(licenseDir)) {
1223
+ fs.mkdirSync(licenseDir, { recursive: true });
1224
+ }
1225
+ fs.writeFileSync(licensePath, JSON.stringify(licenseData, null, 2));
1226
+ console.log(chalk.green('License activated successfully.'));
1227
+ console.log(chalk.dim('Tier: pro'));
1228
+ });
1229
+
1197
1230
  // Hide legacy/internal commands from --help
1198
1231
  ['install', 'mode', 'status', 'policy', 'auth', 'audit',
1199
1232
  'explain-decision', 'uninstall', 'proxy', 'hook'].forEach(name => {
@@ -262,8 +262,45 @@ Run full governance compliance checks. Verify security, policy compliance, evide
262
262
  }
263
263
  log(` ${green('✓')} ${installed} agents installed (${Object.keys(agents).length - installed} already existed)`);
264
264
 
265
- // Step 5: Summary
266
- step(5, 'Done!');
265
+ // Step 5: Create CLAUDE.md for first-run guidance
266
+ step(5, 'Setting up first-run guidance...');
267
+
268
+ const claudeMd = path.join(os.homedir(), 'CLAUDE.md');
269
+ if (!fs.existsSync(claudeMd)) {
270
+ fs.writeFileSync(claudeMd, `# Delimit AI Guardrails
271
+
272
+ Delimit governance tools are installed. On first use, try:
273
+
274
+ - "check governance health" — see the status of this project
275
+ - "initialize governance" — set up policies and ledger for this project
276
+ - "run test coverage" — measure test coverage
277
+ - "analyze this repo" — get a health report
278
+
279
+ ## Quick Start
280
+ If this project hasn't been initialized for governance yet, say:
281
+ "initialize governance for this project"
282
+
283
+ This creates .delimit/policies.yml and a ledger directory.
284
+
285
+ ## Available Agents
286
+ - /lint — check API specs for breaking changes
287
+ - /engineering — build, test, refactor with governance checks
288
+ - /governance — full compliance audit
289
+
290
+ ## Key Tools
291
+ - delimit_init — bootstrap governance for a project
292
+ - delimit_lint — diff two OpenAPI specs
293
+ - delimit_test_coverage — measure test coverage
294
+ - delimit_gov_health — check governance status
295
+ - delimit_repo_analyze — full repo health report
296
+ `);
297
+ log(` ${green('✓')} Created ${claudeMd} with first-run guidance`);
298
+ } else {
299
+ log(` ${dim(' CLAUDE.md already exists — skipped')}`);
300
+ }
301
+
302
+ // Step 6: Summary
303
+ step(6, 'Done!');
267
304
  log('');
268
305
  log(` ${green('Delimit is installed.')} Your AI agents are now monitored.`);
269
306
  log('');
@@ -0,0 +1,133 @@
1
+ """
2
+ Delimit license validation.
3
+ Free tools work without a key. Premium tools check for a valid key.
4
+ Validates against Lemon Squeezy API when online, falls back to local cache.
5
+ """
6
+ import hashlib
7
+ import json
8
+ import os
9
+ import time
10
+ from pathlib import Path
11
+
12
+ LICENSE_FILE = Path.home() / ".delimit" / "license.json"
13
+ LS_VALIDATE_URL = "https://api.lemonsqueezy.com/v1/licenses/validate"
14
+
15
+
16
+ def get_license() -> dict:
17
+ """Load license from ~/.delimit/license.json"""
18
+ if not LICENSE_FILE.exists():
19
+ return {"tier": "free", "valid": True}
20
+ try:
21
+ data = json.loads(LICENSE_FILE.read_text())
22
+ if data.get("expires_at") and data["expires_at"] < time.time():
23
+ return {"tier": "free", "valid": True, "expired": True}
24
+ return data
25
+ except Exception:
26
+ return {"tier": "free", "valid": True}
27
+
28
+
29
+ def is_premium() -> bool:
30
+ """Check if user has a premium license."""
31
+ lic = get_license()
32
+ return lic.get("tier") in ("pro", "enterprise") and lic.get("valid", False)
33
+
34
+
35
+ def require_premium(tool_name: str) -> dict | None:
36
+ """Check premium access. Returns None if allowed, error dict if not."""
37
+ if is_premium():
38
+ return None
39
+ return {
40
+ "error": f"'{tool_name}' requires Delimit Pro. Upgrade at https://delimit.ai/pricing",
41
+ "status": "premium_required",
42
+ "tool": tool_name,
43
+ "current_tier": get_license().get("tier", "free"),
44
+ }
45
+
46
+
47
+ def activate_license(key: str) -> dict:
48
+ """Activate a license key via Lemon Squeezy API.
49
+ Falls back to local validation if API is unreachable."""
50
+ if not key or len(key) < 10:
51
+ return {"error": "Invalid license key format"}
52
+
53
+ machine_hash = hashlib.sha256(str(Path.home()).encode()).hexdigest()[:16]
54
+
55
+ # Try Lemon Squeezy remote validation
56
+ try:
57
+ import urllib.request
58
+ import urllib.error
59
+
60
+ data = json.dumps({
61
+ "license_key": key,
62
+ "instance_name": machine_hash,
63
+ }).encode()
64
+
65
+ req = urllib.request.Request(
66
+ LS_VALIDATE_URL,
67
+ data=data,
68
+ headers={
69
+ "Content-Type": "application/json",
70
+ "Accept": "application/json",
71
+ },
72
+ method="POST",
73
+ )
74
+
75
+ with urllib.request.urlopen(req, timeout=10) as resp:
76
+ result = json.loads(resp.read())
77
+
78
+ if result.get("valid"):
79
+ license_data = {
80
+ "key": key,
81
+ "tier": "pro",
82
+ "valid": True,
83
+ "activated_at": time.time(),
84
+ "machine_hash": machine_hash,
85
+ "instance_id": result.get("instance", {}).get("id"),
86
+ "license_id": result.get("license_key", {}).get("id"),
87
+ "customer_name": result.get("meta", {}).get("customer_name", ""),
88
+ "validated_via": "lemon_squeezy",
89
+ }
90
+ LICENSE_FILE.parent.mkdir(parents=True, exist_ok=True)
91
+ LICENSE_FILE.write_text(json.dumps(license_data, indent=2))
92
+ return {"status": "activated", "tier": "pro", "message": "License activated successfully."}
93
+ else:
94
+ return {
95
+ "error": "Invalid license key. Check your key and try again.",
96
+ "status": "invalid",
97
+ "detail": result.get("error", ""),
98
+ }
99
+
100
+ except (urllib.error.URLError, OSError):
101
+ # API unreachable — accept key locally (offline activation)
102
+ license_data = {
103
+ "key": key,
104
+ "tier": "pro",
105
+ "valid": True,
106
+ "activated_at": time.time(),
107
+ "machine_hash": machine_hash,
108
+ "validated_via": "offline",
109
+ }
110
+ LICENSE_FILE.parent.mkdir(parents=True, exist_ok=True)
111
+ LICENSE_FILE.write_text(json.dumps(license_data, indent=2))
112
+ return {
113
+ "status": "activated",
114
+ "tier": "pro",
115
+ "message": "License activated (offline). Will validate online next time.",
116
+ }
117
+ except Exception as e:
118
+ # Unexpected error — still activate locally
119
+ license_data = {
120
+ "key": key,
121
+ "tier": "pro",
122
+ "valid": True,
123
+ "activated_at": time.time(),
124
+ "machine_hash": machine_hash,
125
+ "validated_via": "fallback",
126
+ }
127
+ LICENSE_FILE.parent.mkdir(parents=True, exist_ok=True)
128
+ LICENSE_FILE.write_text(json.dumps(license_data, indent=2))
129
+ return {
130
+ "status": "activated",
131
+ "tier": "pro",
132
+ "message": f"License activated (validation error: {e}). Will retry online later.",
133
+ }
File without changes
@@ -36,7 +36,21 @@ logger = logging.getLogger("delimit.ai")
36
36
  mcp = FastMCP("delimit")
37
37
  mcp.description = "Delimit — The smart lint engine for OpenAPI. Unified agent surface."
38
38
 
39
- VERSION = "2.0.0"
39
+ VERSION = "3.2.0"
40
+
41
+ # LED-044: Hide STUB and PASS-THROUGH tools from MCP unless opted in.
42
+ # Set DELIMIT_SHOW_EXPERIMENTAL=1 to expose all tools (internal development).
43
+ SHOW_EXPERIMENTAL = os.environ.get("DELIMIT_SHOW_EXPERIMENTAL", "") == "1"
44
+
45
+
46
+ def _experimental_tool():
47
+ """Decorator that only registers the function as an MCP tool if SHOW_EXPERIMENTAL is set.
48
+ When disabled, the function still exists but is not exposed via MCP."""
49
+ def decorator(fn):
50
+ if SHOW_EXPERIMENTAL:
51
+ return mcp.tool()(fn)
52
+ return fn
53
+ return decorator
40
54
 
41
55
 
42
56
  def _safe_call(fn, **kwargs) -> Dict[str, Any]:
@@ -266,7 +280,7 @@ def delimit_init(
266
280
 
267
281
  @mcp.tool()
268
282
  def delimit_os_plan(operation: str, target: str, parameters: Optional[Dict[str, Any]] = None, require_approval: bool = True) -> Dict[str, Any]:
269
- """Create a governed execution plan.
283
+ """Create a governed execution plan (Pro).
270
284
 
271
285
  Args:
272
286
  operation: Operation to plan (e.g. "deploy", "migrate").
@@ -274,24 +288,36 @@ def delimit_os_plan(operation: str, target: str, parameters: Optional[Dict[str,
274
288
  parameters: Operation parameters.
275
289
  require_approval: Whether to require approval before execution.
276
290
  """
291
+ from ai.license import require_premium
292
+ gate = require_premium("os_plan")
293
+ if gate:
294
+ return gate
277
295
  from backends.os_bridge import create_plan
278
296
  return _safe_call(create_plan, operation=operation, target=target, parameters=parameters, require_approval=require_approval)
279
297
 
280
298
 
281
299
  @mcp.tool()
282
300
  def delimit_os_status() -> Dict[str, Any]:
283
- """Get current Delimit OS status with plan/task/token counts."""
301
+ """Get current Delimit OS status with plan/task/token counts (Pro)."""
302
+ from ai.license import require_premium
303
+ gate = require_premium("os_status")
304
+ if gate:
305
+ return gate
284
306
  from backends.os_bridge import get_status
285
307
  return _safe_call(get_status)
286
308
 
287
309
 
288
310
  @mcp.tool()
289
311
  def delimit_os_gates(plan_id: str) -> Dict[str, Any]:
290
- """Check governance gates for a plan.
312
+ """Check governance gates for a plan (Pro).
291
313
 
292
314
  Args:
293
315
  plan_id: The plan ID (e.g. "PLAN-A1B2C3D4").
294
316
  """
317
+ from ai.license import require_premium
318
+ gate = require_premium("os_gates")
319
+ if gate:
320
+ return gate
295
321
  from backends.os_bridge import check_gates
296
322
  return _safe_call(check_gates, plan_id=plan_id)
297
323
 
@@ -300,53 +326,69 @@ def delimit_os_gates(plan_id: str) -> Dict[str, Any]:
300
326
 
301
327
  @mcp.tool()
302
328
  def delimit_gov_health(repo: str = ".") -> Dict[str, Any]:
303
- """Check governance system health.
329
+ """Check governance system health (Pro).
304
330
 
305
331
  Args:
306
332
  repo: Repository path to check.
307
333
  """
334
+ from ai.license import require_premium
335
+ gate = require_premium("gov_health")
336
+ if gate:
337
+ return gate
308
338
  from backends.governance_bridge import health
309
339
  return _safe_call(health, repo=repo)
310
340
 
311
341
 
312
342
  @mcp.tool()
313
343
  def delimit_gov_status(repo: str = ".") -> Dict[str, Any]:
314
- """Get current governance status for a repository.
344
+ """Get current governance status for a repository (Pro).
315
345
 
316
346
  Args:
317
347
  repo: Repository path.
318
348
  """
349
+ from ai.license import require_premium
350
+ gate = require_premium("gov_status")
351
+ if gate:
352
+ return gate
319
353
  from backends.governance_bridge import status
320
354
  return _safe_call(status, repo=repo)
321
355
 
322
356
 
323
357
  @mcp.tool()
324
358
  def delimit_gov_policy(repo: str = ".") -> Dict[str, Any]:
325
- """Get governance policy for a repository.
359
+ """Get governance policy for a repository (Pro).
326
360
 
327
361
  Args:
328
362
  repo: Repository path.
329
363
  """
364
+ from ai.license import require_premium
365
+ gate = require_premium("gov_policy")
366
+ if gate:
367
+ return gate
330
368
  from backends.governance_bridge import policy
331
369
  return _safe_call(policy, repo=repo)
332
370
 
333
371
 
334
372
  @mcp.tool()
335
373
  def delimit_gov_evaluate(action: str, context: Optional[Dict[str, Any]] = None, repo: str = ".") -> Dict[str, Any]:
336
- """Evaluate if governance is required for an action (requires governancegate).
374
+ """Evaluate if governance is required for an action (requires governancegate) (Pro).
337
375
 
338
376
  Args:
339
377
  action: The action to evaluate.
340
378
  context: Additional context.
341
379
  repo: Repository path.
342
380
  """
381
+ from ai.license import require_premium
382
+ gate = require_premium("gov_evaluate")
383
+ if gate:
384
+ return gate
343
385
  from backends.governance_bridge import evaluate_trigger
344
386
  return _safe_call(evaluate_trigger, action=action, context=context, repo=repo)
345
387
 
346
388
 
347
389
  @mcp.tool()
348
390
  def delimit_gov_new_task(title: str, scope: str, risk_level: str = "medium", repo: str = ".") -> Dict[str, Any]:
349
- """Create a new governance task (requires governancegate).
391
+ """Create a new governance task (requires governancegate) (Pro).
350
392
 
351
393
  Args:
352
394
  title: Task title.
@@ -354,30 +396,42 @@ def delimit_gov_new_task(title: str, scope: str, risk_level: str = "medium", rep
354
396
  risk_level: Risk level (low/medium/high/critical).
355
397
  repo: Repository path.
356
398
  """
399
+ from ai.license import require_premium
400
+ gate = require_premium("gov_new_task")
401
+ if gate:
402
+ return gate
357
403
  from backends.governance_bridge import new_task
358
404
  return _safe_call(new_task, title=title, scope=scope, risk_level=risk_level, repo=repo)
359
405
 
360
406
 
361
407
  @mcp.tool()
362
408
  def delimit_gov_run(task_id: str, repo: str = ".") -> Dict[str, Any]:
363
- """Run a governance task (requires governancegate).
409
+ """Run a governance task (requires governancegate) (Pro).
364
410
 
365
411
  Args:
366
412
  task_id: Task ID to run.
367
413
  repo: Repository path.
368
414
  """
415
+ from ai.license import require_premium
416
+ gate = require_premium("gov_run")
417
+ if gate:
418
+ return gate
369
419
  from backends.governance_bridge import run_task
370
420
  return _safe_call(run_task, task_id=task_id, repo=repo)
371
421
 
372
422
 
373
423
  @mcp.tool()
374
424
  def delimit_gov_verify(task_id: str, repo: str = ".") -> Dict[str, Any]:
375
- """Verify a governance task (requires governancegate).
425
+ """Verify a governance task (requires governancegate) (Pro).
376
426
 
377
427
  Args:
378
428
  task_id: Task ID to verify.
379
429
  repo: Repository path.
380
430
  """
431
+ from ai.license import require_premium
432
+ gate = require_premium("gov_verify")
433
+ if gate:
434
+ return gate
381
435
  from backends.governance_bridge import verify
382
436
  return _safe_call(verify, task_id=task_id, repo=repo)
383
437
 
@@ -386,36 +440,48 @@ def delimit_gov_verify(task_id: str, repo: str = ".") -> Dict[str, Any]:
386
440
 
387
441
  @mcp.tool()
388
442
  def delimit_memory_search(query: str, limit: int = 10) -> Dict[str, Any]:
389
- """Search conversation memory semantically.
443
+ """Search conversation memory semantically (Pro).
390
444
 
391
445
  Args:
392
446
  query: Natural language search query.
393
447
  limit: Maximum results to return.
394
448
  """
449
+ from ai.license import require_premium
450
+ gate = require_premium("memory_search")
451
+ if gate:
452
+ return gate
395
453
  from backends.memory_bridge import search
396
454
  return _safe_call(search, query=query, limit=limit)
397
455
 
398
456
 
399
457
  @mcp.tool()
400
458
  def delimit_memory_store(content: str, tags: Optional[List[str]] = None, context: Optional[str] = None) -> Dict[str, Any]:
401
- """Store a memory entry for future retrieval.
459
+ """Store a memory entry for future retrieval (Pro).
402
460
 
403
461
  Args:
404
462
  content: The content to remember.
405
463
  tags: Optional categorization tags.
406
464
  context: Optional context about when/why this was stored.
407
465
  """
466
+ from ai.license import require_premium
467
+ gate = require_premium("memory_store")
468
+ if gate:
469
+ return gate
408
470
  from backends.memory_bridge import store
409
471
  return _safe_call(store, content=content, tags=tags, context=context)
410
472
 
411
473
 
412
474
  @mcp.tool()
413
475
  def delimit_memory_recent(limit: int = 5) -> Dict[str, Any]:
414
- """Get recent work summary from memory.
476
+ """Get recent work summary from memory (Pro).
415
477
 
416
478
  Args:
417
479
  limit: Number of recent entries to return.
418
480
  """
481
+ from ai.license import require_premium
482
+ gate = require_premium("memory_recent")
483
+ if gate:
484
+ return gate
419
485
  from backends.memory_bridge import get_recent
420
486
  return _safe_call(get_recent, limit=limit)
421
487
 
@@ -424,25 +490,37 @@ def delimit_memory_recent(limit: int = 5) -> Dict[str, Any]:
424
490
 
425
491
  @mcp.tool()
426
492
  def delimit_vault_search(query: str) -> Dict[str, Any]:
427
- """Search vault entries.
493
+ """Search vault entries (Pro).
428
494
 
429
495
  Args:
430
496
  query: Search query for vault entries.
431
497
  """
498
+ from ai.license import require_premium
499
+ gate = require_premium("vault_search")
500
+ if gate:
501
+ return gate
432
502
  from backends.vault_bridge import search
433
503
  return _safe_call(search, query=query)
434
504
 
435
505
 
436
506
  @mcp.tool()
437
507
  def delimit_vault_health() -> Dict[str, Any]:
438
- """Check vault health status."""
508
+ """Check vault health status (Pro)."""
509
+ from ai.license import require_premium
510
+ gate = require_premium("vault_health")
511
+ if gate:
512
+ return gate
439
513
  from backends.vault_bridge import health
440
514
  return _safe_call(health)
441
515
 
442
516
 
443
517
  @mcp.tool()
444
518
  def delimit_vault_snapshot() -> Dict[str, Any]:
445
- """Get a vault state snapshot."""
519
+ """Get a vault state snapshot (Pro)."""
520
+ from ai.license import require_premium
521
+ gate = require_premium("vault_snapshot")
522
+ if gate:
523
+ return gate
446
524
  from backends.vault_bridge import snapshot
447
525
  return _safe_call(snapshot)
448
526
 
@@ -456,82 +534,106 @@ def delimit_vault_snapshot() -> Dict[str, Any]:
456
534
 
457
535
  @mcp.tool()
458
536
  def delimit_deploy_plan(app: str, env: str, git_ref: Optional[str] = None) -> Dict[str, Any]:
459
- """Plan deployment with build steps.
537
+ """Plan deployment with build steps (Pro).
460
538
 
461
539
  Args:
462
540
  app: Application name.
463
541
  env: Target environment (staging/production).
464
542
  git_ref: Git reference (branch, tag, or SHA).
465
543
  """
544
+ from ai.license import require_premium
545
+ gate = require_premium("deploy_plan")
546
+ if gate:
547
+ return gate
466
548
  from backends.deploy_bridge import plan
467
549
  return _safe_call(plan, app=app, env=env, git_ref=git_ref)
468
550
 
469
551
 
470
552
  @mcp.tool()
471
553
  def delimit_deploy_build(app: str, git_ref: Optional[str] = None) -> Dict[str, Any]:
472
- """Build Docker images with SHA tags.
554
+ """Build Docker images with SHA tags (Pro).
473
555
 
474
556
  Args:
475
557
  app: Application name.
476
558
  git_ref: Git reference.
477
559
  """
560
+ from ai.license import require_premium
561
+ gate = require_premium("deploy_build")
562
+ if gate:
563
+ return gate
478
564
  from backends.deploy_bridge import build
479
565
  return _safe_call(build, app=app, git_ref=git_ref)
480
566
 
481
567
 
482
568
  @mcp.tool()
483
569
  def delimit_deploy_publish(app: str, git_ref: Optional[str] = None) -> Dict[str, Any]:
484
- """Publish images to registry.
570
+ """Publish images to registry (Pro).
485
571
 
486
572
  Args:
487
573
  app: Application name.
488
574
  git_ref: Git reference.
489
575
  """
576
+ from ai.license import require_premium
577
+ gate = require_premium("deploy_publish")
578
+ if gate:
579
+ return gate
490
580
  from backends.deploy_bridge import publish
491
581
  return _safe_call(publish, app=app, git_ref=git_ref)
492
582
 
493
583
 
494
- @mcp.tool()
584
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
495
585
  def delimit_deploy_verify(app: str, env: str, git_ref: Optional[str] = None) -> Dict[str, Any]:
496
- """Verify deployment health (experimental).
586
+ """Verify deployment health (experimental) (Pro).
497
587
 
498
588
  Args:
499
589
  app: Application name.
500
590
  env: Target environment.
501
591
  git_ref: Git reference.
502
592
  """
593
+ from ai.license import require_premium
594
+ gate = require_premium("deploy_verify")
595
+ if gate:
596
+ return gate
503
597
  from backends.deploy_bridge import verify
504
598
  return _safe_call(verify, app=app, env=env, git_ref=git_ref)
505
599
 
506
600
 
507
601
  @mcp.tool()
508
602
  def delimit_deploy_rollback(app: str, env: str, to_sha: Optional[str] = None) -> Dict[str, Any]:
509
- """Rollback to previous SHA.
603
+ """Rollback to previous SHA (Pro).
510
604
 
511
605
  Args:
512
606
  app: Application name.
513
607
  env: Target environment.
514
608
  to_sha: SHA to rollback to.
515
609
  """
610
+ from ai.license import require_premium
611
+ gate = require_premium("deploy_rollback")
612
+ if gate:
613
+ return gate
516
614
  from backends.deploy_bridge import rollback
517
615
  return _safe_call(rollback, app=app, env=env, to_sha=to_sha)
518
616
 
519
617
 
520
618
  @mcp.tool()
521
619
  def delimit_deploy_status(app: str, env: str) -> Dict[str, Any]:
522
- """Get deployment status.
620
+ """Get deployment status (Pro).
523
621
 
524
622
  Args:
525
623
  app: Application name.
526
624
  env: Target environment.
527
625
  """
626
+ from ai.license import require_premium
627
+ gate = require_premium("deploy_status")
628
+ if gate:
629
+ return gate
528
630
  from backends.deploy_bridge import status
529
631
  return _safe_call(status, app=app, env=env)
530
632
 
531
633
 
532
634
  # ─── Intel ──────────────────────────────────────────────────────────────
533
635
 
534
- @mcp.tool()
636
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
535
637
  def delimit_intel_dataset_register(name: str, schema: Dict[str, Any], description: Optional[str] = None) -> Dict[str, Any]:
536
638
  """Register a new dataset with schema (coming soon).
537
639
 
@@ -544,14 +646,14 @@ def delimit_intel_dataset_register(name: str, schema: Dict[str, Any], descriptio
544
646
  return _safe_call(dataset_register, name=name, schema=schema, description=description)
545
647
 
546
648
 
547
- @mcp.tool()
649
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
548
650
  def delimit_intel_dataset_list() -> Dict[str, Any]:
549
651
  """List registered datasets (coming soon)."""
550
652
  from backends.intel_bridge import dataset_list
551
653
  return _safe_call(dataset_list)
552
654
 
553
655
 
554
- @mcp.tool()
656
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
555
657
  def delimit_intel_dataset_freeze(dataset_id: str) -> Dict[str, Any]:
556
658
  """Mark dataset as immutable (coming soon).
557
659
 
@@ -562,7 +664,7 @@ def delimit_intel_dataset_freeze(dataset_id: str) -> Dict[str, Any]:
562
664
  return _safe_call(dataset_freeze, dataset_id=dataset_id)
563
665
 
564
666
 
565
- @mcp.tool()
667
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
566
668
  def delimit_intel_snapshot_ingest(data: Dict[str, Any], provenance: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
567
669
  """Store research snapshot with provenance (coming soon).
568
670
 
@@ -574,7 +676,7 @@ def delimit_intel_snapshot_ingest(data: Dict[str, Any], provenance: Optional[Dic
574
676
  return _safe_call(snapshot_ingest, data=data, provenance=provenance)
575
677
 
576
678
 
577
- @mcp.tool()
679
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
578
680
  def delimit_intel_query(dataset_id: str, query: str, parameters: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
579
681
  """Execute deterministic query on dataset (coming soon).
580
682
 
@@ -618,46 +720,62 @@ def delimit_generate_scaffold(project_type: str, name: str, packages: Optional[L
618
720
 
619
721
  # ─── Repo (RepoDoctor + ConfigSentry) ──────────────────────────────────
620
722
 
621
- @mcp.tool()
723
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
622
724
  def delimit_repo_diagnose(target: str = ".") -> Dict[str, Any]:
623
- """Diagnose repository health issues (experimental).
725
+ """Diagnose repository health issues (experimental) (Pro).
624
726
 
625
727
  Args:
626
728
  target: Repository path.
627
729
  """
730
+ from ai.license import require_premium
731
+ gate = require_premium("repo_diagnose")
732
+ if gate:
733
+ return gate
628
734
  from backends.repo_bridge import diagnose
629
735
  return _safe_call(diagnose, target=target)
630
736
 
631
737
 
632
- @mcp.tool()
738
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
633
739
  def delimit_repo_analyze(target: str = ".") -> Dict[str, Any]:
634
- """Analyze repository structure and quality (experimental).
740
+ """Analyze repository structure and quality (experimental) (Pro).
635
741
 
636
742
  Args:
637
743
  target: Repository path.
638
744
  """
745
+ from ai.license import require_premium
746
+ gate = require_premium("repo_analyze")
747
+ if gate:
748
+ return gate
639
749
  from backends.repo_bridge import analyze
640
750
  return _safe_call(analyze, target=target)
641
751
 
642
752
 
643
- @mcp.tool()
753
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
644
754
  def delimit_repo_config_validate(target: str = ".") -> Dict[str, Any]:
645
- """Validate configuration files (experimental).
755
+ """Validate configuration files (experimental) (Pro).
646
756
 
647
757
  Args:
648
758
  target: Repository or config path.
649
759
  """
760
+ from ai.license import require_premium
761
+ gate = require_premium("repo_config_validate")
762
+ if gate:
763
+ return gate
650
764
  from backends.repo_bridge import config_validate
651
765
  return _safe_call(config_validate, target=target)
652
766
 
653
767
 
654
- @mcp.tool()
768
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
655
769
  def delimit_repo_config_audit(target: str = ".") -> Dict[str, Any]:
656
- """Audit configuration compliance (experimental).
770
+ """Audit configuration compliance (experimental) (Pro).
657
771
 
658
772
  Args:
659
773
  target: Repository or config path.
660
774
  """
775
+ from ai.license import require_premium
776
+ gate = require_premium("repo_config_audit")
777
+ if gate:
778
+ return gate
661
779
  from backends.repo_bridge import config_audit
662
780
  return _safe_call(config_audit, target=target)
663
781
 
@@ -666,22 +784,30 @@ def delimit_repo_config_audit(target: str = ".") -> Dict[str, Any]:
666
784
 
667
785
  @mcp.tool()
668
786
  def delimit_security_scan(target: str = ".") -> Dict[str, Any]:
669
- """Scan for security vulnerabilities.
787
+ """Scan for security vulnerabilities (Pro).
670
788
 
671
789
  Args:
672
790
  target: Repository or file path.
673
791
  """
792
+ from ai.license import require_premium
793
+ gate = require_premium("security_scan")
794
+ if gate:
795
+ return gate
674
796
  from backends.repo_bridge import security_scan
675
797
  return _safe_call(security_scan, target=target)
676
798
 
677
799
 
678
- @mcp.tool()
800
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
679
801
  def delimit_security_audit(target: str = ".") -> Dict[str, Any]:
680
- """Audit security compliance (experimental).
802
+ """Audit security compliance (experimental) (Pro).
681
803
 
682
804
  Args:
683
805
  target: Repository or file path.
684
806
  """
807
+ from ai.license import require_premium
808
+ gate = require_premium("security_audit")
809
+ if gate:
810
+ return gate
685
811
  from backends.repo_bridge import security_audit
686
812
  return _safe_call(security_audit, target=target)
687
813
 
@@ -690,23 +816,31 @@ def delimit_security_audit(target: str = ".") -> Dict[str, Any]:
690
816
 
691
817
  @mcp.tool()
692
818
  def delimit_evidence_collect(target: str = ".") -> Dict[str, Any]:
693
- """Collect evidence artifacts for governance.
819
+ """Collect evidence artifacts for governance (Pro).
694
820
 
695
821
  Args:
696
822
  target: Repository or task path.
697
823
  """
824
+ from ai.license import require_premium
825
+ gate = require_premium("evidence_collect")
826
+ if gate:
827
+ return gate
698
828
  from backends.repo_bridge import evidence_collect
699
829
  return _safe_call(evidence_collect, target=target)
700
830
 
701
831
 
702
832
  @mcp.tool()
703
833
  def delimit_evidence_verify(bundle_id: Optional[str] = None, bundle_path: Optional[str] = None) -> Dict[str, Any]:
704
- """Verify evidence bundle integrity.
834
+ """Verify evidence bundle integrity (Pro).
705
835
 
706
836
  Args:
707
837
  bundle_id: Evidence bundle ID to verify.
708
838
  bundle_path: Path to evidence bundle file.
709
839
  """
840
+ from ai.license import require_premium
841
+ gate = require_premium("evidence_verify")
842
+ if gate:
843
+ return gate
710
844
  from backends.repo_bridge import evidence_verify
711
845
  return _safe_call(evidence_verify, bundle_id=bundle_id, bundle_path=bundle_path)
712
846
 
@@ -718,7 +852,7 @@ def delimit_evidence_verify(bundle_id: Optional[str] = None, bundle_path: Option
718
852
 
719
853
  # ─── ReleasePilot (Governance Primitive) ────────────────────────────────
720
854
 
721
- @mcp.tool()
855
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
722
856
  def delimit_release_plan(environment: str, version: str, repository: str, services: Optional[List[str]] = None) -> Dict[str, Any]:
723
857
  """Create deployment plan with approval gates (experimental).
724
858
 
@@ -732,7 +866,7 @@ def delimit_release_plan(environment: str, version: str, repository: str, servic
732
866
  return _safe_call(release_plan, environment=environment, version=version, repository=repository, services=services)
733
867
 
734
868
 
735
- @mcp.tool()
869
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
736
870
  def delimit_release_validate(environment: str, version: str) -> Dict[str, Any]:
737
871
  """Validate release readiness (experimental).
738
872
 
@@ -744,7 +878,7 @@ def delimit_release_validate(environment: str, version: str) -> Dict[str, Any]:
744
878
  return _safe_call(release_validate, environment=environment, version=version)
745
879
 
746
880
 
747
- @mcp.tool()
881
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
748
882
  def delimit_release_status(environment: str) -> Dict[str, Any]:
749
883
  """Check deployment status (experimental).
750
884
 
@@ -755,7 +889,7 @@ def delimit_release_status(environment: str) -> Dict[str, Any]:
755
889
  return _safe_call(release_status, environment=environment)
756
890
 
757
891
 
758
- @mcp.tool()
892
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
759
893
  def delimit_release_rollback(environment: str, version: str, to_version: str) -> Dict[str, Any]:
760
894
  """Rollback deployment to previous version (experimental).
761
895
 
@@ -768,7 +902,7 @@ def delimit_release_rollback(environment: str, version: str, to_version: str) ->
768
902
  return _safe_call(release_rollback, environment=environment, version=version, to_version=to_version)
769
903
 
770
904
 
771
- @mcp.tool()
905
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
772
906
  def delimit_release_history(environment: str, limit: int = 10) -> Dict[str, Any]:
773
907
  """Show release history (experimental).
774
908
 
@@ -793,7 +927,7 @@ def delimit_cost_analyze(target: str = ".") -> Dict[str, Any]:
793
927
  return _safe_call(cost_analyze, target=target)
794
928
 
795
929
 
796
- @mcp.tool()
930
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
797
931
  def delimit_cost_optimize(target: str = ".") -> Dict[str, Any]:
798
932
  """Generate cost optimization recommendations (experimental).
799
933
 
@@ -804,7 +938,7 @@ def delimit_cost_optimize(target: str = ".") -> Dict[str, Any]:
804
938
  return _safe_call(cost_optimize, target=target)
805
939
 
806
940
 
807
- @mcp.tool()
941
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
808
942
  def delimit_cost_alert(action: str = "list") -> Dict[str, Any]:
809
943
  """Manage cost alerts and notifications (experimental).
810
944
 
@@ -828,7 +962,7 @@ def delimit_data_validate(target: str = ".") -> Dict[str, Any]:
828
962
  return _safe_call(data_validate, target=target)
829
963
 
830
964
 
831
- @mcp.tool()
965
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
832
966
  def delimit_data_migrate(target: str = ".") -> Dict[str, Any]:
833
967
  """Execute data migration (experimental, plan-only by default).
834
968
 
@@ -839,7 +973,7 @@ def delimit_data_migrate(target: str = ".") -> Dict[str, Any]:
839
973
  return _safe_call(data_migrate, target=target)
840
974
 
841
975
 
842
- @mcp.tool()
976
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
843
977
  def delimit_data_backup(target: str = ".") -> Dict[str, Any]:
844
978
  """Create data backups (experimental).
845
979
 
@@ -852,7 +986,7 @@ def delimit_data_backup(target: str = ".") -> Dict[str, Any]:
852
986
 
853
987
  # ─── ObservabilityOps (Internal OS) ────────────────────────────────────
854
988
 
855
- @mcp.tool()
989
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
856
990
  def delimit_obs_metrics(query: str, time_range: str = "1h", source: Optional[str] = None) -> Dict[str, Any]:
857
991
  """Query and analyze metrics (experimental).
858
992
 
@@ -865,7 +999,7 @@ def delimit_obs_metrics(query: str, time_range: str = "1h", source: Optional[str
865
999
  return _safe_call(obs_metrics, query=query, time_range=time_range, source=source)
866
1000
 
867
1001
 
868
- @mcp.tool()
1002
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
869
1003
  def delimit_obs_logs(query: str, time_range: str = "1h", source: Optional[str] = None) -> Dict[str, Any]:
870
1004
  """Query and search logs (experimental).
871
1005
 
@@ -878,7 +1012,7 @@ def delimit_obs_logs(query: str, time_range: str = "1h", source: Optional[str] =
878
1012
  return _safe_call(obs_logs, query=query, time_range=time_range, source=source)
879
1013
 
880
1014
 
881
- @mcp.tool()
1015
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
882
1016
  def delimit_obs_alerts(action: str, alert_rule: Optional[Dict[str, Any]] = None, rule_id: Optional[str] = None) -> Dict[str, Any]:
883
1017
  """Manage alerting rules (experimental).
884
1018
 
@@ -891,7 +1025,7 @@ def delimit_obs_alerts(action: str, alert_rule: Optional[Dict[str, Any]] = None,
891
1025
  return _safe_call(obs_alerts, action=action, alert_rule=alert_rule, rule_id=rule_id)
892
1026
 
893
1027
 
894
- @mcp.tool()
1028
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
895
1029
  def delimit_obs_status() -> Dict[str, Any]:
896
1030
  """Get observability system status (experimental)."""
897
1031
  from backends.ops_bridge import obs_status
@@ -900,7 +1034,7 @@ def delimit_obs_status() -> Dict[str, Any]:
900
1034
 
901
1035
  # ─── DesignSystem (UI Tooling) ──────────────────────────────────────────
902
1036
 
903
- @mcp.tool()
1037
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
904
1038
  def delimit_design_extract_tokens(figma_file_key: str, token_types: Optional[List[str]] = None) -> Dict[str, Any]:
905
1039
  """Extract design tokens from Figma (coming soon).
906
1040
 
@@ -912,7 +1046,7 @@ def delimit_design_extract_tokens(figma_file_key: str, token_types: Optional[Lis
912
1046
  return _safe_call(design_extract_tokens, figma_file_key=figma_file_key, token_types=token_types)
913
1047
 
914
1048
 
915
- @mcp.tool()
1049
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
916
1050
  def delimit_design_generate_component(component_name: str, figma_node_id: Optional[str] = None, output_path: Optional[str] = None) -> Dict[str, Any]:
917
1051
  """Generate Next.js component from Figma design (coming soon).
918
1052
 
@@ -925,7 +1059,7 @@ def delimit_design_generate_component(component_name: str, figma_node_id: Option
925
1059
  return _safe_call(design_generate_component, component_name=component_name, figma_node_id=figma_node_id, output_path=output_path)
926
1060
 
927
1061
 
928
- @mcp.tool()
1062
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
929
1063
  def delimit_design_generate_tailwind(figma_file_key: str, output_path: Optional[str] = None) -> Dict[str, Any]:
930
1064
  """Generate Tailwind config from Figma design tokens (coming soon).
931
1065
 
@@ -937,7 +1071,7 @@ def delimit_design_generate_tailwind(figma_file_key: str, output_path: Optional[
937
1071
  return _safe_call(design_generate_tailwind, figma_file_key=figma_file_key, output_path=output_path)
938
1072
 
939
1073
 
940
- @mcp.tool()
1074
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
941
1075
  def delimit_design_validate_responsive(project_path: str, check_types: Optional[List[str]] = None) -> Dict[str, Any]:
942
1076
  """Validate responsive design patterns (coming soon).
943
1077
 
@@ -949,7 +1083,7 @@ def delimit_design_validate_responsive(project_path: str, check_types: Optional[
949
1083
  return _safe_call(design_validate_responsive, project_path=project_path, check_types=check_types)
950
1084
 
951
1085
 
952
- @mcp.tool()
1086
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
953
1087
  def delimit_design_component_library(project_path: str, output_format: str = "json") -> Dict[str, Any]:
954
1088
  """Generate component library documentation (coming soon).
955
1089
 
@@ -963,7 +1097,7 @@ def delimit_design_component_library(project_path: str, output_format: str = "js
963
1097
 
964
1098
  # ─── Storybook (UI Tooling + Visual Regression) ────────────────────────
965
1099
 
966
- @mcp.tool()
1100
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
967
1101
  def delimit_story_generate(component_path: str, story_name: Optional[str] = None, variants: Optional[List[str]] = None) -> Dict[str, Any]:
968
1102
  """Generate Storybook story for a component (coming soon).
969
1103
 
@@ -976,7 +1110,7 @@ def delimit_story_generate(component_path: str, story_name: Optional[str] = None
976
1110
  return _safe_call(story_generate, component_path=component_path, story_name=story_name, variants=variants)
977
1111
 
978
1112
 
979
- @mcp.tool()
1113
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
980
1114
  def delimit_story_visual_test(url: str, project_path: Optional[str] = None, threshold: float = 0.05) -> Dict[str, Any]:
981
1115
  """Run visual regression test with Playwright screenshots (coming soon).
982
1116
 
@@ -989,7 +1123,7 @@ def delimit_story_visual_test(url: str, project_path: Optional[str] = None, thre
989
1123
  return _safe_call(story_visual_test, url=url, project_path=project_path, threshold=threshold)
990
1124
 
991
1125
 
992
- @mcp.tool()
1126
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
993
1127
  def delimit_story_build(project_path: str, output_dir: Optional[str] = None) -> Dict[str, Any]:
994
1128
  """Build Storybook static site (coming soon).
995
1129
 
@@ -1001,7 +1135,7 @@ def delimit_story_build(project_path: str, output_dir: Optional[str] = None) ->
1001
1135
  return _safe_call(story_build, project_path=project_path, output_dir=output_dir)
1002
1136
 
1003
1137
 
1004
- @mcp.tool()
1138
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
1005
1139
  def delimit_story_accessibility(project_path: str, standards: str = "WCAG2AA") -> Dict[str, Any]:
1006
1140
  """Run WCAG accessibility tests on components (coming soon).
1007
1141
 
@@ -1015,46 +1149,58 @@ def delimit_story_accessibility(project_path: str, standards: str = "WCAG2AA") -
1015
1149
 
1016
1150
  # ─── TestSmith (Testing) ───────────────────────────────────────────────
1017
1151
 
1018
- @mcp.tool()
1152
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
1019
1153
  def delimit_test_generate(project_path: str, source_files: Optional[List[str]] = None, framework: str = "jest") -> Dict[str, Any]:
1020
- """Generate tests for source code (experimental).
1154
+ """Generate tests for source code (experimental) (Pro).
1021
1155
 
1022
1156
  Args:
1023
1157
  project_path: Project path.
1024
1158
  source_files: Specific files to generate tests for.
1025
1159
  framework: Test framework (jest/pytest/vitest).
1026
1160
  """
1161
+ from ai.license import require_premium
1162
+ gate = require_premium("test_generate")
1163
+ if gate:
1164
+ return gate
1027
1165
  from backends.ui_bridge import test_generate
1028
1166
  return _safe_call(test_generate, project_path=project_path, source_files=source_files, framework=framework)
1029
1167
 
1030
1168
 
1031
- @mcp.tool()
1169
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
1032
1170
  def delimit_test_coverage(project_path: str, threshold: int = 80) -> Dict[str, Any]:
1033
- """Analyze test coverage (experimental).
1171
+ """Analyze test coverage (experimental) (Pro).
1034
1172
 
1035
1173
  Args:
1036
1174
  project_path: Project path.
1037
1175
  threshold: Coverage threshold percentage.
1038
1176
  """
1177
+ from ai.license import require_premium
1178
+ gate = require_premium("test_coverage")
1179
+ if gate:
1180
+ return gate
1039
1181
  from backends.ui_bridge import test_coverage
1040
1182
  return _safe_call(test_coverage, project_path=project_path, threshold=threshold)
1041
1183
 
1042
1184
 
1043
1185
  @mcp.tool()
1044
1186
  def delimit_test_smoke(project_path: str, test_suite: Optional[str] = None) -> Dict[str, Any]:
1045
- """Run smoke tests (experimental).
1187
+ """Run smoke tests (experimental) (Pro).
1046
1188
 
1047
1189
  Args:
1048
1190
  project_path: Project path.
1049
1191
  test_suite: Specific test suite to run.
1050
1192
  """
1193
+ from ai.license import require_premium
1194
+ gate = require_premium("test_smoke")
1195
+ if gate:
1196
+ return gate
1051
1197
  from backends.ui_bridge import test_smoke
1052
1198
  return _safe_call(test_smoke, project_path=project_path, test_suite=test_suite)
1053
1199
 
1054
1200
 
1055
1201
  # ─── Docs ───────────────────────────────────────────────────────────────
1056
1202
 
1057
- @mcp.tool()
1203
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
1058
1204
  def delimit_docs_generate(target: str = ".") -> Dict[str, Any]:
1059
1205
  """Generate documentation for a project (experimental).
1060
1206
 
@@ -1065,7 +1211,7 @@ def delimit_docs_generate(target: str = ".") -> Dict[str, Any]:
1065
1211
  return _safe_call(docs_generate, target=target)
1066
1212
 
1067
1213
 
1068
- @mcp.tool()
1214
+ @_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
1069
1215
  def delimit_docs_validate(target: str = ".") -> Dict[str, Any]:
1070
1216
  """Validate documentation quality and completeness (experimental).
1071
1217
 
@@ -1249,6 +1395,138 @@ def delimit_version() -> Dict[str, Any]:
1249
1395
  }
1250
1396
 
1251
1397
 
1398
+ # ═══════════════════════════════════════════════════════════════════════
1399
+ # META TOOLS (help, diagnose)
1400
+ # ═══════════════════════════════════════════════════════════════════════
1401
+
1402
+
1403
+ TOOL_HELP = {
1404
+ "init": {"desc": "Initialize governance for a project", "example": "delimit_init(project_path='.', preset='default')", "params": "project_path (str), preset (strict|default|relaxed)"},
1405
+ "lint": {"desc": "Diff two OpenAPI specs and check policy violations", "example": "delimit_lint(old_spec='base.yaml', new_spec='new.yaml')", "params": "old_spec (path), new_spec (path), policy_file (optional path)"},
1406
+ "diff": {"desc": "Pure diff between two specs — no policy, just changes", "example": "delimit_diff(old_spec='base.yaml', new_spec='new.yaml')", "params": "old_spec (path), new_spec (path)"},
1407
+ "semver": {"desc": "Classify the semver bump for a spec change", "example": "delimit_semver(old_spec='base.yaml', new_spec='new.yaml', current_version='1.2.3')", "params": "old_spec, new_spec, current_version (optional)"},
1408
+ "explain": {"desc": "Human-readable explanation of API changes", "example": "delimit_explain(old_spec='base.yaml', new_spec='new.yaml', template='pr_comment')", "params": "old_spec, new_spec, template (developer|pr_comment|migration|changelog)"},
1409
+ "gov_health": {"desc": "Check governance status — is the project initialized?", "example": "delimit_gov_health(repo='.')", "params": "repo (path, default '.')"},
1410
+ "test_coverage": {"desc": "Measure test coverage for a project", "example": "delimit_test_coverage(project_path='.', threshold=80)", "params": "project_path, threshold (default 80)"},
1411
+ "repo_analyze": {"desc": "Full repo health report — code quality, security, dependencies", "example": "delimit_repo_analyze(target='.')", "params": "target (path)"},
1412
+ "zero_spec": {"desc": "Extract OpenAPI spec from source code (FastAPI, Express, NestJS)", "example": "delimit_zero_spec(project_dir='.')", "params": "project_dir (path)"},
1413
+ "sensor_github_issue": {"desc": "Monitor a GitHub issue for new comments", "example": "delimit_sensor_github_issue(repo='owner/repo', issue_number=123)", "params": "repo (owner/name), issue_number (int)"},
1414
+ }
1415
+
1416
+
1417
+ @mcp.tool()
1418
+ def delimit_help(tool_name: str = "") -> Dict[str, Any]:
1419
+ """Get help for a Delimit tool — what it does, parameters, and examples.
1420
+
1421
+ Args:
1422
+ tool_name: Tool name (e.g. 'lint', 'gov_health'). Leave empty for overview.
1423
+ """
1424
+ if not tool_name:
1425
+ return {
1426
+ "message": "Delimit has 77 tools. Here are the most useful ones to start with:",
1427
+ "essential_tools": {k: v["desc"] for k, v in TOOL_HELP.items()},
1428
+ "tip": "Run delimit_help(tool_name='lint') for detailed help on a specific tool.",
1429
+ "all_tools": "Run delimit_version() for the complete list.",
1430
+ }
1431
+
1432
+ # Normalize name
1433
+ clean = tool_name.replace("delimit_", "").replace("mcp__delimit__delimit_", "")
1434
+ info = TOOL_HELP.get(clean)
1435
+ if info:
1436
+ return {"tool": clean, **info}
1437
+ return {"error": f"No help for '{tool_name}'. Try: {', '.join(TOOL_HELP.keys())}"}
1438
+
1439
+
1440
+ @mcp.tool()
1441
+ def delimit_diagnose(project_path: str = ".") -> Dict[str, Any]:
1442
+ """Diagnose your Delimit setup — check environment, config, and tool status.
1443
+
1444
+ Universal 'get me unstuck' command. Checks Python, MCP config, governance state,
1445
+ and reports any issues with suggested fixes.
1446
+
1447
+ Args:
1448
+ project_path: Project to diagnose.
1449
+ """
1450
+ issues = []
1451
+ checks = {}
1452
+
1453
+ # Python version
1454
+ import sys
1455
+ checks["python"] = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
1456
+
1457
+ # Check .delimit/ dir
1458
+ p = Path(project_path).resolve()
1459
+ delimit_dir = p / ".delimit"
1460
+ policies = delimit_dir / "policies.yml"
1461
+ ledger = delimit_dir / "ledger" / "events.jsonl"
1462
+
1463
+ checks["project_path"] = str(p)
1464
+ checks["delimit_initialized"] = delimit_dir.is_dir()
1465
+ checks["policies_file"] = policies.is_file()
1466
+ checks["ledger_file"] = ledger.is_file()
1467
+
1468
+ if not delimit_dir.is_dir():
1469
+ issues.append({
1470
+ "issue": "Project not initialized",
1471
+ "fix": "Run delimit_init(project_path='.') or say 'initialize governance for this project'",
1472
+ })
1473
+ elif not policies.is_file():
1474
+ issues.append({
1475
+ "issue": "Missing policies.yml",
1476
+ "fix": "Run delimit_init(project_path='.', preset='default')",
1477
+ })
1478
+
1479
+ # Check key dependencies
1480
+ for pkg in ["yaml", "pydantic", "packaging"]:
1481
+ try:
1482
+ __import__(pkg)
1483
+ checks[f"dep_{pkg}"] = True
1484
+ except ImportError:
1485
+ checks[f"dep_{pkg}"] = False
1486
+ issues.append({"issue": f"Missing Python package: {pkg}", "fix": f"pip install {pkg}"})
1487
+
1488
+ # Check fastmcp
1489
+ try:
1490
+ import fastmcp
1491
+ checks["fastmcp"] = True
1492
+ except ImportError:
1493
+ checks["fastmcp"] = False
1494
+ issues.append({"issue": "FastMCP not installed", "fix": "pip install fastmcp"})
1495
+
1496
+ # Summary
1497
+ status = "healthy" if not issues else "issues_found"
1498
+ return {
1499
+ "status": status,
1500
+ "checks": checks,
1501
+ "issues": issues,
1502
+ "issue_count": len(issues),
1503
+ "tip": "If everything looks good but tools aren't working, try restarting Claude Code.",
1504
+ }
1505
+
1506
+
1507
+ # ═══════════════════════════════════════════════════════════════════════
1508
+ # LICENSE
1509
+ # ═══════════════════════════════════════════════════════════════════════
1510
+
1511
+
1512
+ @mcp.tool()
1513
+ def delimit_activate(license_key: str) -> Dict[str, Any]:
1514
+ """Activate a Delimit Pro license key.
1515
+
1516
+ Args:
1517
+ license_key: The license key to activate (e.g. DELIMIT-XXXX-XXXX-XXXX).
1518
+ """
1519
+ from ai.license import activate_license
1520
+ return activate_license(license_key)
1521
+
1522
+
1523
+ @mcp.tool()
1524
+ def delimit_license_status() -> Dict[str, Any]:
1525
+ """Check current Delimit license status -- tier, validity, and expiry."""
1526
+ from ai.license import get_license
1527
+ return get_license()
1528
+
1529
+
1252
1530
  # ═══════════════════════════════════════════════════════════════════════
1253
1531
  # ENTRY POINT
1254
1532
  # ═══════════════════════════════════════════════════════════════════════
@@ -1261,3 +1539,9 @@ async def run_mcp_server(server, server_name="delimit"):
1261
1539
  if __name__ == "__main__":
1262
1540
  import asyncio
1263
1541
  asyncio.run(run_mcp_server(mcp))
1542
+
1543
+
1544
+ def main():
1545
+ """Entry point for `delimit-mcp` console script."""
1546
+ import asyncio
1547
+ asyncio.run(run_mcp_server(mcp))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "delimit-cli",
3
- "version": "3.1.1",
3
+ "version": "3.3.0",
4
4
  "description": "AI agent guardrails for developers. Install governance tools into Claude Code with one command.",
5
5
  "main": "index.js",
6
6
  "bin": {
package/server.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.delimit-ai/delimit",
4
+ "title": "Delimit",
5
+ "description": "API governance for AI agents. Detect breaking OpenAPI changes and enforce policies in CI.",
6
+ "repository": {
7
+ "url": "https://github.com/delimit-ai/delimit",
8
+ "source": "github"
9
+ },
10
+ "version": "3.2.0",
11
+ "websiteUrl": "https://delimit.ai",
12
+ "packages": [
13
+ {
14
+ "registryType": "npm",
15
+ "identifier": "delimit-cli",
16
+ "version": "3.2.0",
17
+ "transport": {
18
+ "type": "stdio"
19
+ }
20
+ }
21
+ ]
22
+ }