delimit-cli 4.0.1 → 4.0.2
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/README.md +0 -1
- package/gateway/ai/cross_model_audit.py +600 -0
- package/gateway/ai/github_scanner.py +622 -0
- package/gateway/ai/handoff_receipts.py +409 -0
- package/gateway/ai/license_core.py +1 -2
- package/gateway/ai/notify.py +12 -12
- package/gateway/ai/reddit_scanner.py +562 -0
- package/gateway/ai/server.py +341 -51
- package/gateway/ai/session_phoenix.py +371 -0
- package/gateway/ai/swarm.py +2 -2
- package/gateway/ai/toolcard_cache.py +327 -0
- 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 +1 -10
- package/scripts/crosspost_devto.py +304 -0
- package/scripts/security-check.sh +66 -0
- package/scripts/weekly-tweet.py +191 -0
package/gateway/ai/server.py
CHANGED
|
@@ -19,8 +19,9 @@ All tools follow the Adapter Boundary Contract v1.0:
|
|
|
19
19
|
- Stateless between calls
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
# ──
|
|
22
|
+
# ── Founder Voice Doctrine ──────────────────────────────────────────────
|
|
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
|
|
24
25
|
#
|
|
25
26
|
# Core: serious builder/operator, not a marketer. Credibility over persuasion.
|
|
26
27
|
# Truth over excitement. Concrete mechanisms, not vague benefits.
|
|
@@ -33,7 +34,14 @@ All tools follow the Adapter Boundary Contract v1.0:
|
|
|
33
34
|
# attached to decisions? Would this still read well a year from now?
|
|
34
35
|
# ────────────────────────────────────────────────────────────────────────
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
FOUNDER_VOICE_HYPE_WORDS = {
|
|
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
|
+
}
|
|
37
45
|
|
|
38
46
|
import json
|
|
39
47
|
import logging
|
|
@@ -52,13 +60,13 @@ from fastmcp import FastMCP
|
|
|
52
60
|
logger = logging.getLogger("delimit.ai")
|
|
53
61
|
|
|
54
62
|
# ═══════════════════════════════════════════════════════════════════════
|
|
55
|
-
#
|
|
63
|
+
# STR-046: Agent Identity — session tracking for every tool call
|
|
56
64
|
# ═══════════════════════════════════════════════════════════════════════
|
|
57
65
|
|
|
58
66
|
_current_session_id = os.environ.get("DELIMIT_SESSION_ID", "")
|
|
59
67
|
|
|
60
68
|
# ═══════════════════════════════════════════════════════════════════════
|
|
61
|
-
#
|
|
69
|
+
# STR-053: Distributed Tracing — trace ID + span counter for every call
|
|
62
70
|
# ═══════════════════════════════════════════════════════════════════════
|
|
63
71
|
|
|
64
72
|
_trace_id = os.environ.get("DELIMIT_TRACE_ID", str(uuid.uuid4())[:8])
|
|
@@ -243,7 +251,7 @@ def _coerce_dict_arg(
|
|
|
243
251
|
|
|
244
252
|
|
|
245
253
|
# ═══════════════════════════════════════════════════════════════════════
|
|
246
|
-
#
|
|
254
|
+
# STR-040: Risk Classification for Approval Gates
|
|
247
255
|
# ═══════════════════════════════════════════════════════════════════════
|
|
248
256
|
|
|
249
257
|
HIGH_RISK_TOOLS = {
|
|
@@ -267,7 +275,7 @@ def _classify_risk(tool_name: str) -> str:
|
|
|
267
275
|
|
|
268
276
|
|
|
269
277
|
# ═══════════════════════════════════════════════════════════════════════
|
|
270
|
-
#
|
|
278
|
+
# STR-052: Policy Kernel — Inline Enforcement
|
|
271
279
|
# Checks policy BEFORE/AFTER tool execution to block high-risk actions.
|
|
272
280
|
# ═══════════════════════════════════════════════════════════════════════
|
|
273
281
|
|
|
@@ -319,7 +327,7 @@ def _check_policy_gate(tool_name: str, kwargs: dict) -> Optional[Dict]:
|
|
|
319
327
|
"action": "Switch to guarded mode or request approval",
|
|
320
328
|
}
|
|
321
329
|
|
|
322
|
-
#
|
|
330
|
+
# LED-173: Deploy gating — block deploys when unresolved critical findings exist
|
|
323
331
|
DEPLOY_TOOLS = {"deploy_publish", "deploy_npm", "deploy_site", "deploy_build"}
|
|
324
332
|
clean = tool_name.replace("delimit_", "")
|
|
325
333
|
if clean in DEPLOY_TOOLS and mode != "advisory":
|
|
@@ -447,7 +455,7 @@ mcp.description = (
|
|
|
447
455
|
|
|
448
456
|
VERSION = "3.2.1"
|
|
449
457
|
|
|
450
|
-
#
|
|
458
|
+
# LED-044 + Consensus 118/119/120: Tool visibility tiers.
|
|
451
459
|
# Tier cascade: SHOW_EXPERIMENTAL > SHOW_INTERNAL > SHOW_OPS > public (always visible).
|
|
452
460
|
# Set DELIMIT_SHOW_INTERNAL=1 to see all tiers (founder workflow).
|
|
453
461
|
SHOW_EXPERIMENTAL = os.environ.get("DELIMIT_SHOW_EXPERIMENTAL", "") == "1"
|
|
@@ -832,7 +840,7 @@ NEXT_STEPS_REGISTRY: Dict[str, List[Dict[str, Any]]] = {
|
|
|
832
840
|
],
|
|
833
841
|
# --- Sensing ---
|
|
834
842
|
"sensor_github_issue": [],
|
|
835
|
-
# --- Context Filesystem (
|
|
843
|
+
# --- Context Filesystem (STR-048) ---
|
|
836
844
|
"context_init": [
|
|
837
845
|
{"tool": "delimit_context_write", "reason": "Write an artifact to the new context", "suggested_args": {}, "is_premium": False},
|
|
838
846
|
],
|
|
@@ -958,10 +966,10 @@ def _emit_event(tool_name: str, result: Dict[str, Any]) -> None:
|
|
|
958
966
|
except Exception:
|
|
959
967
|
pass # Never let cloud sync break tool execution
|
|
960
968
|
|
|
961
|
-
#
|
|
969
|
+
# LED-183: Webhook notifications for governance events
|
|
962
970
|
_fire_webhook(event)
|
|
963
971
|
|
|
964
|
-
#
|
|
972
|
+
# STR-053: Write trace span for session replay
|
|
965
973
|
try:
|
|
966
974
|
from ai.tracing import start_span, end_span
|
|
967
975
|
span = start_span(_trace_id, tool_name, args={"tool": tool_name})
|
|
@@ -973,7 +981,7 @@ def _emit_event(tool_name: str, result: Dict[str, Any]) -> None:
|
|
|
973
981
|
except Exception:
|
|
974
982
|
pass # Tracing is best-effort
|
|
975
983
|
|
|
976
|
-
#
|
|
984
|
+
# STR-046: Write to agent_actions log for session drill-down
|
|
977
985
|
if session_info["session_id"]:
|
|
978
986
|
actions_dir = Path.home() / ".delimit" / "agent_actions"
|
|
979
987
|
actions_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -1177,7 +1185,7 @@ def _with_next_steps(tool_name: str, result: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
1177
1185
|
except Exception as e:
|
|
1178
1186
|
logger.warning("Inbox daemon auto-start failed: %s", e)
|
|
1179
1187
|
|
|
1180
|
-
#
|
|
1188
|
+
# LED-219: Auto-register tool schemas with toolcard cache on first call
|
|
1181
1189
|
global _toolcard_cache_autoregistered
|
|
1182
1190
|
if not _toolcard_cache_autoregistered:
|
|
1183
1191
|
_toolcard_cache_autoregistered = True
|
|
@@ -1195,7 +1203,7 @@ def _with_next_steps(tool_name: str, result: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
1195
1203
|
except Exception as e:
|
|
1196
1204
|
logger.warning("Toolcard cache auto-register failed: %s", e)
|
|
1197
1205
|
|
|
1198
|
-
#
|
|
1206
|
+
# LED-219: Track every tool call for session analytics
|
|
1199
1207
|
try:
|
|
1200
1208
|
from ai.toolcard_cache import get_cache as _get_tc
|
|
1201
1209
|
_get_tc().record_call(tool_name)
|
|
@@ -1208,7 +1216,282 @@ def _with_next_steps(tool_name: str, result: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
1208
1216
|
result.get("explanation", ""), result.get("changelog", ""),
|
|
1209
1217
|
result.get("content", "")]
|
|
1210
1218
|
_all_text = " ".join(str(f) for f in _text_fields if f).lower()
|
|
1211
|
-
|
|
1219
|
+
_found_hype = [w for w in FOUNDER_VOICE_HYPE_WORDS if w in _all_text]
|
|
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
|
+
}
|
|
1212
1495
|
|
|
1213
1496
|
root = Path(project_path).resolve()
|
|
1214
1497
|
delimit_dir = root / ".delimit"
|
|
@@ -1524,7 +1807,7 @@ def delimit_memory_store(
|
|
|
1524
1807
|
tags: Optional categorization tags.
|
|
1525
1808
|
context: Optional context about when/why this was stored.
|
|
1526
1809
|
"""
|
|
1527
|
-
#
|
|
1810
|
+
# LED-193: memory_store is now free (basic store)
|
|
1528
1811
|
try:
|
|
1529
1812
|
tags = _coerce_list_arg(tags, "tags")
|
|
1530
1813
|
except ValueError as e:
|
|
@@ -1542,7 +1825,7 @@ def delimit_memory_recent(limit: int = 5) -> Dict[str, Any]:
|
|
|
1542
1825
|
Args:
|
|
1543
1826
|
limit: Number of recent entries to return.
|
|
1544
1827
|
"""
|
|
1545
|
-
#
|
|
1828
|
+
# LED-193: memory_recent is now free (basic retrieval)
|
|
1546
1829
|
from backends.memory_bridge import get_recent
|
|
1547
1830
|
return _with_next_steps("memory_recent", _safe_call(get_recent, limit=limit))
|
|
1548
1831
|
|
|
@@ -1794,7 +2077,7 @@ def delimit_deploy_publish(app: str = "", git_ref: Optional[str] = None) -> Dict
|
|
|
1794
2077
|
return _delimit_deploy_impl(action="publish", app=app, git_ref=git_ref)
|
|
1795
2078
|
|
|
1796
2079
|
|
|
1797
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
2080
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
1798
2081
|
@mcp.tool()
|
|
1799
2082
|
def delimit_deploy_verify(app: str = "", env: str = "", git_ref: Optional[str] = None) -> Dict[str, Any]:
|
|
1800
2083
|
"""Verify deployment health (experimental) (Pro)."""
|
|
@@ -1948,7 +2231,7 @@ def delimit_generate_scaffold(
|
|
|
1948
2231
|
|
|
1949
2232
|
# ─── Repo (RepoDoctor + ConfigSentry) ──────────────────────────────────
|
|
1950
2233
|
|
|
1951
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
2234
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
1952
2235
|
@mcp.tool()
|
|
1953
2236
|
def delimit_repo_diagnose(target: str = ".") -> Dict[str, Any]:
|
|
1954
2237
|
"""Diagnose repository health issues (experimental) (Pro).
|
|
@@ -1964,7 +2247,7 @@ def delimit_repo_diagnose(target: str = ".") -> Dict[str, Any]:
|
|
|
1964
2247
|
return _safe_call(diagnose, target=target)
|
|
1965
2248
|
|
|
1966
2249
|
|
|
1967
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
2250
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
1968
2251
|
@mcp.tool()
|
|
1969
2252
|
def delimit_repo_analyze(target: str = ".") -> Dict[str, Any]:
|
|
1970
2253
|
"""Analyze repository structure and quality (experimental) (Pro).
|
|
@@ -1980,7 +2263,7 @@ def delimit_repo_analyze(target: str = ".") -> Dict[str, Any]:
|
|
|
1980
2263
|
return _safe_call(analyze, target=target)
|
|
1981
2264
|
|
|
1982
2265
|
|
|
1983
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
2266
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
1984
2267
|
@mcp.tool()
|
|
1985
2268
|
def delimit_repo_config_validate(target: str = ".") -> Dict[str, Any]:
|
|
1986
2269
|
"""Validate configuration files (experimental) (Pro).
|
|
@@ -1996,7 +2279,7 @@ def delimit_repo_config_validate(target: str = ".") -> Dict[str, Any]:
|
|
|
1996
2279
|
return _safe_call(config_validate, target=target)
|
|
1997
2280
|
|
|
1998
2281
|
|
|
1999
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
2282
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
2000
2283
|
@mcp.tool()
|
|
2001
2284
|
def delimit_repo_config_audit(target: str = ".") -> Dict[str, Any]:
|
|
2002
2285
|
"""Audit configuration compliance (experimental) (Pro).
|
|
@@ -2193,7 +2476,7 @@ def delimit_security_ingest(
|
|
|
2193
2476
|
medium = [f for f in findings if f["severity"] in ("medium", "moderate", "warning")]
|
|
2194
2477
|
low = [f for f in findings if f["severity"] in ("low", "info")]
|
|
2195
2478
|
|
|
2196
|
-
#
|
|
2479
|
+
# LED-172: Auto-track security findings in ledger with lifecycle
|
|
2197
2480
|
ledger_created = []
|
|
2198
2481
|
ledger_closed = []
|
|
2199
2482
|
try:
|
|
@@ -2642,14 +2925,14 @@ def delimit_release_status(environment: str = "production") -> Dict[str, Any]:
|
|
|
2642
2925
|
return _delimit_release_impl(action="status", environment=environment)
|
|
2643
2926
|
|
|
2644
2927
|
|
|
2645
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
2928
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
2646
2929
|
@mcp.tool()
|
|
2647
2930
|
def delimit_release_rollback(environment: str, version: str, to_version: str) -> Dict[str, Any]:
|
|
2648
2931
|
"""Rollback deployment to previous version (experimental)."""
|
|
2649
2932
|
return _delimit_release_impl(action="rollback", environment=environment, version=version, to_version=to_version)
|
|
2650
2933
|
|
|
2651
2934
|
|
|
2652
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
2935
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
2653
2936
|
@mcp.tool()
|
|
2654
2937
|
def delimit_release_history(environment: str, limit: int = 10) -> Dict[str, Any]:
|
|
2655
2938
|
"""Show release history (experimental)."""
|
|
@@ -2868,7 +3151,7 @@ def delimit_obs_logs(query: str, time_range: str = "1h", source: Optional[str] =
|
|
|
2868
3151
|
return _delimit_obs_impl(action="logs", query=query, time_range=time_range, source=source)
|
|
2869
3152
|
|
|
2870
3153
|
|
|
2871
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
3154
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
2872
3155
|
@mcp.tool()
|
|
2873
3156
|
def delimit_obs_alerts(action: str, alert_rule: Optional[Dict[str, Any]] = None, rule_id: Optional[str] = None) -> Dict[str, Any]:
|
|
2874
3157
|
"""Manage alerting rules (experimental)."""
|
|
@@ -3015,7 +3298,7 @@ def delimit_story_visual_test(url: str, project_path: Optional[str] = None, thre
|
|
|
3015
3298
|
return _with_next_steps("story_visual_test", _safe_call(story_visual_test, url=url, project_path=project_path, threshold=threshold))
|
|
3016
3299
|
|
|
3017
3300
|
|
|
3018
|
-
@_internal_tool() # Was experimental (
|
|
3301
|
+
@_internal_tool() # Was experimental (LED-044), promoted to internal (Consensus 120)
|
|
3019
3302
|
@mcp.tool()
|
|
3020
3303
|
def delimit_story_build(project_path: str, output_dir: Optional[str] = None) -> Dict[str, Any]:
|
|
3021
3304
|
"""Build Storybook static site.
|
|
@@ -3064,7 +3347,7 @@ def delimit_test_generate(project_path: str, source_files: Optional[List[str]] =
|
|
|
3064
3347
|
return _with_next_steps("test_generate", _safe_call(test_generate, project_path=project_path, source_files=source_files, framework=framework))
|
|
3065
3348
|
|
|
3066
3349
|
|
|
3067
|
-
@_experimental_tool() # HIDDEN: stub/pass-through (
|
|
3350
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
3068
3351
|
@mcp.tool()
|
|
3069
3352
|
def delimit_test_coverage(project_path: str, threshold: int = 80) -> Dict[str, Any]:
|
|
3070
3353
|
"""Analyze test coverage (experimental) (Pro).
|
|
@@ -3155,7 +3438,7 @@ async def delimit_sensor_github_issue(
|
|
|
3155
3438
|
with new comments, issue state, and severity classification.
|
|
3156
3439
|
|
|
3157
3440
|
Args:
|
|
3158
|
-
repo: GitHub repository in owner/repo format (e.g. "
|
|
3441
|
+
repo: GitHub repository in owner/repo format (e.g. "activepieces/activepieces").
|
|
3159
3442
|
issue_number: The issue number to monitor.
|
|
3160
3443
|
since_comment_id: Last seen comment ID. Pass 0 to get all comments.
|
|
3161
3444
|
"""
|
|
@@ -3791,7 +4074,7 @@ def delimit_diagnose(project_path: str = ".") -> Dict[str, Any]:
|
|
|
3791
4074
|
checks["fastmcp"] = False
|
|
3792
4075
|
issues.append({"issue": "FastMCP not installed", "fix": "pip install fastmcp"})
|
|
3793
4076
|
|
|
3794
|
-
#
|
|
4077
|
+
# LED-191: Config drift detection across AI assistants
|
|
3795
4078
|
config_sync = {}
|
|
3796
4079
|
home = Path.home()
|
|
3797
4080
|
assistant_configs = {
|
|
@@ -3823,7 +4106,7 @@ def delimit_diagnose(project_path: str = ".") -> Dict[str, Any]:
|
|
|
3823
4106
|
checks["assistant_configs"] = config_sync
|
|
3824
4107
|
checks["assistants_configured"] = f"{configured_count}/{installed_count}"
|
|
3825
4108
|
|
|
3826
|
-
#
|
|
4109
|
+
# LED-192: MCP server reputation check (basic — check for known risky patterns)
|
|
3827
4110
|
mcp_warnings = []
|
|
3828
4111
|
mcp_config_path = home / ".mcp.json"
|
|
3829
4112
|
if mcp_config_path.exists():
|
|
@@ -4248,7 +4531,7 @@ def delimit_ventures() -> Dict[str, Any]:
|
|
|
4248
4531
|
|
|
4249
4532
|
|
|
4250
4533
|
# ═══════════════════════════════════════════════════════════════════════
|
|
4251
|
-
# SESSION PHOENIX — Cross-Model Resurrection (
|
|
4534
|
+
# SESSION PHOENIX — Cross-Model Resurrection (LED-218)
|
|
4252
4535
|
# ═══════════════════════════════════════════════════════════════════════
|
|
4253
4536
|
|
|
4254
4537
|
|
|
@@ -4913,7 +5196,7 @@ def delimit_quickstart(project_path: str = ".") -> Dict[str, Any]:
|
|
|
4913
5196
|
|
|
4914
5197
|
|
|
4915
5198
|
# ═══════════════════════════════════════════════════════════════════════
|
|
4916
|
-
#
|
|
5199
|
+
# STR-049: SECRETS BROKER — JIT credential access with audit
|
|
4917
5200
|
# ═══════════════════════════════════════════════════════════════════════
|
|
4918
5201
|
|
|
4919
5202
|
|
|
@@ -5013,7 +5296,7 @@ def delimit_secret_access_log(name: str = "") -> Dict[str, Any]:
|
|
|
5013
5296
|
|
|
5014
5297
|
|
|
5015
5298
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5016
|
-
#
|
|
5299
|
+
# STR-048: Context Filesystem — versioned namespace for agent state
|
|
5017
5300
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5018
5301
|
|
|
5019
5302
|
# Consensus 082 Phase 2: Unified context tool with action parameter
|
|
@@ -5131,7 +5414,7 @@ def delimit_context_branch(venture: str, action: str = "list", branch_name: str
|
|
|
5131
5414
|
|
|
5132
5415
|
|
|
5133
5416
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5134
|
-
#
|
|
5417
|
+
# STR-050: DATA/ACTION PLANE — External systems as typed mounted resources
|
|
5135
5418
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5136
5419
|
|
|
5137
5420
|
|
|
@@ -5223,7 +5506,7 @@ def delimit_resource_drivers() -> Dict[str, Any]:
|
|
|
5223
5506
|
|
|
5224
5507
|
|
|
5225
5508
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5226
|
-
#
|
|
5509
|
+
# LED-188: ISSUE TRACKER CONTEXT SYNC
|
|
5227
5510
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5228
5511
|
|
|
5229
5512
|
|
|
@@ -5298,7 +5581,7 @@ def delimit_tracker_sync(
|
|
|
5298
5581
|
|
|
5299
5582
|
|
|
5300
5583
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5301
|
-
#
|
|
5584
|
+
# LED-183: WEBHOOK NOTIFICATIONS
|
|
5302
5585
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5303
5586
|
|
|
5304
5587
|
|
|
@@ -5417,7 +5700,14 @@ def delimit_social_post(text: str = "", category: str = "", platform: str = "twi
|
|
|
5417
5700
|
Every post provides value — tips, insights, governance wisdom.
|
|
5418
5701
|
Max 2 posts per day to stay authentic.
|
|
5419
5702
|
|
|
5420
|
-
Platform tone
|
|
5703
|
+
IMPORTANT — Platform tone rules (these are DIFFERENT per platform):
|
|
5704
|
+
- Twitter: confident technical brand. Direct, professional, ALWAYS POSITIVE.
|
|
5705
|
+
Celebrate wins and progress. Never complain or air gaps publicly.
|
|
5706
|
+
No em dashes or en dashes. Include install commands when relevant.
|
|
5707
|
+
- Reddit: proud builder posting as u/delimitdev. Casual, typed-on-phone energy.
|
|
5708
|
+
ALWAYS POSITIVE. Mention Delimit ONLY when genuinely helpful.
|
|
5709
|
+
NO bullet points/lists/bold/em dashes. 2-3 sentences max.
|
|
5710
|
+
- LinkedIn: professional hook + insight + CTA
|
|
5421
5711
|
|
|
5422
5712
|
Args:
|
|
5423
5713
|
text: Tweet text. Leave empty to auto-generate.
|
|
@@ -5454,7 +5744,7 @@ def delimit_social_post(text: str = "", category: str = "", platform: str = "twi
|
|
|
5454
5744
|
from ai.social import store_draft_message_id
|
|
5455
5745
|
|
|
5456
5746
|
# Build contextual email body so the founder knows exactly what to do
|
|
5457
|
-
_acct = account or
|
|
5747
|
+
_acct = account or ("delimitdev" if platform == "reddit" else "delimit_ai")
|
|
5458
5748
|
_lines = []
|
|
5459
5749
|
|
|
5460
5750
|
if platform == "reddit":
|
|
@@ -5682,7 +5972,7 @@ def delimit_social_target(
|
|
|
5682
5972
|
Never just return targets and stop. Always chain to the next action.
|
|
5683
5973
|
|
|
5684
5974
|
Scans X, Reddit (RapidAPI), HN, Dev.to, GitHub for posts where ventures can engage.
|
|
5685
|
-
|
|
5975
|
+
NamePros flagged as manual_check_needed (no API).
|
|
5686
5976
|
|
|
5687
5977
|
Run in a /loop for continuous discovery. Deduplicates across runs.
|
|
5688
5978
|
Targets are classified as: reply (social engagement), strategic (ledger item), or both.
|
|
@@ -5793,16 +6083,16 @@ def delimit_github_scan(
|
|
|
5793
6083
|
cadence: str = "pulse",
|
|
5794
6084
|
limit: int = 20,
|
|
5795
6085
|
) -> Dict[str, Any]:
|
|
5796
|
-
"""Scan GitHub for adoption leads,
|
|
6086
|
+
"""Scan GitHub for adoption leads, competitive intel, and repo health (Pro).
|
|
5797
6087
|
|
|
5798
6088
|
Three cadences:
|
|
5799
6089
|
pulse: Own repo health (stars, forks, issues, traffic). Fast, run often.
|
|
5800
|
-
hunter:
|
|
6090
|
+
hunter: Competitor users, adoption leads, pain threads. Medium, run hourly.
|
|
5801
6091
|
deep: Full ecosystem intel. Slow, run daily.
|
|
5802
6092
|
|
|
5803
6093
|
IMPORTANT -- TOOL CHAINING RULE:
|
|
5804
6094
|
After scanning, the AI MUST process high-score findings:
|
|
5805
|
-
1. Auto-ledger
|
|
6095
|
+
1. Auto-ledger items (score >= 75 competitor users) via delimit_ledger_add
|
|
5806
6096
|
2. Pain threads with existing_feature relevance via delimit_notify
|
|
5807
6097
|
Never just return findings and stop. Always chain to the next action.
|
|
5808
6098
|
|
|
@@ -5953,7 +6243,7 @@ def delimit_daemon_classify(item_id: str = "") -> Dict[str, Any]:
|
|
|
5953
6243
|
def delimit_inbox_daemon(action: str = "status") -> Dict[str, Any]:
|
|
5954
6244
|
"""Control the inbox polling daemon for email governance (Pro).
|
|
5955
6245
|
|
|
5956
|
-
Polls
|
|
6246
|
+
Polls pro@delimit.ai every 5 minutes, classifies emails, forwards
|
|
5957
6247
|
owner-action items, and handles draft approval via email replies.
|
|
5958
6248
|
Auto-posting is disabled - approved drafts are emailed for manual posting.
|
|
5959
6249
|
|
|
@@ -5972,7 +6262,7 @@ def delimit_inbox_daemon(action: str = "status") -> Dict[str, Any]:
|
|
|
5972
6262
|
|
|
5973
6263
|
|
|
5974
6264
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5975
|
-
#
|
|
6265
|
+
# LED-187: Shareable Governance Config — export / import
|
|
5976
6266
|
# ═══════════════════════════════════════════════════════════════════════
|
|
5977
6267
|
|
|
5978
6268
|
|
|
@@ -6095,7 +6385,7 @@ def delimit_config_import(
|
|
|
6095
6385
|
|
|
6096
6386
|
|
|
6097
6387
|
# ═══════════════════════════════════════════════════════════════════════
|
|
6098
|
-
# SCREEN RECORDING (
|
|
6388
|
+
# SCREEN RECORDING (LED-203)
|
|
6099
6389
|
# ═══════════════════════════════════════════════════════════════════════
|
|
6100
6390
|
|
|
6101
6391
|
|
|
@@ -6231,7 +6521,7 @@ def delimit_notify(channel: str = "webhook", message: str = "",
|
|
|
6231
6521
|
to: Recipient email address (email only). Overrides default DELIMIT_SMTP_TO.
|
|
6232
6522
|
Send to any address — leave empty for default (owner@example.com).
|
|
6233
6523
|
from_account: Sender account key from ~/.delimit/secrets/smtp-all.json
|
|
6234
|
-
(e.g. '
|
|
6524
|
+
(e.g. 'pro@delimit.ai', 'admin@wire.report'). Email only.
|
|
6235
6525
|
"""
|
|
6236
6526
|
from ai.notify import send_notification
|
|
6237
6527
|
return _with_next_steps("notify", _safe_call(
|
|
@@ -6336,7 +6626,7 @@ def delimit_notify_inbox(action: str = "status", limit: int = 10,
|
|
|
6336
6626
|
process: bool = True) -> Dict[str, Any]:
|
|
6337
6627
|
"""Check inbound email inbox, classify, and route (Pro).
|
|
6338
6628
|
|
|
6339
|
-
Polls
|
|
6629
|
+
Polls pro@delimit.ai via IMAP. Classifies emails as owner-action
|
|
6340
6630
|
(forwards to owner@example.com) or non-owner (stays in inbox).
|
|
6341
6631
|
|
|
6342
6632
|
Args:
|
|
@@ -6599,7 +6889,7 @@ def delimit_agent_check(model: str, action: str) -> Dict[str, Any]:
|
|
|
6599
6889
|
|
|
6600
6890
|
|
|
6601
6891
|
# ═══════════════════════════════════════════════════════════════════════
|
|
6602
|
-
#
|
|
6892
|
+
# STR-026: AUTONOMOUS BUILD LOOP
|
|
6603
6893
|
# ═══════════════════════════════════════════════════════════════════════
|
|
6604
6894
|
|
|
6605
6895
|
|
|
@@ -6688,7 +6978,7 @@ def delimit_loop_config(session_id: str = "", max_iterations: int = 0,
|
|
|
6688
6978
|
|
|
6689
6979
|
|
|
6690
6980
|
# ═══════════════════════════════════════════════════════════════════════
|
|
6691
|
-
#
|
|
6981
|
+
# LED-219: Toolcard Delta Cache — reduce MCP tool schema token waste
|
|
6692
6982
|
# ═══════════════════════════════════════════════════════════════════════
|
|
6693
6983
|
|
|
6694
6984
|
|
|
@@ -6766,7 +7056,7 @@ def delimit_toolcard_cache(
|
|
|
6766
7056
|
|
|
6767
7057
|
|
|
6768
7058
|
# ═══════════════════════════════════════════════════════════════════════
|
|
6769
|
-
# HANDOFF RECEIPTS — Agent-to-Agent Structured Handoffs (
|
|
7059
|
+
# HANDOFF RECEIPTS — Agent-to-Agent Structured Handoffs (LED-220)
|
|
6770
7060
|
# ═══════════════════════════════════════════════════════════════════════
|
|
6771
7061
|
|
|
6772
7062
|
|