delimit-cli 3.15.14 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -1
- package/gateway/ai/license_core.py +2 -1
- package/gateway/ai/notify.py +12 -12
- package/gateway/ai/server.py +49 -332
- package/gateway/ai/swarm.py +2 -2
- package/gateway/core/contract_ledger.py +1 -1
- package/gateway/core/dependency_graph.py +1 -1
- package/gateway/core/dependency_manifest.py +1 -1
- package/gateway/core/event_backbone.py +2 -2
- package/gateway/core/event_schema.py +1 -1
- package/gateway/core/impact_analyzer.py +1 -1
- package/package.json +4 -1
- package/scripts/crosspost_devto.py +0 -304
- package/scripts/security-check.sh +0 -66
- package/scripts/weekly-tweet.py +0 -191
package/gateway/ai/server.py
CHANGED
|
@@ -19,9 +19,8 @@ All tools follow the Adapter Boundary Contract v1.0:
|
|
|
19
19
|
- Stateless between calls
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
# ──
|
|
22
|
+
# ── Output Quality Rules ──────────────────────────────────────────────
|
|
23
23
|
# Applies to ALL outward-facing text generated by any tool in this server.
|
|
24
|
-
# Full doctrine: /home/delimit/delimit-private/style/FOUNDER_VOICE_DOCTRINE.md
|
|
25
24
|
#
|
|
26
25
|
# Core: serious builder/operator, not a marketer. Credibility over persuasion.
|
|
27
26
|
# Truth over excitement. Concrete mechanisms, not vague benefits.
|
|
@@ -34,14 +33,7 @@ All tools follow the Adapter Boundary Contract v1.0:
|
|
|
34
33
|
# attached to decisions? Would this still read well a year from now?
|
|
35
34
|
# ────────────────────────────────────────────────────────────────────────
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
"revolutionary", "game-changing", "world-class", "cutting-edge",
|
|
39
|
-
"best-in-class", "seamless", "unlock", "supercharge", "next-generation",
|
|
40
|
-
"magical", "delightful", "effortless", "frictionless", "transformative",
|
|
41
|
-
"paradigm shift", "visionary", "category-defining", "industry-leading",
|
|
42
|
-
"innovative", "reimagine", "future of", "changing the game",
|
|
43
|
-
"empowering teams", "built for everyone",
|
|
44
|
-
}
|
|
36
|
+
# Output quality rules loaded from user config at runtime.
|
|
45
37
|
|
|
46
38
|
import json
|
|
47
39
|
import logging
|
|
@@ -60,13 +52,13 @@ from fastmcp import FastMCP
|
|
|
60
52
|
logger = logging.getLogger("delimit.ai")
|
|
61
53
|
|
|
62
54
|
# ═══════════════════════════════════════════════════════════════════════
|
|
63
|
-
#
|
|
55
|
+
# TASK: Agent Identity — session tracking for every tool call
|
|
64
56
|
# ═══════════════════════════════════════════════════════════════════════
|
|
65
57
|
|
|
66
58
|
_current_session_id = os.environ.get("DELIMIT_SESSION_ID", "")
|
|
67
59
|
|
|
68
60
|
# ═══════════════════════════════════════════════════════════════════════
|
|
69
|
-
#
|
|
61
|
+
# TASK: Distributed Tracing — trace ID + span counter for every call
|
|
70
62
|
# ═══════════════════════════════════════════════════════════════════════
|
|
71
63
|
|
|
72
64
|
_trace_id = os.environ.get("DELIMIT_TRACE_ID", str(uuid.uuid4())[:8])
|
|
@@ -251,7 +243,7 @@ def _coerce_dict_arg(
|
|
|
251
243
|
|
|
252
244
|
|
|
253
245
|
# ═══════════════════════════════════════════════════════════════════════
|
|
254
|
-
#
|
|
246
|
+
# TASK: Risk Classification for Approval Gates
|
|
255
247
|
# ═══════════════════════════════════════════════════════════════════════
|
|
256
248
|
|
|
257
249
|
HIGH_RISK_TOOLS = {
|
|
@@ -275,7 +267,7 @@ def _classify_risk(tool_name: str) -> str:
|
|
|
275
267
|
|
|
276
268
|
|
|
277
269
|
# ═══════════════════════════════════════════════════════════════════════
|
|
278
|
-
#
|
|
270
|
+
# TASK: Policy Kernel — Inline Enforcement
|
|
279
271
|
# Checks policy BEFORE/AFTER tool execution to block high-risk actions.
|
|
280
272
|
# ═══════════════════════════════════════════════════════════════════════
|
|
281
273
|
|
|
@@ -327,7 +319,7 @@ def _check_policy_gate(tool_name: str, kwargs: dict) -> Optional[Dict]:
|
|
|
327
319
|
"action": "Switch to guarded mode or request approval",
|
|
328
320
|
}
|
|
329
321
|
|
|
330
|
-
#
|
|
322
|
+
# TASK: Deploy gating — block deploys when unresolved critical findings exist
|
|
331
323
|
DEPLOY_TOOLS = {"deploy_publish", "deploy_npm", "deploy_site", "deploy_build"}
|
|
332
324
|
clean = tool_name.replace("delimit_", "")
|
|
333
325
|
if clean in DEPLOY_TOOLS and mode != "advisory":
|
|
@@ -455,7 +447,7 @@ mcp.description = (
|
|
|
455
447
|
|
|
456
448
|
VERSION = "3.2.1"
|
|
457
449
|
|
|
458
|
-
#
|
|
450
|
+
# TASK + Consensus 118/119/120: Tool visibility tiers.
|
|
459
451
|
# Tier cascade: SHOW_EXPERIMENTAL > SHOW_INTERNAL > SHOW_OPS > public (always visible).
|
|
460
452
|
# Set DELIMIT_SHOW_INTERNAL=1 to see all tiers (founder workflow).
|
|
461
453
|
SHOW_EXPERIMENTAL = os.environ.get("DELIMIT_SHOW_EXPERIMENTAL", "") == "1"
|
|
@@ -840,7 +832,7 @@ NEXT_STEPS_REGISTRY: Dict[str, List[Dict[str, Any]]] = {
|
|
|
840
832
|
],
|
|
841
833
|
# --- Sensing ---
|
|
842
834
|
"sensor_github_issue": [],
|
|
843
|
-
# --- Context Filesystem (
|
|
835
|
+
# --- Context Filesystem (TASK) ---
|
|
844
836
|
"context_init": [
|
|
845
837
|
{"tool": "delimit_context_write", "reason": "Write an artifact to the new context", "suggested_args": {}, "is_premium": False},
|
|
846
838
|
],
|
|
@@ -966,10 +958,10 @@ def _emit_event(tool_name: str, result: Dict[str, Any]) -> None:
|
|
|
966
958
|
except Exception:
|
|
967
959
|
pass # Never let cloud sync break tool execution
|
|
968
960
|
|
|
969
|
-
#
|
|
961
|
+
# TASK: Webhook notifications for governance events
|
|
970
962
|
_fire_webhook(event)
|
|
971
963
|
|
|
972
|
-
#
|
|
964
|
+
# TASK: Write trace span for session replay
|
|
973
965
|
try:
|
|
974
966
|
from ai.tracing import start_span, end_span
|
|
975
967
|
span = start_span(_trace_id, tool_name, args={"tool": tool_name})
|
|
@@ -981,7 +973,7 @@ def _emit_event(tool_name: str, result: Dict[str, Any]) -> None:
|
|
|
981
973
|
except Exception:
|
|
982
974
|
pass # Tracing is best-effort
|
|
983
975
|
|
|
984
|
-
#
|
|
976
|
+
# TASK: Write to agent_actions log for session drill-down
|
|
985
977
|
if session_info["session_id"]:
|
|
986
978
|
actions_dir = Path.home() / ".delimit" / "agent_actions"
|
|
987
979
|
actions_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -1185,7 +1177,7 @@ def _with_next_steps(tool_name: str, result: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
1185
1177
|
except Exception as e:
|
|
1186
1178
|
logger.warning("Inbox daemon auto-start failed: %s", e)
|
|
1187
1179
|
|
|
1188
|
-
#
|
|
1180
|
+
# TASK: Auto-register tool schemas with toolcard cache on first call
|
|
1189
1181
|
global _toolcard_cache_autoregistered
|
|
1190
1182
|
if not _toolcard_cache_autoregistered:
|
|
1191
1183
|
_toolcard_cache_autoregistered = True
|
|
@@ -1203,7 +1195,7 @@ def _with_next_steps(tool_name: str, result: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
1203
1195
|
except Exception as e:
|
|
1204
1196
|
logger.warning("Toolcard cache auto-register failed: %s", e)
|
|
1205
1197
|
|
|
1206
|
-
#
|
|
1198
|
+
# TASK: Track every tool call for session analytics
|
|
1207
1199
|
try:
|
|
1208
1200
|
from ai.toolcard_cache import get_cache as _get_tc
|
|
1209
1201
|
_get_tc().record_call(tool_name)
|
|
@@ -1216,282 +1208,7 @@ def _with_next_steps(tool_name: str, result: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
1216
1208
|
result.get("explanation", ""), result.get("changelog", ""),
|
|
1217
1209
|
result.get("content", "")]
|
|
1218
1210
|
_all_text = " ".join(str(f) for f in _text_fields if f).lower()
|
|
1219
|
-
|
|
1220
|
-
if _found_hype:
|
|
1221
|
-
result.setdefault("voice_warnings", []).append(
|
|
1222
|
-
f"VOICE DOCTRINE: Hype words detected: {', '.join(_found_hype)}. "
|
|
1223
|
-
f"Rewrite with concrete mechanisms, not vague benefits."
|
|
1224
|
-
)
|
|
1225
|
-
|
|
1226
|
-
# Emit event for real-time dashboard
|
|
1227
|
-
_emit_event(tool_name, result)
|
|
1228
|
-
|
|
1229
|
-
# STR-052: Policy kernel inline enforcement
|
|
1230
|
-
policy_gate = _check_policy_gate(tool_name, result if isinstance(result, dict) else {})
|
|
1231
|
-
if policy_gate:
|
|
1232
|
-
policy_gate["original_result"] = result
|
|
1233
|
-
policy_gate["governance"] = {"action": "policy_blocked", "reason": policy_gate["reason"]}
|
|
1234
|
-
return policy_gate
|
|
1235
|
-
|
|
1236
|
-
# LED-195: Prompt injection detection on tool inputs
|
|
1237
|
-
if isinstance(result, dict):
|
|
1238
|
-
injection = _detect_prompt_injection(result, tool_name)
|
|
1239
|
-
if injection:
|
|
1240
|
-
result["_security_warning"] = injection
|
|
1241
|
-
|
|
1242
|
-
# Pro license gate — blocks execution for premium tools
|
|
1243
|
-
full_name = f"delimit_{tool_name}" if not tool_name.startswith("delimit_") else tool_name
|
|
1244
|
-
gate = _check_pro(full_name)
|
|
1245
|
-
if gate:
|
|
1246
|
-
return gate
|
|
1247
|
-
|
|
1248
|
-
# Route through governance loop
|
|
1249
|
-
try:
|
|
1250
|
-
from ai.governance import govern
|
|
1251
|
-
return govern(tool_name, result)
|
|
1252
|
-
except Exception:
|
|
1253
|
-
# Fallback: just add next_steps from registry
|
|
1254
|
-
steps = NEXT_STEPS_REGISTRY.get(tool_name, [])
|
|
1255
|
-
result["next_steps"] = steps
|
|
1256
|
-
return result
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
# ═══════════════════════════════════════════════════════════════════════
|
|
1260
|
-
# TIER 1: CORE — API Lint Engine
|
|
1261
|
-
# ═══════════════════════════════════════════════════════════════════════
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
@mcp.tool()
|
|
1265
|
-
def delimit_lint(old_spec: str, new_spec: str, policy_file: Optional[str] = None) -> Dict[str, Any]:
|
|
1266
|
-
"""Lint two OpenAPI specs for breaking changes and policy violations.
|
|
1267
|
-
Primary CI integration point. Combines diff + policy into pass/fail.
|
|
1268
|
-
Auto-chains: semver classification, governance evaluation on breaking changes.
|
|
1269
|
-
|
|
1270
|
-
Args:
|
|
1271
|
-
old_spec: Path to the old (baseline) OpenAPI spec file.
|
|
1272
|
-
new_spec: Path to the new (proposed) OpenAPI spec file.
|
|
1273
|
-
policy_file: Optional path to a .delimit/policies.yml file.
|
|
1274
|
-
"""
|
|
1275
|
-
from backends.gateway_core import run_lint, run_semver
|
|
1276
|
-
|
|
1277
|
-
# Step 1: Core lint
|
|
1278
|
-
lint_result = _safe_call(run_lint, old_spec=old_spec, new_spec=new_spec, policy_file=policy_file)
|
|
1279
|
-
chain: Dict[str, Any] = {"id": "lint_chain", "steps": []}
|
|
1280
|
-
|
|
1281
|
-
if lint_result.get("error"):
|
|
1282
|
-
lint_result["chain"] = chain
|
|
1283
|
-
return _with_next_steps("lint", lint_result)
|
|
1284
|
-
|
|
1285
|
-
# Step 2: Auto-classify semver bump (non-blocking on failure)
|
|
1286
|
-
semver_result = _chain_call("lint", "semver", run_semver,
|
|
1287
|
-
required=False, old_spec=old_spec, new_spec=new_spec)
|
|
1288
|
-
chain["steps"].append({"step": "semver", "ok": not _chain_is_error(semver_result)})
|
|
1289
|
-
lint_result["semver"] = semver_result
|
|
1290
|
-
|
|
1291
|
-
if _chain_is_error(semver_result):
|
|
1292
|
-
chain["status"] = "semver_failed_nonfatal"
|
|
1293
|
-
lint_result["chain"] = chain
|
|
1294
|
-
return _with_next_steps("lint", lint_result)
|
|
1295
|
-
|
|
1296
|
-
bump = str(semver_result.get("bump", "")).upper()
|
|
1297
|
-
|
|
1298
|
-
# Step 2b: Impact-based notification routing (LED-233, non-blocking)
|
|
1299
|
-
try:
|
|
1300
|
-
from ai.notify import route_by_impact
|
|
1301
|
-
all_changes = lint_result.get("all_changes", lint_result.get("violations", []))
|
|
1302
|
-
if all_changes:
|
|
1303
|
-
routing_result = route_by_impact(all_changes, dry_run=False)
|
|
1304
|
-
chain["steps"].append({"step": "impact_routing", "ok": True})
|
|
1305
|
-
lint_result["impact_routing"] = routing_result
|
|
1306
|
-
except Exception as e:
|
|
1307
|
-
logger.debug("Impact routing non-fatal error: %s", e)
|
|
1308
|
-
chain["steps"].append({"step": "impact_routing", "ok": False, "error": str(e)})
|
|
1309
|
-
|
|
1310
|
-
if bump != "MAJOR":
|
|
1311
|
-
chain["status"] = f"complete_{bump.lower() or 'none'}"
|
|
1312
|
-
lint_result["chain"] = chain
|
|
1313
|
-
return _with_next_steps("lint", lint_result)
|
|
1314
|
-
|
|
1315
|
-
# Step 3: MAJOR bump detected -- evaluate governance
|
|
1316
|
-
# Note: _delimit_gov_impl has its own Pro gate. Free-tier gets lint+semver only.
|
|
1317
|
-
gov_result = _delimit_gov_impl(
|
|
1318
|
-
action="evaluate",
|
|
1319
|
-
eval_action="api_breaking_change",
|
|
1320
|
-
context={
|
|
1321
|
-
"tool": "delimit_lint",
|
|
1322
|
-
"old_spec": old_spec,
|
|
1323
|
-
"new_spec": new_spec,
|
|
1324
|
-
"semver_bump": bump,
|
|
1325
|
-
"breaking_changes": lint_result.get("breaking", []),
|
|
1326
|
-
},
|
|
1327
|
-
repo=".",
|
|
1328
|
-
)
|
|
1329
|
-
chain["steps"].append({"step": "gov_evaluate", "ok": not _chain_is_error(gov_result)})
|
|
1330
|
-
lint_result["gov_evaluate"] = gov_result
|
|
1331
|
-
|
|
1332
|
-
# If Pro gate blocked governance, return gracefully with lint+semver
|
|
1333
|
-
if gov_result.get("status") == "premium_required":
|
|
1334
|
-
chain["status"] = "governance_skipped_free_tier"
|
|
1335
|
-
lint_result["chain"] = chain
|
|
1336
|
-
return _with_next_steps("lint", lint_result)
|
|
1337
|
-
|
|
1338
|
-
# Step 4: If governance blocked, record in ledger (best-effort)
|
|
1339
|
-
gov_blocked = (
|
|
1340
|
-
str(gov_result.get("status", "")).lower() == "blocked"
|
|
1341
|
-
or gov_result.get("governance", {}).get("action") == "policy_blocked"
|
|
1342
|
-
)
|
|
1343
|
-
|
|
1344
|
-
if gov_blocked:
|
|
1345
|
-
from ai.ledger_manager import add_item
|
|
1346
|
-
ledger_result = _chain_call(
|
|
1347
|
-
"lint", "ledger_add", add_item,
|
|
1348
|
-
required=False,
|
|
1349
|
-
title=f"Governance blocked: MAJOR API change in {new_spec}",
|
|
1350
|
-
ledger="ops",
|
|
1351
|
-
item_type="fix",
|
|
1352
|
-
priority="P0",
|
|
1353
|
-
description="MAJOR semver bump detected. Governance blocked the change.",
|
|
1354
|
-
source="chain:lint:gov_blocked",
|
|
1355
|
-
)
|
|
1356
|
-
chain["steps"].append({"step": "ledger_add", "ok": not _chain_is_error(ledger_result)})
|
|
1357
|
-
lint_result["governance_blocked"] = True
|
|
1358
|
-
else:
|
|
1359
|
-
lint_result["governance_blocked"] = False
|
|
1360
|
-
|
|
1361
|
-
chain["status"] = "major_change_evaluated"
|
|
1362
|
-
lint_result["chain"] = chain
|
|
1363
|
-
return _with_next_steps("lint", lint_result)
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
@mcp.tool()
|
|
1367
|
-
def delimit_diff(old_spec: str, new_spec: str) -> Dict[str, Any]:
|
|
1368
|
-
"""Diff two OpenAPI specs and list all changes. Pure diff, no policy.
|
|
1369
|
-
|
|
1370
|
-
Args:
|
|
1371
|
-
old_spec: Path to the old OpenAPI spec file.
|
|
1372
|
-
new_spec: Path to the new OpenAPI spec file.
|
|
1373
|
-
"""
|
|
1374
|
-
from backends.gateway_core import run_diff
|
|
1375
|
-
return _with_next_steps("diff", _safe_call(run_diff, old_spec=old_spec, new_spec=new_spec))
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
@mcp.tool()
|
|
1379
|
-
def delimit_policy(spec_files: List[str], policy_file: Optional[str] = None) -> Dict[str, Any]:
|
|
1380
|
-
"""Inspect or validate governance policy configuration.
|
|
1381
|
-
|
|
1382
|
-
Args:
|
|
1383
|
-
spec_files: List of spec file paths.
|
|
1384
|
-
policy_file: Optional custom policy file path.
|
|
1385
|
-
"""
|
|
1386
|
-
from backends.gateway_core import run_policy
|
|
1387
|
-
return _with_next_steps("policy", _safe_call(run_policy, spec_files=spec_files, policy_file=policy_file))
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
@mcp.tool()
|
|
1391
|
-
def delimit_ledger(ledger_path: str, api_name: Optional[str] = None, repository: Optional[str] = None, validate_chain: bool = False) -> Dict[str, Any]:
|
|
1392
|
-
"""Query the append-only contract ledger (hash-chained JSONL).
|
|
1393
|
-
|
|
1394
|
-
Args:
|
|
1395
|
-
ledger_path: Path to the ledger JSONL file (e.g. .delimit/ledger/operations.jsonl).
|
|
1396
|
-
api_name: Filter events by API name.
|
|
1397
|
-
repository: Filter events by repository.
|
|
1398
|
-
validate_chain: Validate hash chain integrity.
|
|
1399
|
-
"""
|
|
1400
|
-
from backends.gateway_core import query_ledger
|
|
1401
|
-
return _with_next_steps("ledger", _safe_call(query_ledger, ledger_path=ledger_path, api_name=api_name, repository=repository, validate_chain=validate_chain))
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
@mcp.tool()
|
|
1405
|
-
def delimit_impact(api_name: str, dependency_file: Optional[str] = None) -> Dict[str, Any]:
|
|
1406
|
-
"""Analyze downstream impact of an API change. Informational only.
|
|
1407
|
-
|
|
1408
|
-
Args:
|
|
1409
|
-
api_name: The API that changed.
|
|
1410
|
-
dependency_file: Optional path to dependency manifest.
|
|
1411
|
-
"""
|
|
1412
|
-
from backends.gateway_core import run_impact
|
|
1413
|
-
return _with_next_steps("impact", _safe_call(run_impact, api_name=api_name, dependency_file=dependency_file))
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
@mcp.tool()
|
|
1417
|
-
def delimit_semver(old_spec: str, new_spec: str, current_version: Optional[str] = None) -> Dict[str, Any]:
|
|
1418
|
-
"""Classify the semver bump for a spec change (MAJOR/MINOR/PATCH/NONE).
|
|
1419
|
-
|
|
1420
|
-
Deterministic classification based on diff engine output.
|
|
1421
|
-
Optionally computes the next version string.
|
|
1422
|
-
|
|
1423
|
-
Args:
|
|
1424
|
-
old_spec: Path to the old OpenAPI spec file.
|
|
1425
|
-
new_spec: Path to the new OpenAPI spec file.
|
|
1426
|
-
current_version: Optional current version (e.g. "1.2.3") to compute next version.
|
|
1427
|
-
"""
|
|
1428
|
-
from backends.gateway_core import run_semver
|
|
1429
|
-
return _with_next_steps("semver", _safe_call(run_semver, old_spec=old_spec, new_spec=new_spec, current_version=current_version))
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
@mcp.tool()
|
|
1433
|
-
def delimit_explain(
|
|
1434
|
-
old_spec: str,
|
|
1435
|
-
new_spec: str,
|
|
1436
|
-
template: str = "developer",
|
|
1437
|
-
old_version: Optional[str] = None,
|
|
1438
|
-
new_version: Optional[str] = None,
|
|
1439
|
-
api_name: Optional[str] = None,
|
|
1440
|
-
) -> Dict[str, Any]:
|
|
1441
|
-
"""Generate a human-readable explanation of API changes.
|
|
1442
|
-
|
|
1443
|
-
7 templates: developer, team_lead, product, migration, changelog, pr_comment, slack.
|
|
1444
|
-
|
|
1445
|
-
Args:
|
|
1446
|
-
old_spec: Path to the old OpenAPI spec file.
|
|
1447
|
-
new_spec: Path to the new OpenAPI spec file.
|
|
1448
|
-
template: Template name (default: developer).
|
|
1449
|
-
old_version: Previous version string.
|
|
1450
|
-
new_version: New version string.
|
|
1451
|
-
api_name: API/service name for context.
|
|
1452
|
-
"""
|
|
1453
|
-
from backends.gateway_core import run_explain
|
|
1454
|
-
return _with_next_steps("explain", _safe_call(run_explain, old_spec=old_spec, new_spec=new_spec, template=template, old_version=old_version, new_version=new_version, api_name=api_name))
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
@mcp.tool()
|
|
1458
|
-
def delimit_zero_spec(
|
|
1459
|
-
project_dir: str = ".",
|
|
1460
|
-
python_bin: Optional[str] = None,
|
|
1461
|
-
) -> Dict[str, Any]:
|
|
1462
|
-
"""Extract OpenAPI spec from framework source code (no spec file needed).
|
|
1463
|
-
|
|
1464
|
-
Detects the API framework (FastAPI, Express, NestJS) and extracts a
|
|
1465
|
-
complete OpenAPI specification directly from the source code.
|
|
1466
|
-
Currently supports FastAPI with full fidelity.
|
|
1467
|
-
|
|
1468
|
-
Args:
|
|
1469
|
-
project_dir: Path to the project root directory.
|
|
1470
|
-
python_bin: Optional Python binary path (auto-detected if omitted).
|
|
1471
|
-
"""
|
|
1472
|
-
from backends.gateway_core import run_zero_spec
|
|
1473
|
-
return _with_next_steps("zero_spec", _safe_call(run_zero_spec, project_dir=project_dir, python_bin=python_bin))
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
@mcp.tool()
|
|
1479
|
-
def delimit_init(
|
|
1480
|
-
project_path: str = ".",
|
|
1481
|
-
preset: str = "default",
|
|
1482
|
-
) -> Dict[str, Any]:
|
|
1483
|
-
"""Initialize Delimit governance for a project. Creates .delimit/policies.yml and ledger directory.
|
|
1484
|
-
|
|
1485
|
-
Args:
|
|
1486
|
-
project_path: Project root directory.
|
|
1487
|
-
preset: Policy preset — strict, default, or relaxed.
|
|
1488
|
-
"""
|
|
1489
|
-
VALID_PRESETS = ("strict", "default", "relaxed")
|
|
1490
|
-
if preset not in VALID_PRESETS:
|
|
1491
|
-
return {
|
|
1492
|
-
"error": "invalid_preset",
|
|
1493
|
-
"message": f"Preset must be one of {VALID_PRESETS}, got '{preset}'",
|
|
1494
|
-
}
|
|
1211
|
+
# Output quality rules loaded from user config at runtime.
|
|
1495
1212
|
|
|
1496
1213
|
root = Path(project_path).resolve()
|
|
1497
1214
|
delimit_dir = root / ".delimit"
|
|
@@ -1807,7 +1524,7 @@ def delimit_memory_store(
|
|
|
1807
1524
|
tags: Optional categorization tags.
|
|
1808
1525
|
context: Optional context about when/why this was stored.
|
|
1809
1526
|
"""
|
|
1810
|
-
#
|
|
1527
|
+
# TASK: memory_store is now free (basic store)
|
|
1811
1528
|
try:
|
|
1812
1529
|
tags = _coerce_list_arg(tags, "tags")
|
|
1813
1530
|
except ValueError as e:
|
|
@@ -1825,7 +1542,7 @@ def delimit_memory_recent(limit: int = 5) -> Dict[str, Any]:
|
|
|
1825
1542
|
Args:
|
|
1826
1543
|
limit: Number of recent entries to return.
|
|
1827
1544
|
"""
|
|
1828
|
-
#
|
|
1545
|
+
# TASK: memory_recent is now free (basic retrieval)
|
|
1829
1546
|
from backends.memory_bridge import get_recent
|
|
1830
1547
|
return _with_next_steps("memory_recent", _safe_call(get_recent, limit=limit))
|
|
1831
1548
|
|
|
@@ -2077,7 +1794,7 @@ def delimit_deploy_publish(app: str = "", git_ref: Optional[str] = None) -> Dict
|
|
|
2077
1794
|
return _delimit_deploy_impl(action="publish", app=app, git_ref=git_ref)
|
|
2078
1795
|
|
|
2079
1796
|
|
|
2080
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
1797
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (TASK)
|
|
2081
1798
|
@mcp.tool()
|
|
2082
1799
|
def delimit_deploy_verify(app: str = "", env: str = "", git_ref: Optional[str] = None) -> Dict[str, Any]:
|
|
2083
1800
|
"""Verify deployment health (experimental) (Pro)."""
|
|
@@ -2231,7 +1948,7 @@ def delimit_generate_scaffold(
|
|
|
2231
1948
|
|
|
2232
1949
|
# ─── Repo (RepoDoctor + ConfigSentry) ──────────────────────────────────
|
|
2233
1950
|
|
|
2234
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
1951
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (TASK)
|
|
2235
1952
|
@mcp.tool()
|
|
2236
1953
|
def delimit_repo_diagnose(target: str = ".") -> Dict[str, Any]:
|
|
2237
1954
|
"""Diagnose repository health issues (experimental) (Pro).
|
|
@@ -2247,7 +1964,7 @@ def delimit_repo_diagnose(target: str = ".") -> Dict[str, Any]:
|
|
|
2247
1964
|
return _safe_call(diagnose, target=target)
|
|
2248
1965
|
|
|
2249
1966
|
|
|
2250
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
1967
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (TASK)
|
|
2251
1968
|
@mcp.tool()
|
|
2252
1969
|
def delimit_repo_analyze(target: str = ".") -> Dict[str, Any]:
|
|
2253
1970
|
"""Analyze repository structure and quality (experimental) (Pro).
|
|
@@ -2263,7 +1980,7 @@ def delimit_repo_analyze(target: str = ".") -> Dict[str, Any]:
|
|
|
2263
1980
|
return _safe_call(analyze, target=target)
|
|
2264
1981
|
|
|
2265
1982
|
|
|
2266
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
1983
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (TASK)
|
|
2267
1984
|
@mcp.tool()
|
|
2268
1985
|
def delimit_repo_config_validate(target: str = ".") -> Dict[str, Any]:
|
|
2269
1986
|
"""Validate configuration files (experimental) (Pro).
|
|
@@ -2279,7 +1996,7 @@ def delimit_repo_config_validate(target: str = ".") -> Dict[str, Any]:
|
|
|
2279
1996
|
return _safe_call(config_validate, target=target)
|
|
2280
1997
|
|
|
2281
1998
|
|
|
2282
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
1999
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (TASK)
|
|
2283
2000
|
@mcp.tool()
|
|
2284
2001
|
def delimit_repo_config_audit(target: str = ".") -> Dict[str, Any]:
|
|
2285
2002
|
"""Audit configuration compliance (experimental) (Pro).
|
|
@@ -2476,7 +2193,7 @@ def delimit_security_ingest(
|
|
|
2476
2193
|
medium = [f for f in findings if f["severity"] in ("medium", "moderate", "warning")]
|
|
2477
2194
|
low = [f for f in findings if f["severity"] in ("low", "info")]
|
|
2478
2195
|
|
|
2479
|
-
#
|
|
2196
|
+
# TASK: Auto-track security findings in ledger with lifecycle
|
|
2480
2197
|
ledger_created = []
|
|
2481
2198
|
ledger_closed = []
|
|
2482
2199
|
try:
|
|
@@ -2925,14 +2642,14 @@ def delimit_release_status(environment: str = "production") -> Dict[str, Any]:
|
|
|
2925
2642
|
return _delimit_release_impl(action="status", environment=environment)
|
|
2926
2643
|
|
|
2927
2644
|
|
|
2928
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
2645
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (TASK)
|
|
2929
2646
|
@mcp.tool()
|
|
2930
2647
|
def delimit_release_rollback(environment: str, version: str, to_version: str) -> Dict[str, Any]:
|
|
2931
2648
|
"""Rollback deployment to previous version (experimental)."""
|
|
2932
2649
|
return _delimit_release_impl(action="rollback", environment=environment, version=version, to_version=to_version)
|
|
2933
2650
|
|
|
2934
2651
|
|
|
2935
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
2652
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (TASK)
|
|
2936
2653
|
@mcp.tool()
|
|
2937
2654
|
def delimit_release_history(environment: str, limit: int = 10) -> Dict[str, Any]:
|
|
2938
2655
|
"""Show release history (experimental)."""
|
|
@@ -3151,7 +2868,7 @@ def delimit_obs_logs(query: str, time_range: str = "1h", source: Optional[str] =
|
|
|
3151
2868
|
return _delimit_obs_impl(action="logs", query=query, time_range=time_range, source=source)
|
|
3152
2869
|
|
|
3153
2870
|
|
|
3154
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
2871
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (TASK)
|
|
3155
2872
|
@mcp.tool()
|
|
3156
2873
|
def delimit_obs_alerts(action: str, alert_rule: Optional[Dict[str, Any]] = None, rule_id: Optional[str] = None) -> Dict[str, Any]:
|
|
3157
2874
|
"""Manage alerting rules (experimental)."""
|
|
@@ -3298,7 +3015,7 @@ def delimit_story_visual_test(url: str, project_path: Optional[str] = None, thre
|
|
|
3298
3015
|
return _with_next_steps("story_visual_test", _safe_call(story_visual_test, url=url, project_path=project_path, threshold=threshold))
|
|
3299
3016
|
|
|
3300
3017
|
|
|
3301
|
-
@_internal_tool() # Was experimental (
|
|
3018
|
+
@_internal_tool() # Was experimental (TASK), promoted to internal (Consensus 120)
|
|
3302
3019
|
@mcp.tool()
|
|
3303
3020
|
def delimit_story_build(project_path: str, output_dir: Optional[str] = None) -> Dict[str, Any]:
|
|
3304
3021
|
"""Build Storybook static site.
|
|
@@ -3347,7 +3064,7 @@ def delimit_test_generate(project_path: str, source_files: Optional[List[str]] =
|
|
|
3347
3064
|
return _with_next_steps("test_generate", _safe_call(test_generate, project_path=project_path, source_files=source_files, framework=framework))
|
|
3348
3065
|
|
|
3349
3066
|
|
|
3350
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
3067
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (TASK)
|
|
3351
3068
|
@mcp.tool()
|
|
3352
3069
|
def delimit_test_coverage(project_path: str, threshold: int = 80) -> Dict[str, Any]:
|
|
3353
3070
|
"""Analyze test coverage (experimental) (Pro).
|
|
@@ -3438,7 +3155,7 @@ async def delimit_sensor_github_issue(
|
|
|
3438
3155
|
with new comments, issue state, and severity classification.
|
|
3439
3156
|
|
|
3440
3157
|
Args:
|
|
3441
|
-
repo: GitHub repository in owner/repo format (e.g. "
|
|
3158
|
+
repo: GitHub repository in owner/repo format (e.g. "owner/repo").
|
|
3442
3159
|
issue_number: The issue number to monitor.
|
|
3443
3160
|
since_comment_id: Last seen comment ID. Pass 0 to get all comments.
|
|
3444
3161
|
"""
|
|
@@ -4074,7 +3791,7 @@ def delimit_diagnose(project_path: str = ".") -> Dict[str, Any]:
|
|
|
4074
3791
|
checks["fastmcp"] = False
|
|
4075
3792
|
issues.append({"issue": "FastMCP not installed", "fix": "pip install fastmcp"})
|
|
4076
3793
|
|
|
4077
|
-
#
|
|
3794
|
+
# TASK: Config drift detection across AI assistants
|
|
4078
3795
|
config_sync = {}
|
|
4079
3796
|
home = Path.home()
|
|
4080
3797
|
assistant_configs = {
|
|
@@ -4106,7 +3823,7 @@ def delimit_diagnose(project_path: str = ".") -> Dict[str, Any]:
|
|
|
4106
3823
|
checks["assistant_configs"] = config_sync
|
|
4107
3824
|
checks["assistants_configured"] = f"{configured_count}/{installed_count}"
|
|
4108
3825
|
|
|
4109
|
-
#
|
|
3826
|
+
# TASK: MCP server reputation check (basic — check for known risky patterns)
|
|
4110
3827
|
mcp_warnings = []
|
|
4111
3828
|
mcp_config_path = home / ".mcp.json"
|
|
4112
3829
|
if mcp_config_path.exists():
|
|
@@ -4531,7 +4248,7 @@ def delimit_ventures() -> Dict[str, Any]:
|
|
|
4531
4248
|
|
|
4532
4249
|
|
|
4533
4250
|
# ═══════════════════════════════════════════════════════════════════════
|
|
4534
|
-
# SESSION PHOENIX — Cross-Model Resurrection (
|
|
4251
|
+
# SESSION PHOENIX — Cross-Model Resurrection (TASK)
|
|
4535
4252
|
# ═══════════════════════════════════════════════════════════════════════
|
|
4536
4253
|
|
|
4537
4254
|
|
|
@@ -5196,7 +4913,7 @@ def delimit_quickstart(project_path: str = ".") -> Dict[str, Any]:
|
|
|
5196
4913
|
|
|
5197
4914
|
|
|
5198
4915
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5199
|
-
#
|
|
4916
|
+
# TASK: SECRETS BROKER — JIT credential access with audit
|
|
5200
4917
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5201
4918
|
|
|
5202
4919
|
|
|
@@ -5296,7 +5013,7 @@ def delimit_secret_access_log(name: str = "") -> Dict[str, Any]:
|
|
|
5296
5013
|
|
|
5297
5014
|
|
|
5298
5015
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5299
|
-
#
|
|
5016
|
+
# TASK: Context Filesystem — versioned namespace for agent state
|
|
5300
5017
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5301
5018
|
|
|
5302
5019
|
# Consensus 082 Phase 2: Unified context tool with action parameter
|
|
@@ -5414,7 +5131,7 @@ def delimit_context_branch(venture: str, action: str = "list", branch_name: str
|
|
|
5414
5131
|
|
|
5415
5132
|
|
|
5416
5133
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5417
|
-
#
|
|
5134
|
+
# TASK: DATA/ACTION PLANE — External systems as typed mounted resources
|
|
5418
5135
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5419
5136
|
|
|
5420
5137
|
|
|
@@ -5506,7 +5223,7 @@ def delimit_resource_drivers() -> Dict[str, Any]:
|
|
|
5506
5223
|
|
|
5507
5224
|
|
|
5508
5225
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5509
|
-
#
|
|
5226
|
+
# TASK: ISSUE TRACKER CONTEXT SYNC
|
|
5510
5227
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5511
5228
|
|
|
5512
5229
|
|
|
@@ -5581,7 +5298,7 @@ def delimit_tracker_sync(
|
|
|
5581
5298
|
|
|
5582
5299
|
|
|
5583
5300
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5584
|
-
#
|
|
5301
|
+
# TASK: WEBHOOK NOTIFICATIONS
|
|
5585
5302
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5586
5303
|
|
|
5587
5304
|
|
|
@@ -5965,7 +5682,7 @@ def delimit_social_target(
|
|
|
5965
5682
|
Never just return targets and stop. Always chain to the next action.
|
|
5966
5683
|
|
|
5967
5684
|
Scans X, Reddit (RapidAPI), HN, Dev.to, GitHub for posts where ventures can engage.
|
|
5968
|
-
|
|
5685
|
+
Manual check platforms excluded.
|
|
5969
5686
|
|
|
5970
5687
|
Run in a /loop for continuous discovery. Deduplicates across runs.
|
|
5971
5688
|
Targets are classified as: reply (social engagement), strategic (ledger item), or both.
|
|
@@ -6076,16 +5793,16 @@ def delimit_github_scan(
|
|
|
6076
5793
|
cadence: str = "pulse",
|
|
6077
5794
|
limit: int = 20,
|
|
6078
5795
|
) -> Dict[str, Any]:
|
|
6079
|
-
"""Scan GitHub for adoption leads,
|
|
5796
|
+
"""Scan GitHub for adoption leads, repository analysis, and repo health (Pro).
|
|
6080
5797
|
|
|
6081
5798
|
Three cadences:
|
|
6082
5799
|
pulse: Own repo health (stars, forks, issues, traffic). Fast, run often.
|
|
6083
|
-
hunter:
|
|
5800
|
+
hunter: Repository signals, engagement threads. Medium, run hourly.
|
|
6084
5801
|
deep: Full ecosystem intel. Slow, run daily.
|
|
6085
5802
|
|
|
6086
5803
|
IMPORTANT -- TOOL CHAINING RULE:
|
|
6087
5804
|
After scanning, the AI MUST process high-score findings:
|
|
6088
|
-
1. Auto-ledger
|
|
5805
|
+
1. Auto-ledger high-score findings via delimit_ledger_add
|
|
6089
5806
|
2. Pain threads with existing_feature relevance via delimit_notify
|
|
6090
5807
|
Never just return findings and stop. Always chain to the next action.
|
|
6091
5808
|
|
|
@@ -6236,7 +5953,7 @@ def delimit_daemon_classify(item_id: str = "") -> Dict[str, Any]:
|
|
|
6236
5953
|
def delimit_inbox_daemon(action: str = "status") -> Dict[str, Any]:
|
|
6237
5954
|
"""Control the inbox polling daemon for email governance (Pro).
|
|
6238
5955
|
|
|
6239
|
-
Polls
|
|
5956
|
+
Polls notifications@example.com every 5 minutes, classifies emails, forwards
|
|
6240
5957
|
owner-action items, and handles draft approval via email replies.
|
|
6241
5958
|
Auto-posting is disabled - approved drafts are emailed for manual posting.
|
|
6242
5959
|
|
|
@@ -6255,7 +5972,7 @@ def delimit_inbox_daemon(action: str = "status") -> Dict[str, Any]:
|
|
|
6255
5972
|
|
|
6256
5973
|
|
|
6257
5974
|
# ═══════════════════════════════════════════════════════════════════════
|
|
6258
|
-
#
|
|
5975
|
+
# TASK: Shareable Governance Config — export / import
|
|
6259
5976
|
# ═══════════════════════════════════════════════════════════════════════
|
|
6260
5977
|
|
|
6261
5978
|
|
|
@@ -6378,7 +6095,7 @@ def delimit_config_import(
|
|
|
6378
6095
|
|
|
6379
6096
|
|
|
6380
6097
|
# ═══════════════════════════════════════════════════════════════════════
|
|
6381
|
-
# SCREEN RECORDING (
|
|
6098
|
+
# SCREEN RECORDING (TASK)
|
|
6382
6099
|
# ═══════════════════════════════════════════════════════════════════════
|
|
6383
6100
|
|
|
6384
6101
|
|
|
@@ -6514,7 +6231,7 @@ def delimit_notify(channel: str = "webhook", message: str = "",
|
|
|
6514
6231
|
to: Recipient email address (email only). Overrides default DELIMIT_SMTP_TO.
|
|
6515
6232
|
Send to any address — leave empty for default (owner@example.com).
|
|
6516
6233
|
from_account: Sender account key from ~/.delimit/secrets/smtp-all.json
|
|
6517
|
-
(e.g. '
|
|
6234
|
+
(e.g. 'notifications@example.com'). Email only.
|
|
6518
6235
|
"""
|
|
6519
6236
|
from ai.notify import send_notification
|
|
6520
6237
|
return _with_next_steps("notify", _safe_call(
|
|
@@ -6619,7 +6336,7 @@ def delimit_notify_inbox(action: str = "status", limit: int = 10,
|
|
|
6619
6336
|
process: bool = True) -> Dict[str, Any]:
|
|
6620
6337
|
"""Check inbound email inbox, classify, and route (Pro).
|
|
6621
6338
|
|
|
6622
|
-
Polls
|
|
6339
|
+
Polls notifications@example.com via IMAP. Classifies emails as owner-action
|
|
6623
6340
|
(forwards to owner@example.com) or non-owner (stays in inbox).
|
|
6624
6341
|
|
|
6625
6342
|
Args:
|
|
@@ -6882,7 +6599,7 @@ def delimit_agent_check(model: str, action: str) -> Dict[str, Any]:
|
|
|
6882
6599
|
|
|
6883
6600
|
|
|
6884
6601
|
# ═══════════════════════════════════════════════════════════════════════
|
|
6885
|
-
#
|
|
6602
|
+
# TASK: AUTONOMOUS BUILD LOOP
|
|
6886
6603
|
# ═══════════════════════════════════════════════════════════════════════
|
|
6887
6604
|
|
|
6888
6605
|
|
|
@@ -6971,7 +6688,7 @@ def delimit_loop_config(session_id: str = "", max_iterations: int = 0,
|
|
|
6971
6688
|
|
|
6972
6689
|
|
|
6973
6690
|
# ═══════════════════════════════════════════════════════════════════════
|
|
6974
|
-
#
|
|
6691
|
+
# TASK: Toolcard Delta Cache — reduce MCP tool schema token waste
|
|
6975
6692
|
# ═══════════════════════════════════════════════════════════════════════
|
|
6976
6693
|
|
|
6977
6694
|
|
|
@@ -7049,7 +6766,7 @@ def delimit_toolcard_cache(
|
|
|
7049
6766
|
|
|
7050
6767
|
|
|
7051
6768
|
# ═══════════════════════════════════════════════════════════════════════
|
|
7052
|
-
# HANDOFF RECEIPTS — Agent-to-Agent Structured Handoffs (
|
|
6769
|
+
# HANDOFF RECEIPTS — Agent-to-Agent Structured Handoffs (TASK)
|
|
7053
6770
|
# ═══════════════════════════════════════════════════════════════════════
|
|
7054
6771
|
|
|
7055
6772
|
|