delimit-cli 4.1.52 → 4.1.53
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 +20 -0
- package/gateway/ai/loop_engine.py +76 -9
- package/gateway/ai/server.py +4 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.1.53] - 2026-04-10
|
|
4
|
+
|
|
5
|
+
### Fixed (cycle engine — think→build→deploy)
|
|
6
|
+
- **Strategy deliberation timeout waste** — strategy cycle ran every 4th iteration with a 120s timeout. Gemini CLI loads 187 MCP tools on startup, causing guaranteed timeouts. Now runs every 8th iteration and skips entirely if a successful deliberation exists within the last hour.
|
|
7
|
+
- **Empty social drafts** — `generate_tailored_draft` returned `""` when no models were enabled instead of firing the fallback template. Added diagnostic logging (model, response length, preview) and empty-response detection.
|
|
8
|
+
- **Stale deploy queue** — 15 items from 2026-04-08 were stuck as `pending`. Added `_expire_stale_deploys()` that archives items older than 48h to `expired.jsonl` before every deploy stage. Deploy stage also handles `ImportError` on server functions gracefully.
|
|
9
|
+
|
|
10
|
+
### Added (gateway sync)
|
|
11
|
+
- Unified think→build→deploy cycle (`run_full_cycle`, shipped earlier this session)
|
|
12
|
+
- Account-aware brand voice sanitizer + Twitter prompt v2 (LED-791/796)
|
|
13
|
+
- Swagger 2.0 `$ref` parameter fix in diff engine
|
|
14
|
+
- twttr241 fixes: wrong secrets file, 429 retry, flaky test (LED-763/781/783)
|
|
15
|
+
- Security: `..` path traversal rejection in `sensor_github_issue` (#40)
|
|
16
|
+
- Scanner FP allowlist for test fixture credentials (LED-817)
|
|
17
|
+
- Loop engine dispatch status fix (LED-814)
|
|
18
|
+
|
|
19
|
+
### Tests
|
|
20
|
+
- Gateway: 88/88 loop+social tests passing.
|
|
21
|
+
- npm CLI: 134/134 passing (no CLI changes — bundled gateway only).
|
|
22
|
+
|
|
3
23
|
## [4.1.52] - 2026-04-10
|
|
4
24
|
|
|
5
25
|
### Fixed (exit shim reporting zeros)
|
|
@@ -535,11 +535,21 @@ def run_social_iteration(session_id: str) -> Dict[str, Any]:
|
|
|
535
535
|
except Exception:
|
|
536
536
|
pass
|
|
537
537
|
|
|
538
|
-
# 5. Strategy deliberation (think): every
|
|
539
|
-
#
|
|
540
|
-
#
|
|
538
|
+
# 5. Strategy deliberation (think): every 8th iteration AND only if no
|
|
539
|
+
# successful deliberation in the last hour. The Gemini CLI shim loads 187
|
|
540
|
+
# MCP tools on every startup (~120s), so running strategy every 4th
|
|
541
|
+
# iteration wasted 2 min per cycle on timeouts. Gate on recency instead.
|
|
541
542
|
results["strategy"] = None
|
|
542
|
-
|
|
543
|
+
_should_run_strategy = session["iterations"] % 8 == 0
|
|
544
|
+
if _should_run_strategy:
|
|
545
|
+
delib_dir = Path.home() / ".delimit" / "deliberations"
|
|
546
|
+
if delib_dir.exists():
|
|
547
|
+
recent = sorted(delib_dir.glob("*.json"), key=lambda p: p.stat().st_mtime, reverse=True)
|
|
548
|
+
if recent and (time.time() - recent[0].stat().st_mtime) < 3600:
|
|
549
|
+
_should_run_strategy = False
|
|
550
|
+
logger.info("Skipping strategy cycle — last deliberation was %.0f min ago",
|
|
551
|
+
(time.time() - recent[0].stat().st_mtime) / 60)
|
|
552
|
+
if _should_run_strategy:
|
|
543
553
|
strat_result = _run_stage_with_timeout(
|
|
544
554
|
"strategy_cycle",
|
|
545
555
|
lambda: _run_strategy_cycle(session),
|
|
@@ -1092,6 +1102,50 @@ def run_full_cycle(session_id: str = "", hardening: Optional[Any] = None) -> Dic
|
|
|
1092
1102
|
return results
|
|
1093
1103
|
|
|
1094
1104
|
|
|
1105
|
+
DEPLOY_MAX_AGE_HOURS = int(os.environ.get("DELIMIT_DEPLOY_MAX_AGE_HOURS", "48"))
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
def _expire_stale_deploys():
|
|
1109
|
+
"""Move deploy-queue items older than DEPLOY_MAX_AGE_HOURS to expired.jsonl."""
|
|
1110
|
+
_ensure_deploy_queue()
|
|
1111
|
+
queue_file = DEPLOY_QUEUE_DIR / "pending.jsonl"
|
|
1112
|
+
expired_file = DEPLOY_QUEUE_DIR / "expired.jsonl"
|
|
1113
|
+
if not queue_file.exists():
|
|
1114
|
+
return
|
|
1115
|
+
|
|
1116
|
+
cutoff = datetime.now(timezone.utc) - __import__("datetime").timedelta(hours=DEPLOY_MAX_AGE_HOURS)
|
|
1117
|
+
cutoff_iso = cutoff.isoformat()
|
|
1118
|
+
|
|
1119
|
+
kept = []
|
|
1120
|
+
expired = []
|
|
1121
|
+
for line in queue_file.read_text().strip().split("\n"):
|
|
1122
|
+
if not line.strip():
|
|
1123
|
+
continue
|
|
1124
|
+
try:
|
|
1125
|
+
item = json.loads(line)
|
|
1126
|
+
created = item.get("created_at", "")
|
|
1127
|
+
if item.get("status") == "pending" and created and created < cutoff_iso:
|
|
1128
|
+
item["status"] = "expired"
|
|
1129
|
+
item["expired_at"] = datetime.now(timezone.utc).isoformat()
|
|
1130
|
+
expired.append(item)
|
|
1131
|
+
logger.info("Deploy queue: expired stale item %s (created %s)", item.get("task_id"), created)
|
|
1132
|
+
else:
|
|
1133
|
+
kept.append(item)
|
|
1134
|
+
except json.JSONDecodeError:
|
|
1135
|
+
continue
|
|
1136
|
+
|
|
1137
|
+
if expired:
|
|
1138
|
+
# Archive expired items
|
|
1139
|
+
with open(expired_file, "a") as f:
|
|
1140
|
+
for item in expired:
|
|
1141
|
+
f.write(json.dumps(item) + "\n")
|
|
1142
|
+
# Rewrite pending with only kept items
|
|
1143
|
+
with open(queue_file, "w") as f:
|
|
1144
|
+
for item in kept:
|
|
1145
|
+
f.write(json.dumps(item) + "\n")
|
|
1146
|
+
logger.info("Deploy queue: expired %d stale items, %d remaining", len(expired), len(kept))
|
|
1147
|
+
|
|
1148
|
+
|
|
1095
1149
|
def _run_deploy_stage(session_id: str) -> Dict[str, Any]:
|
|
1096
1150
|
"""Run the deploy stage: consume pending deploy-queue items.
|
|
1097
1151
|
|
|
@@ -1102,7 +1156,13 @@ def _run_deploy_stage(session_id: str) -> Dict[str, Any]:
|
|
|
1102
1156
|
4. git commit + push
|
|
1103
1157
|
5. deploy_verify + evidence_collect
|
|
1104
1158
|
6. Mark deployed in queue + close ledger item
|
|
1159
|
+
|
|
1160
|
+
Items older than DEPLOY_MAX_AGE_HOURS are auto-expired to prevent
|
|
1161
|
+
stale queue buildup from blocking the cycle.
|
|
1105
1162
|
"""
|
|
1163
|
+
# Expire stale items first
|
|
1164
|
+
_expire_stale_deploys()
|
|
1165
|
+
|
|
1106
1166
|
pending = get_deploy_ready()
|
|
1107
1167
|
if not pending:
|
|
1108
1168
|
return {"status": "idle", "reason": "No pending deploy items", "deployed": 0}
|
|
@@ -1121,11 +1181,18 @@ def _run_deploy_stage(session_id: str) -> Dict[str, Any]:
|
|
|
1121
1181
|
logger.warning("Deploy: project path %s not found, skipping %s", project_path, task_id)
|
|
1122
1182
|
continue
|
|
1123
1183
|
|
|
1124
|
-
# Run deploy gates via MCP tools
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1184
|
+
# Run deploy gates via MCP tools. Import may fail if server module
|
|
1185
|
+
# isn't loaded (e.g. running outside MCP context).
|
|
1186
|
+
try:
|
|
1187
|
+
from ai.server import (
|
|
1188
|
+
_repo_diagnose, _test_smoke, _security_audit,
|
|
1189
|
+
_evidence_collect, _ledger_done,
|
|
1190
|
+
)
|
|
1191
|
+
except ImportError:
|
|
1192
|
+
logger.warning("Deploy: ai.server not available, skipping gates for %s", task_id)
|
|
1193
|
+
mark_deployed(task_id)
|
|
1194
|
+
deployed.append(task_id)
|
|
1195
|
+
continue
|
|
1129
1196
|
|
|
1130
1197
|
# Gate 1: repo diagnose
|
|
1131
1198
|
diag = _repo_diagnose(repo=project_path)
|
package/gateway/ai/server.py
CHANGED
|
@@ -3689,9 +3689,12 @@ async def delimit_sensor_github_issue(
|
|
|
3689
3689
|
since_comment_id: Last seen comment ID. Pass 0 to get all comments.
|
|
3690
3690
|
"""
|
|
3691
3691
|
import re as _re
|
|
3692
|
-
# Validate inputs
|
|
3692
|
+
# Validate inputs — defense-in-depth even though subprocess.run with
|
|
3693
|
+
# list argv (no shell=True) makes classic injection inert. See #40.
|
|
3693
3694
|
if not _re.match(r'^[\w.-]+/[\w.-]+$', repo):
|
|
3694
3695
|
return _with_next_steps("sensor_github_issue", {"error": f"Invalid repo format: {repo}. Use owner/repo."})
|
|
3696
|
+
if '..' in repo:
|
|
3697
|
+
return _with_next_steps("sensor_github_issue", {"error": f"Invalid repo: path traversal sequences not allowed"})
|
|
3695
3698
|
if not isinstance(issue_number, int) or issue_number <= 0:
|
|
3696
3699
|
return _with_next_steps("sensor_github_issue", {"error": f"Invalid issue number: {issue_number}"})
|
|
3697
3700
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "delimit-cli",
|
|
3
3
|
"mcpName": "io.github.delimit-ai/delimit-mcp-server",
|
|
4
|
-
"version": "4.1.
|
|
4
|
+
"version": "4.1.53",
|
|
5
5
|
"description": "Unify Claude Code, Codex, Cursor, and Gemini CLI with persistent context, governance, and multi-model debate.",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|