delimit-cli 4.1.53 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +34 -3
- package/bin/delimit-cli.js +150 -2
- package/bin/delimit-setup.js +22 -7
- package/gateway/ai/agent_dispatch.py +79 -0
- package/gateway/ai/daily_digest.py +386 -0
- package/gateway/ai/ledger_manager.py +32 -0
- package/gateway/ai/license_core.py +2 -0
- package/gateway/ai/notify.py +17 -11
- package/gateway/ai/reddit_proxy.py +28 -9
- package/gateway/ai/sensing/__init__.py +35 -0
- package/gateway/ai/sensing/schema.py +107 -0
- package/gateway/ai/sensing/signal_store.py +348 -0
- package/gateway/ai/server.py +419 -6
- package/gateway/ai/supabase_sync.py +308 -0
- package/gateway/ai/work_order.py +216 -0
- package/gateway/ai/workers/__init__.py +32 -0
- package/gateway/ai/workers/base.py +154 -0
- package/gateway/ai/workers/executor.py +861 -0
- package/gateway/ai/workers/outreach_drafter.py +161 -0
- package/gateway/ai/workers/pr_drafter.py +148 -0
- package/lib/ai-sbom-engine.js +154 -0
- package/lib/trust-page-engine.js +179 -0
- package/lib/wrap-engine.js +431 -0
- package/package.json +14 -1
- package/adapters/codex-security.js +0 -64
- package/adapters/codex-skill.js +0 -78
- package/adapters/cursor-rules.js +0 -73
- package/gateway/ai/continuity.py +0 -462
- package/gateway/ai/inbox_daemon_runner.py +0 -217
- package/gateway/ai/loop_engine.py +0 -1303
- package/gateway/ai/social_cache.py +0 -341
- package/gateway/ai/social_daemon.py +0 -483
- package/gateway/ai/tweet_corpus_schema.sql +0 -76
- package/scripts/crosspost_devto.py +0 -304
- package/scripts/demo-v420-clean.sh +0 -267
- package/scripts/demo-v420-deliberation.sh +0 -217
- package/scripts/demo-v420.sh +0 -55
- package/scripts/sync-gateway.sh +0 -112
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Base worker class — defines the bounded capability surface.
|
|
2
|
+
|
|
3
|
+
Every worker:
|
|
4
|
+
- Receives a ledger item as input
|
|
5
|
+
- Has access to READ-ONLY tools only (lint, diff, grep, read)
|
|
6
|
+
- Produces a WorkerResult containing a work-order artifact
|
|
7
|
+
- Cannot call state-changing tools (write, commit, push, notify)
|
|
8
|
+
- Records its work in the audit trail
|
|
9
|
+
|
|
10
|
+
The capability boundary is enforced by the ALLOWED_TOOLS whitelist.
|
|
11
|
+
Workers that try to call tools outside the whitelist get an error,
|
|
12
|
+
not a silent fallback.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
import time
|
|
20
|
+
from abc import ABC, abstractmethod
|
|
21
|
+
from dataclasses import dataclass, field, asdict
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any, Dict, List, Optional
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger("delimit.workers")
|
|
26
|
+
|
|
27
|
+
# Read-only tool whitelist — workers CANNOT call anything else.
|
|
28
|
+
# This is the "sandboxed" property from the swarm charter.
|
|
29
|
+
ALLOWED_TOOLS = frozenset({
|
|
30
|
+
"delimit_lint",
|
|
31
|
+
"delimit_diff",
|
|
32
|
+
"delimit_semver",
|
|
33
|
+
"delimit_spec_health",
|
|
34
|
+
"delimit_repo_analyze",
|
|
35
|
+
"delimit_sense",
|
|
36
|
+
"delimit_ledger_query",
|
|
37
|
+
"delimit_ledger_context",
|
|
38
|
+
"delimit_memory_search",
|
|
39
|
+
"delimit_memory_recent",
|
|
40
|
+
"delimit_intel_query",
|
|
41
|
+
"delimit_gov_health",
|
|
42
|
+
# File system reads (not MCP tools, used via subprocess)
|
|
43
|
+
"grep",
|
|
44
|
+
"read_file",
|
|
45
|
+
"glob",
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
# Explicitly DENIED tools — existence check, not exhaustive.
|
|
49
|
+
# Workers hitting these get a clear error message.
|
|
50
|
+
DENIED_TOOLS = frozenset({
|
|
51
|
+
"delimit_ledger_add",
|
|
52
|
+
"delimit_ledger_update",
|
|
53
|
+
"delimit_ledger_done",
|
|
54
|
+
"delimit_memory_store",
|
|
55
|
+
"delimit_notify",
|
|
56
|
+
"delimit_social_post",
|
|
57
|
+
"delimit_deploy_publish",
|
|
58
|
+
"delimit_deploy_site",
|
|
59
|
+
"delimit_secret_store",
|
|
60
|
+
"write_file",
|
|
61
|
+
"edit_file",
|
|
62
|
+
"bash",
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
AUDIT_DIR = Path.home() / ".delimit" / "workers" / "audit"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class WorkerResult:
|
|
70
|
+
"""The output of a worker execution."""
|
|
71
|
+
worker_type: str
|
|
72
|
+
ledger_item_id: str
|
|
73
|
+
success: bool
|
|
74
|
+
artifact_path: str = ""
|
|
75
|
+
artifact_preview: str = ""
|
|
76
|
+
work_order_id: str = ""
|
|
77
|
+
error: str = ""
|
|
78
|
+
tools_called: List[str] = field(default_factory=list)
|
|
79
|
+
duration_seconds: float = 0.0
|
|
80
|
+
timestamp: str = ""
|
|
81
|
+
|
|
82
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
83
|
+
return asdict(self)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class Worker(ABC):
|
|
87
|
+
"""Base class for all read-only workers."""
|
|
88
|
+
|
|
89
|
+
worker_type: str = "base"
|
|
90
|
+
description: str = "Base worker"
|
|
91
|
+
|
|
92
|
+
def __init__(self):
|
|
93
|
+
self._tools_called: List[str] = []
|
|
94
|
+
|
|
95
|
+
def check_tool_allowed(self, tool_name: str) -> bool:
|
|
96
|
+
"""Check if a tool is in the worker's allowed set."""
|
|
97
|
+
if tool_name in DENIED_TOOLS:
|
|
98
|
+
logger.warning("Worker %s attempted denied tool: %s", self.worker_type, tool_name)
|
|
99
|
+
return False
|
|
100
|
+
return tool_name in ALLOWED_TOOLS
|
|
101
|
+
|
|
102
|
+
def call_tool(self, tool_name: str, **kwargs) -> Any:
|
|
103
|
+
"""Call a tool through the bounded surface. Raises if denied."""
|
|
104
|
+
if not self.check_tool_allowed(tool_name):
|
|
105
|
+
raise PermissionError(
|
|
106
|
+
f"Worker '{self.worker_type}' cannot call '{tool_name}'. "
|
|
107
|
+
f"Allowed: {sorted(ALLOWED_TOOLS)}"
|
|
108
|
+
)
|
|
109
|
+
self._tools_called.append(tool_name)
|
|
110
|
+
# Import and call the tool from the server module
|
|
111
|
+
from ai import server as srv
|
|
112
|
+
fn = getattr(srv, f"delimit_{tool_name}" if not tool_name.startswith("delimit_") else tool_name, None)
|
|
113
|
+
if fn is None:
|
|
114
|
+
raise ValueError(f"Tool '{tool_name}' not found in server module")
|
|
115
|
+
return fn(**kwargs)
|
|
116
|
+
|
|
117
|
+
@abstractmethod
|
|
118
|
+
def execute(self, ledger_item: Dict[str, Any]) -> WorkerResult:
|
|
119
|
+
"""Execute the worker's task on a ledger item.
|
|
120
|
+
|
|
121
|
+
Must return a WorkerResult with an artifact (work order).
|
|
122
|
+
Must NOT modify any state — output only.
|
|
123
|
+
"""
|
|
124
|
+
...
|
|
125
|
+
|
|
126
|
+
def run(self, ledger_item: Dict[str, Any]) -> WorkerResult:
|
|
127
|
+
"""Run the worker with timing + audit trail."""
|
|
128
|
+
start = time.time()
|
|
129
|
+
try:
|
|
130
|
+
result = self.execute(ledger_item)
|
|
131
|
+
except Exception as e:
|
|
132
|
+
result = WorkerResult(
|
|
133
|
+
worker_type=self.worker_type,
|
|
134
|
+
ledger_item_id=ledger_item.get("id", "?"),
|
|
135
|
+
success=False,
|
|
136
|
+
error=str(e),
|
|
137
|
+
)
|
|
138
|
+
result.duration_seconds = round(time.time() - start, 2)
|
|
139
|
+
result.timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
140
|
+
result.tools_called = self._tools_called.copy()
|
|
141
|
+
self._tools_called.clear()
|
|
142
|
+
|
|
143
|
+
# Audit trail
|
|
144
|
+
self._record_audit(result)
|
|
145
|
+
return result
|
|
146
|
+
|
|
147
|
+
def _record_audit(self, result: WorkerResult):
|
|
148
|
+
AUDIT_DIR.mkdir(parents=True, exist_ok=True)
|
|
149
|
+
audit_file = AUDIT_DIR / f"{self.worker_type}.jsonl"
|
|
150
|
+
try:
|
|
151
|
+
with audit_file.open("a") as f:
|
|
152
|
+
f.write(json.dumps(result.to_dict()) + "\n")
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.warning("Failed to write worker audit: %s", e)
|