delimit-cli 3.15.12 → 3.15.13
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 +13 -0
- package/gateway/ai/activate_helpers.py +210 -0
- package/gateway/ai/content_engine.py +2 -7
- 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/key_resolver.py +2 -7
- package/gateway/ai/notify.py +4 -4
- package/gateway/ai/reddit_scanner.py +562 -0
- package/gateway/ai/secrets_broker.py +232 -4
- package/gateway/ai/server.py +9 -1
- package/gateway/ai/session_phoenix.py +371 -0
- package/gateway/ai/supabase_sync.py +2 -7
- package/gateway/ai/swarm.py +106 -0
- package/gateway/ai/tool_metadata.py +34 -6
- package/gateway/ai/toolcard_cache.py +327 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.15.13] - 2026-03-29
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Self-extending swarm**: Architect and Senior Dev agents can create new MCP tools at runtime
|
|
7
|
+
- **Tool security scan**: Block dangerous patterns (subprocess, exec, eval, socket) in custom tools
|
|
8
|
+
- **8 new modules**: activate_helpers, cross_model_audit, github_scanner, handoff_receipts, reddit_scanner, session_phoenix, social_target, toolcard_cache
|
|
9
|
+
- **Reviewer approval gate**: Custom tools require reviewer sign-off before activation
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Swarm actions expanded: create_tool, list_tools now available via delimit_swarm
|
|
13
|
+
- Inbox daemon: enhanced email classification and approval routing
|
|
14
|
+
- Social pipeline: improved content generation and scheduling
|
|
15
|
+
|
|
3
16
|
## [3.15.9] - 2026-03-30
|
|
4
17
|
|
|
5
18
|
### Added
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""LED-269 / LED-270: Activation checklist helpers.
|
|
2
|
+
|
|
3
|
+
Extracted so they can be tested independently of ai.server (which has
|
|
4
|
+
heavy MCP decorator dependencies).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def activate_auto_permissions(auto_permissions: bool) -> dict:
|
|
14
|
+
"""LED-269: Detect AI assistant and auto-configure Delimit tool permissions.
|
|
15
|
+
|
|
16
|
+
Returns a checklist entry dict with item/status/detail.
|
|
17
|
+
"""
|
|
18
|
+
home = Path.home()
|
|
19
|
+
assistant = None
|
|
20
|
+
config_path = None
|
|
21
|
+
|
|
22
|
+
# Detect which assistant is running
|
|
23
|
+
if os.environ.get("CLAUDE_CODE") or (home / ".claude").is_dir():
|
|
24
|
+
assistant = "claude_code"
|
|
25
|
+
config_path = home / ".claude" / "settings.json"
|
|
26
|
+
elif (home / ".codex" / "config.toml").exists():
|
|
27
|
+
assistant = "codex"
|
|
28
|
+
config_path = home / ".codex" / "config.toml"
|
|
29
|
+
elif (home / ".gemini" / "settings.json").exists():
|
|
30
|
+
assistant = "gemini"
|
|
31
|
+
config_path = home / ".gemini" / "settings.json"
|
|
32
|
+
else:
|
|
33
|
+
if os.environ.get("CODEX_CLI"):
|
|
34
|
+
assistant = "codex"
|
|
35
|
+
config_path = home / ".codex" / "config.toml"
|
|
36
|
+
|
|
37
|
+
if not assistant:
|
|
38
|
+
return {"item": "Permissions", "status": "Skip (no assistant)", "detail": "No AI assistant detected"}
|
|
39
|
+
|
|
40
|
+
if not auto_permissions:
|
|
41
|
+
return {"item": "Permissions", "status": "Skip (manual)", "detail": f"Detected {assistant} — auto-config disabled"}
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
if assistant == "claude_code":
|
|
45
|
+
return configure_claude_code_permissions(config_path)
|
|
46
|
+
elif assistant == "codex":
|
|
47
|
+
return configure_codex_permissions(config_path)
|
|
48
|
+
elif assistant == "gemini":
|
|
49
|
+
return {"item": "Permissions", "status": "Skip (manual)", "detail": "Gemini CLI — configure permissions manually"}
|
|
50
|
+
except Exception as e:
|
|
51
|
+
return {"item": "Permissions", "status": "Fail", "detail": f"Auto-config failed: {e}"}
|
|
52
|
+
|
|
53
|
+
return {"item": "Permissions", "status": "Skip (manual)", "detail": f"Detected {assistant}"}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def configure_claude_code_permissions(config_path: Path) -> dict:
|
|
57
|
+
"""Add mcp__delimit__* to Claude Code permissions.allow if not present."""
|
|
58
|
+
permission_pattern = "mcp__delimit__*"
|
|
59
|
+
|
|
60
|
+
if config_path.exists():
|
|
61
|
+
try:
|
|
62
|
+
data = json.loads(config_path.read_text())
|
|
63
|
+
except (json.JSONDecodeError, OSError):
|
|
64
|
+
data = {}
|
|
65
|
+
else:
|
|
66
|
+
data = {}
|
|
67
|
+
|
|
68
|
+
permissions = data.setdefault("permissions", {})
|
|
69
|
+
allow_list = permissions.setdefault("allow", [])
|
|
70
|
+
|
|
71
|
+
if any(permission_pattern in str(entry) for entry in allow_list):
|
|
72
|
+
return {"item": "Permissions", "status": "Pass", "detail": f"Claude Code: {permission_pattern} already in settings"}
|
|
73
|
+
|
|
74
|
+
allow_list.append(permission_pattern)
|
|
75
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
config_path.write_text(json.dumps(data, indent=2))
|
|
77
|
+
return {"item": "Permissions", "status": "Pass", "detail": f"Claude Code: added {permission_pattern} to {config_path}"}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def configure_codex_permissions(config_path: Path) -> dict:
|
|
81
|
+
"""Set trust_level to trusted for Delimit in Codex config.toml."""
|
|
82
|
+
if config_path.exists():
|
|
83
|
+
content = config_path.read_text()
|
|
84
|
+
if "trust_level" in content and "trusted" in content:
|
|
85
|
+
return {"item": "Permissions", "status": "Pass", "detail": "Codex: already trusted"}
|
|
86
|
+
else:
|
|
87
|
+
content = ""
|
|
88
|
+
|
|
89
|
+
if "[delimit]" not in content:
|
|
90
|
+
addition = '\n[delimit]\ntrust_level = "trusted"\n'
|
|
91
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
with open(config_path, "a") as f:
|
|
93
|
+
f.write(addition)
|
|
94
|
+
return {"item": "Permissions", "status": "Pass", "detail": f"Codex: added trust_level=trusted to {config_path}"}
|
|
95
|
+
else:
|
|
96
|
+
return {"item": "Permissions", "status": "Pass", "detail": "Codex: delimit section exists"}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def build_checklist(
|
|
100
|
+
license_key: str,
|
|
101
|
+
project_path: str,
|
|
102
|
+
auto_permissions: bool,
|
|
103
|
+
) -> Dict[str, Any]:
|
|
104
|
+
"""Build the activation checklist. Core logic extracted from delimit_activate.
|
|
105
|
+
|
|
106
|
+
Returns the result dict (without next_steps wrapping).
|
|
107
|
+
"""
|
|
108
|
+
from ai.license import activate_license, get_license, is_premium, require_premium
|
|
109
|
+
|
|
110
|
+
checklist: list = []
|
|
111
|
+
p = Path(project_path).resolve()
|
|
112
|
+
|
|
113
|
+
# --- Step 1: License activation (if key provided) ---
|
|
114
|
+
if license_key:
|
|
115
|
+
lic_result = activate_license(license_key)
|
|
116
|
+
if lic_result.get("status") == "activated":
|
|
117
|
+
checklist.append({"item": "License activation", "status": "Pass", "detail": f"Tier: {lic_result.get('tier', 'pro')}"})
|
|
118
|
+
else:
|
|
119
|
+
checklist.append({"item": "License activation", "status": "Fail", "detail": lic_result.get("error", "Unknown error")})
|
|
120
|
+
else:
|
|
121
|
+
lic = get_license()
|
|
122
|
+
tier = lic.get("tier", "free")
|
|
123
|
+
checklist.append({"item": "License status", "status": "Pass", "detail": f"Tier: {tier}"})
|
|
124
|
+
|
|
125
|
+
# --- Step 2: MCP server reachable ---
|
|
126
|
+
checklist.append({"item": "MCP server", "status": "Pass", "detail": "Server responding"})
|
|
127
|
+
|
|
128
|
+
# --- Step 3: Python dependencies ---
|
|
129
|
+
dep_ok = True
|
|
130
|
+
for pkg in ["yaml", "pydantic", "packaging", "fastmcp"]:
|
|
131
|
+
try:
|
|
132
|
+
__import__(pkg)
|
|
133
|
+
except ImportError:
|
|
134
|
+
dep_ok = False
|
|
135
|
+
checklist.append({"item": f"Dependency: {pkg}", "status": "Fail", "detail": f"pip install {pkg}"})
|
|
136
|
+
if dep_ok:
|
|
137
|
+
checklist.append({"item": "Dependencies", "status": "Pass", "detail": "All required packages installed"})
|
|
138
|
+
|
|
139
|
+
# --- Step 4: Governance initialized ---
|
|
140
|
+
delimit_dir = p / ".delimit"
|
|
141
|
+
policies = delimit_dir / "policies.yml"
|
|
142
|
+
if delimit_dir.is_dir() and policies.is_file():
|
|
143
|
+
checklist.append({"item": "Governance", "status": "Pass", "detail": f"Initialized at {delimit_dir}"})
|
|
144
|
+
elif delimit_dir.is_dir():
|
|
145
|
+
checklist.append({"item": "Governance", "status": "Fail", "detail": "Missing policies.yml — run delimit_init"})
|
|
146
|
+
else:
|
|
147
|
+
checklist.append({"item": "Governance", "status": "Fail", "detail": "Not initialized — run delimit_init"})
|
|
148
|
+
|
|
149
|
+
# --- Step 5: Test smoke (skip if no framework) ---
|
|
150
|
+
try:
|
|
151
|
+
from backends.tools_real import test_smoke as _test_smoke_fn
|
|
152
|
+
smoke = _test_smoke_fn(project_path=str(p))
|
|
153
|
+
if smoke.get("status") == "no_framework":
|
|
154
|
+
checklist.append({"item": "Test smoke", "status": "Skip (no tests)", "detail": "No test framework detected"})
|
|
155
|
+
elif smoke.get("error"):
|
|
156
|
+
checklist.append({"item": "Test smoke", "status": "Fail", "detail": smoke.get("error", "")})
|
|
157
|
+
else:
|
|
158
|
+
passed_count = smoke.get("passed", 0)
|
|
159
|
+
failed_count = smoke.get("failed", 0)
|
|
160
|
+
if failed_count == 0:
|
|
161
|
+
checklist.append({"item": "Test smoke", "status": "Pass", "detail": f"{passed_count} tests passed"})
|
|
162
|
+
else:
|
|
163
|
+
checklist.append({"item": "Test smoke", "status": "Fail", "detail": f"{passed_count} passed, {failed_count} failed"})
|
|
164
|
+
except Exception as e:
|
|
165
|
+
checklist.append({"item": "Test smoke", "status": "Skip (no tests)", "detail": f"Could not run: {e}"})
|
|
166
|
+
|
|
167
|
+
# --- Step 6: AI assistant detection + permission auto-config (LED-269) ---
|
|
168
|
+
perm_result = activate_auto_permissions(auto_permissions)
|
|
169
|
+
checklist.append(perm_result)
|
|
170
|
+
|
|
171
|
+
# --- Step 7: Premium feature checks (skip on free tier) ---
|
|
172
|
+
premium_checks = [
|
|
173
|
+
("Deliberation (multi-model)", "delimit_deliberate"),
|
|
174
|
+
("Security audit", "delimit_security_ingest"),
|
|
175
|
+
("Deploy pipeline", "delimit_deploy_plan"),
|
|
176
|
+
("Cost analysis", "delimit_cost_analyze"),
|
|
177
|
+
("Release management", "delimit_release_plan"),
|
|
178
|
+
("Agent orchestration", "delimit_agent_dispatch"),
|
|
179
|
+
]
|
|
180
|
+
for label, tool_name in premium_checks:
|
|
181
|
+
gate = require_premium(tool_name)
|
|
182
|
+
if gate is None:
|
|
183
|
+
checklist.append({"item": label, "status": "Pass", "detail": "Pro feature unlocked"})
|
|
184
|
+
else:
|
|
185
|
+
checklist.append({"item": label, "status": "Skip (Pro)", "detail": "Requires Delimit Pro"})
|
|
186
|
+
|
|
187
|
+
# --- Score: only count applicable checks (exclude skips) ---
|
|
188
|
+
applicable = [c for c in checklist if not c["status"].startswith("Skip")]
|
|
189
|
+
passed_total = sum(1 for c in applicable if c["status"] == "Pass")
|
|
190
|
+
total = len(applicable)
|
|
191
|
+
score = f"{passed_total}/{total}"
|
|
192
|
+
|
|
193
|
+
result: Dict[str, Any] = {
|
|
194
|
+
"tool": "activate",
|
|
195
|
+
"status": "complete",
|
|
196
|
+
"score": score,
|
|
197
|
+
"passed": passed_total,
|
|
198
|
+
"total": total,
|
|
199
|
+
"skipped": len(checklist) - total,
|
|
200
|
+
"checklist": checklist,
|
|
201
|
+
"tier": get_license().get("tier", "free"),
|
|
202
|
+
"project": str(p),
|
|
203
|
+
}
|
|
204
|
+
if passed_total == total and total > 0:
|
|
205
|
+
result["message"] = f"All {total} checks passed. Delimit is fully operational."
|
|
206
|
+
elif passed_total < total:
|
|
207
|
+
failed_items = [c["item"] for c in applicable if c["status"] == "Fail"]
|
|
208
|
+
result["message"] = f"{passed_total}/{total} checks passed. Fix: {', '.join(failed_items)}"
|
|
209
|
+
|
|
210
|
+
return result
|
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
This module requires the Delimit MCP server to be running.
|
|
4
|
-
Configure via: npx delimit-cli setup
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
# Stub — full implementation runs server-side
|
|
1
|
+
# content_engine — Pro module (stubbed in npm package)
|
|
2
|
+
# Full implementation available on delimit.ai server
|