delimit-cli 4.1.44 → 4.1.48
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 +22 -0
- package/bin/delimit-cli.js +365 -30
- package/bin/delimit-setup.js +115 -81
- package/gateway/ai/activate_helpers.py +253 -7
- package/gateway/ai/backends/gateway_core.py +236 -13
- package/gateway/ai/backends/repo_bridge.py +80 -16
- package/gateway/ai/backends/tools_infra.py +49 -32
- package/gateway/ai/checksums.sha256 +6 -0
- package/gateway/ai/continuity.py +462 -0
- package/gateway/ai/deliberation.pyi +53 -0
- package/gateway/ai/governance.pyi +32 -0
- package/gateway/ai/governance_hardening.py +569 -0
- package/gateway/ai/hot_reload.py +445 -0
- package/gateway/ai/inbox_daemon_runner.py +217 -0
- package/gateway/ai/ledger_manager.py +40 -0
- package/gateway/ai/license.py +104 -3
- package/gateway/ai/license_core.py +177 -36
- package/gateway/ai/license_core.pyi +50 -0
- package/gateway/ai/loop_engine.py +786 -22
- package/gateway/ai/reddit_scanner.py +150 -5
- package/gateway/ai/server.py +301 -19
- package/gateway/ai/swarm.py +87 -0
- package/gateway/ai/swarm_infra.py +656 -0
- package/gateway/ai/tweet_corpus_schema.sql +76 -0
- package/gateway/core/diff_engine_v2.py +6 -2
- package/gateway/core/generator_drift.py +242 -0
- package/gateway/core/json_schema_diff.py +375 -0
- package/gateway/core/openapi_version.py +124 -0
- package/gateway/core/spec_detector.py +47 -7
- package/gateway/core/spec_health.py +5 -2
- package/lib/cross-model-hooks.js +31 -17
- package/lib/delimit-template.js +19 -85
- package/package.json +9 -2
- package/scripts/sync-gateway.sh +13 -1
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Continuity State Resolution (LED-240).
|
|
3
|
+
|
|
4
|
+
Resolves user identity, project, venture, and private state namespace
|
|
5
|
+
at startup so all tools bind to the correct local state under ~/.delimit/.
|
|
6
|
+
|
|
7
|
+
Design:
|
|
8
|
+
- Single-user (root) today: namespace collapses to ~/.delimit/ (backwards compat)
|
|
9
|
+
- Multi-user (future): each user gets ~/.delimit/users/{user_hash}/
|
|
10
|
+
- Private state never leaks into git-tracked dirs, npm payloads, or public repos
|
|
11
|
+
|
|
12
|
+
Resolution chain:
|
|
13
|
+
resolve_user() -> whoami + git config + gh auth
|
|
14
|
+
resolve_project() -> git remote + .delimit/ dir + package.json/pyproject.toml
|
|
15
|
+
resolve_venture() -> map project to venture via ventures.json registry
|
|
16
|
+
resolve_namespace() -> compute private state path
|
|
17
|
+
auto_bind() -> run all, set env vars for downstream tools
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import hashlib
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
import os
|
|
24
|
+
import subprocess
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any, Dict, Optional
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger("delimit.continuity")
|
|
29
|
+
|
|
30
|
+
DELIMIT_HOME = Path.home() / ".delimit"
|
|
31
|
+
VENTURES_FILE = DELIMIT_HOME / "ventures.json"
|
|
32
|
+
|
|
33
|
+
# Directories that hold private continuity state (must never be git-tracked or published)
|
|
34
|
+
PRIVATE_STATE_DIRS = [
|
|
35
|
+
"souls",
|
|
36
|
+
"handoff_receipts",
|
|
37
|
+
"ledger",
|
|
38
|
+
"evidence",
|
|
39
|
+
"agent_actions",
|
|
40
|
+
"events",
|
|
41
|
+
"traces",
|
|
42
|
+
"deliberations",
|
|
43
|
+
"audit",
|
|
44
|
+
"audits",
|
|
45
|
+
"vault",
|
|
46
|
+
"secrets",
|
|
47
|
+
"credentials",
|
|
48
|
+
"context",
|
|
49
|
+
"continuity",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _run_cmd(args: list, timeout: int = 5) -> str:
|
|
54
|
+
"""Run a command and return stdout, or empty string on failure."""
|
|
55
|
+
try:
|
|
56
|
+
result = subprocess.run(
|
|
57
|
+
args,
|
|
58
|
+
capture_output=True,
|
|
59
|
+
text=True,
|
|
60
|
+
timeout=timeout,
|
|
61
|
+
)
|
|
62
|
+
if result.returncode == 0:
|
|
63
|
+
return result.stdout.strip()
|
|
64
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
65
|
+
pass
|
|
66
|
+
return ""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _stable_hash(value: str) -> str:
|
|
70
|
+
"""Deterministic short hash for namespace isolation."""
|
|
71
|
+
return hashlib.sha256(value.encode()).hexdigest()[:16]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ---- resolve_user -------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
def resolve_user() -> Dict[str, str]:
|
|
77
|
+
"""Detect the current user from OS, git config, and GitHub auth.
|
|
78
|
+
|
|
79
|
+
Returns a dict with:
|
|
80
|
+
- os_user: system username (whoami)
|
|
81
|
+
- git_email: git config user.email (may be empty)
|
|
82
|
+
- git_name: git config user.name (may be empty)
|
|
83
|
+
- gh_user: GitHub username from gh auth status (may be empty)
|
|
84
|
+
- user_hash: stable hash derived from the best available identity
|
|
85
|
+
- identity_source: which signal produced the hash
|
|
86
|
+
"""
|
|
87
|
+
os_user = os.environ.get("USER", "") or _run_cmd(["whoami"])
|
|
88
|
+
git_email = _run_cmd(["git", "config", "--global", "user.email"])
|
|
89
|
+
git_name = _run_cmd(["git", "config", "--global", "user.name"])
|
|
90
|
+
|
|
91
|
+
# gh auth status --active outputs the logged-in user
|
|
92
|
+
gh_user = ""
|
|
93
|
+
gh_raw = _run_cmd(["gh", "auth", "status"])
|
|
94
|
+
if gh_raw:
|
|
95
|
+
# Parse "Logged in to github.com account <user> ..."
|
|
96
|
+
for line in gh_raw.splitlines():
|
|
97
|
+
stripped = line.strip()
|
|
98
|
+
if "account" in stripped.lower():
|
|
99
|
+
parts = stripped.split()
|
|
100
|
+
for i, tok in enumerate(parts):
|
|
101
|
+
if tok.lower() == "account" and i + 1 < len(parts):
|
|
102
|
+
candidate = parts[i + 1].strip("()")
|
|
103
|
+
if candidate and candidate not in ("as", "to"):
|
|
104
|
+
gh_user = candidate
|
|
105
|
+
break
|
|
106
|
+
if gh_user:
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
# Pick the strongest identity signal for hashing
|
|
110
|
+
if gh_user:
|
|
111
|
+
identity_key = f"gh:{gh_user}"
|
|
112
|
+
identity_source = "github"
|
|
113
|
+
elif git_email:
|
|
114
|
+
identity_key = f"email:{git_email}"
|
|
115
|
+
identity_source = "git_email"
|
|
116
|
+
elif os_user:
|
|
117
|
+
identity_key = f"os:{os_user}"
|
|
118
|
+
identity_source = "os_user"
|
|
119
|
+
else:
|
|
120
|
+
identity_key = "unknown"
|
|
121
|
+
identity_source = "none"
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
"os_user": os_user,
|
|
125
|
+
"git_email": git_email,
|
|
126
|
+
"git_name": git_name,
|
|
127
|
+
"gh_user": gh_user,
|
|
128
|
+
"user_hash": _stable_hash(identity_key),
|
|
129
|
+
"identity_source": identity_source,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ---- resolve_project -----------------------------------------------------
|
|
134
|
+
|
|
135
|
+
def resolve_project(project_path: str = ".") -> Dict[str, str]:
|
|
136
|
+
"""Detect the current project from the working directory.
|
|
137
|
+
|
|
138
|
+
Returns a dict with:
|
|
139
|
+
- path: resolved absolute path
|
|
140
|
+
- name: project name (from package.json, pyproject.toml, or dir name)
|
|
141
|
+
- repo_url: git remote origin URL (may be empty)
|
|
142
|
+
- project_hash: stable hash of the resolved path
|
|
143
|
+
- has_delimit_dir: whether .delimit/ exists in the project
|
|
144
|
+
- project_type: node | python | unknown
|
|
145
|
+
"""
|
|
146
|
+
p = Path(project_path).resolve()
|
|
147
|
+
info: Dict[str, str] = {
|
|
148
|
+
"path": str(p),
|
|
149
|
+
"name": p.name,
|
|
150
|
+
"repo_url": "",
|
|
151
|
+
"project_hash": _stable_hash(str(p)),
|
|
152
|
+
"has_delimit_dir": str((p / ".delimit").is_dir()),
|
|
153
|
+
"project_type": "unknown",
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# package.json
|
|
157
|
+
pkg_file = p / "package.json"
|
|
158
|
+
if pkg_file.exists():
|
|
159
|
+
try:
|
|
160
|
+
pkg = json.loads(pkg_file.read_text())
|
|
161
|
+
info["name"] = pkg.get("name", p.name)
|
|
162
|
+
info["project_type"] = "node"
|
|
163
|
+
except Exception:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
# pyproject.toml
|
|
167
|
+
pyproj = p / "pyproject.toml"
|
|
168
|
+
if pyproj.exists() and info["project_type"] == "unknown":
|
|
169
|
+
try:
|
|
170
|
+
for line in pyproj.read_text().splitlines():
|
|
171
|
+
if line.strip().startswith("name"):
|
|
172
|
+
val = line.split("=", 1)[1].strip().strip('"').strip("'")
|
|
173
|
+
if val:
|
|
174
|
+
info["name"] = val
|
|
175
|
+
info["project_type"] = "python"
|
|
176
|
+
break
|
|
177
|
+
except Exception:
|
|
178
|
+
pass
|
|
179
|
+
|
|
180
|
+
# git remote
|
|
181
|
+
remote = _run_cmd(["git", "-C", str(p), "remote", "get-url", "origin"])
|
|
182
|
+
if remote:
|
|
183
|
+
info["repo_url"] = remote
|
|
184
|
+
|
|
185
|
+
return info
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ---- resolve_venture -----------------------------------------------------
|
|
189
|
+
|
|
190
|
+
def resolve_venture(project_path: str = ".") -> Dict[str, str]:
|
|
191
|
+
"""Map a project to its venture using the global ventures registry.
|
|
192
|
+
|
|
193
|
+
Returns a dict with:
|
|
194
|
+
- venture_name: matched venture name (or "unregistered")
|
|
195
|
+
- venture_match: how the match was made (exact | path | repo | none)
|
|
196
|
+
- registered_ventures: count of ventures in registry
|
|
197
|
+
"""
|
|
198
|
+
project = resolve_project(project_path)
|
|
199
|
+
resolved_path = project["path"]
|
|
200
|
+
repo_url = project["repo_url"]
|
|
201
|
+
|
|
202
|
+
ventures = _load_ventures()
|
|
203
|
+
result: Dict[str, str] = {
|
|
204
|
+
"venture_name": "unregistered",
|
|
205
|
+
"venture_match": "none",
|
|
206
|
+
"registered_ventures": str(len(ventures)),
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
# Exact path match
|
|
210
|
+
for name, vinfo in ventures.items():
|
|
211
|
+
v_path = vinfo.get("path", "")
|
|
212
|
+
if v_path and os.path.realpath(v_path) == os.path.realpath(resolved_path):
|
|
213
|
+
result["venture_name"] = name
|
|
214
|
+
result["venture_match"] = "exact"
|
|
215
|
+
return result
|
|
216
|
+
|
|
217
|
+
# Path-prefix match (project is a subdirectory of a venture)
|
|
218
|
+
for name, vinfo in ventures.items():
|
|
219
|
+
v_path = vinfo.get("path", "")
|
|
220
|
+
if v_path:
|
|
221
|
+
try:
|
|
222
|
+
if Path(resolved_path).is_relative_to(Path(v_path).resolve()):
|
|
223
|
+
result["venture_name"] = name
|
|
224
|
+
result["venture_match"] = "path"
|
|
225
|
+
return result
|
|
226
|
+
except (ValueError, TypeError):
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
# Repo URL match
|
|
230
|
+
if repo_url:
|
|
231
|
+
normalized_repo = repo_url.rstrip("/").replace(".git", "").lower()
|
|
232
|
+
for name, vinfo in ventures.items():
|
|
233
|
+
v_repo = vinfo.get("repo", "").rstrip("/").replace(".git", "").lower()
|
|
234
|
+
if v_repo and v_repo == normalized_repo:
|
|
235
|
+
result["venture_name"] = name
|
|
236
|
+
result["venture_match"] = "repo"
|
|
237
|
+
return result
|
|
238
|
+
|
|
239
|
+
return result
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _load_ventures() -> Dict[str, Any]:
|
|
243
|
+
"""Load the global ventures registry."""
|
|
244
|
+
if not VENTURES_FILE.exists():
|
|
245
|
+
return {}
|
|
246
|
+
try:
|
|
247
|
+
return json.loads(VENTURES_FILE.read_text())
|
|
248
|
+
except (json.JSONDecodeError, OSError):
|
|
249
|
+
return {}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# ---- resolve_namespace ---------------------------------------------------
|
|
253
|
+
|
|
254
|
+
def resolve_namespace(
|
|
255
|
+
user_hash: str = "",
|
|
256
|
+
venture_name: str = "",
|
|
257
|
+
project_hash: str = "",
|
|
258
|
+
) -> Dict[str, str]:
|
|
259
|
+
"""Compute the private state path for a user/venture/project combination.
|
|
260
|
+
|
|
261
|
+
Namespace layout:
|
|
262
|
+
Single-user (default): ~/.delimit/ (backwards compat)
|
|
263
|
+
Multi-user (future): ~/.delimit/users/{user_hash}/
|
|
264
|
+
|
|
265
|
+
Within a namespace, state is organized by type:
|
|
266
|
+
souls/{project_hash}/
|
|
267
|
+
handoff_receipts/{project_hash}/
|
|
268
|
+
ledger/
|
|
269
|
+
evidence/
|
|
270
|
+
...
|
|
271
|
+
|
|
272
|
+
Returns a dict with:
|
|
273
|
+
- namespace_root: the base path for this user's private state
|
|
274
|
+
- is_multi_user: whether multi-user scoping is active
|
|
275
|
+
- souls_dir: where soul captures go
|
|
276
|
+
- receipts_dir: where handoff receipts go
|
|
277
|
+
- ledger_dir: central ledger
|
|
278
|
+
- evidence_dir: audit evidence
|
|
279
|
+
- events_dir: event log
|
|
280
|
+
"""
|
|
281
|
+
# Determine if we are in multi-user mode.
|
|
282
|
+
# For now, multi-user activates only if DELIMIT_MULTI_USER=1 is set,
|
|
283
|
+
# keeping backwards compat for the single-user (root) case.
|
|
284
|
+
multi_user = os.environ.get("DELIMIT_MULTI_USER", "") == "1"
|
|
285
|
+
|
|
286
|
+
if multi_user and user_hash:
|
|
287
|
+
namespace_root = DELIMIT_HOME / "users" / user_hash
|
|
288
|
+
else:
|
|
289
|
+
namespace_root = DELIMIT_HOME
|
|
290
|
+
|
|
291
|
+
result = {
|
|
292
|
+
"namespace_root": str(namespace_root),
|
|
293
|
+
"is_multi_user": str(multi_user),
|
|
294
|
+
"souls_dir": str(namespace_root / "souls"),
|
|
295
|
+
"receipts_dir": str(namespace_root / "handoff_receipts"),
|
|
296
|
+
"ledger_dir": str(namespace_root / "ledger"),
|
|
297
|
+
"evidence_dir": str(namespace_root / "evidence"),
|
|
298
|
+
"events_dir": str(namespace_root / "events"),
|
|
299
|
+
"traces_dir": str(namespace_root / "traces"),
|
|
300
|
+
"agent_actions_dir": str(namespace_root / "agent_actions"),
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
# If project_hash is provided, the per-project subdirs get it
|
|
304
|
+
if project_hash:
|
|
305
|
+
result["souls_dir"] = str(namespace_root / "souls" / project_hash)
|
|
306
|
+
result["receipts_dir"] = str(
|
|
307
|
+
namespace_root / "handoff_receipts" / project_hash
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
return result
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# ---- auto_bind -----------------------------------------------------------
|
|
314
|
+
|
|
315
|
+
def auto_bind(project_path: str = ".") -> Dict[str, Any]:
|
|
316
|
+
"""Run full resolution chain and set environment variables.
|
|
317
|
+
|
|
318
|
+
This is the single entry point for startup. It:
|
|
319
|
+
1. Resolves user identity
|
|
320
|
+
2. Resolves current project
|
|
321
|
+
3. Maps project to venture
|
|
322
|
+
4. Computes the namespace
|
|
323
|
+
5. Sets DELIMIT_* env vars so all downstream tools use the right paths
|
|
324
|
+
6. Ensures the namespace directories exist
|
|
325
|
+
|
|
326
|
+
Returns the full resolution context for logging/debugging.
|
|
327
|
+
"""
|
|
328
|
+
user = resolve_user()
|
|
329
|
+
project = resolve_project(project_path)
|
|
330
|
+
venture = resolve_venture(project_path)
|
|
331
|
+
namespace = resolve_namespace(
|
|
332
|
+
user_hash=user["user_hash"],
|
|
333
|
+
venture_name=venture["venture_name"],
|
|
334
|
+
project_hash=project["project_hash"],
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# Set env vars for downstream consumption
|
|
338
|
+
_env_vars = {
|
|
339
|
+
"DELIMIT_USER_HASH": user["user_hash"],
|
|
340
|
+
"DELIMIT_USER_IDENTITY": user.get("gh_user") or user.get("git_email") or user.get("os_user", ""),
|
|
341
|
+
"DELIMIT_PROJECT_PATH": project["path"],
|
|
342
|
+
"DELIMIT_PROJECT_HASH": project["project_hash"],
|
|
343
|
+
"DELIMIT_PROJECT_NAME": project["name"],
|
|
344
|
+
"DELIMIT_VENTURE": venture["venture_name"],
|
|
345
|
+
"DELIMIT_NAMESPACE_ROOT": namespace["namespace_root"],
|
|
346
|
+
"DELIMIT_LEDGER_DIR": namespace["ledger_dir"],
|
|
347
|
+
"DELIMIT_SOULS_DIR": namespace["souls_dir"],
|
|
348
|
+
"DELIMIT_EVIDENCE_DIR": namespace["evidence_dir"],
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
for key, value in _env_vars.items():
|
|
352
|
+
os.environ[key] = value
|
|
353
|
+
|
|
354
|
+
# Ensure critical namespace directories exist
|
|
355
|
+
for dir_key in ("namespace_root", "souls_dir", "receipts_dir", "ledger_dir",
|
|
356
|
+
"evidence_dir", "events_dir", "traces_dir", "agent_actions_dir"):
|
|
357
|
+
Path(namespace[dir_key]).mkdir(parents=True, exist_ok=True)
|
|
358
|
+
|
|
359
|
+
# Verify private state is not inside a git worktree that would be committed
|
|
360
|
+
leak_warnings = _check_for_leaks(namespace["namespace_root"])
|
|
361
|
+
|
|
362
|
+
context = {
|
|
363
|
+
"user": user,
|
|
364
|
+
"project": project,
|
|
365
|
+
"venture": venture,
|
|
366
|
+
"namespace": namespace,
|
|
367
|
+
"env_vars_set": list(_env_vars.keys()),
|
|
368
|
+
"leak_warnings": leak_warnings,
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
logger.info(
|
|
372
|
+
"Continuity bound: user=%s project=%s venture=%s namespace=%s",
|
|
373
|
+
user.get("gh_user") or user.get("os_user", "?"),
|
|
374
|
+
project["name"],
|
|
375
|
+
venture["venture_name"],
|
|
376
|
+
namespace["namespace_root"],
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
return context
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# ---- Safety checks -------------------------------------------------------
|
|
383
|
+
|
|
384
|
+
def _check_for_leaks(namespace_root: str) -> list:
|
|
385
|
+
"""Check that the namespace root is not inside a git worktree or npm package.
|
|
386
|
+
|
|
387
|
+
Returns a list of warning strings (empty if clean).
|
|
388
|
+
"""
|
|
389
|
+
warnings = []
|
|
390
|
+
ns_path = Path(namespace_root).resolve()
|
|
391
|
+
|
|
392
|
+
# Check 1: namespace should be under ~/.delimit/ (home directory)
|
|
393
|
+
home = Path.home().resolve()
|
|
394
|
+
expected_base = home / ".delimit"
|
|
395
|
+
if not str(ns_path).startswith(str(expected_base)):
|
|
396
|
+
warnings.append(
|
|
397
|
+
f"Namespace root {ns_path} is outside ~/.delimit/ -- state may leak"
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
# Check 2: namespace should not be inside a git worktree
|
|
401
|
+
git_root = _run_cmd(["git", "-C", str(ns_path), "rev-parse", "--show-toplevel"])
|
|
402
|
+
if git_root:
|
|
403
|
+
# If the git root is the home dir itself, that is fine (some users have ~/ as a repo)
|
|
404
|
+
# But if it is a project repo, that is a problem.
|
|
405
|
+
git_root_path = Path(git_root).resolve()
|
|
406
|
+
if git_root_path != home and str(ns_path).startswith(str(git_root_path)):
|
|
407
|
+
warnings.append(
|
|
408
|
+
f"Namespace root {ns_path} is inside git worktree {git_root_path} "
|
|
409
|
+
"-- private state could be committed. Add to .gitignore."
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
# Check 3: verify .gitignore coverage in the gateway repo
|
|
413
|
+
gateway_gitignore = Path("/home/delimit/delimit-gateway/.gitignore")
|
|
414
|
+
if gateway_gitignore.exists():
|
|
415
|
+
content = gateway_gitignore.read_text()
|
|
416
|
+
if ".delimit/" not in content and ".delimit/ledger/" not in content:
|
|
417
|
+
warnings.append(
|
|
418
|
+
"Gateway .gitignore does not exclude .delimit/ -- state may be committed"
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
return warnings
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def verify_npm_exclusion() -> Dict[str, Any]:
|
|
425
|
+
"""Verify that private state directories are excluded from npm publish.
|
|
426
|
+
|
|
427
|
+
Checks .npmignore in the npm package for coverage of state dirs.
|
|
428
|
+
Returns a report.
|
|
429
|
+
"""
|
|
430
|
+
npmignore = Path("/home/delimit/npm-delimit/.npmignore")
|
|
431
|
+
result: Dict[str, Any] = {
|
|
432
|
+
"npmignore_exists": npmignore.exists(),
|
|
433
|
+
"covered": [],
|
|
434
|
+
"missing": [],
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if not npmignore.exists():
|
|
438
|
+
result["missing"] = PRIVATE_STATE_DIRS[:]
|
|
439
|
+
return result
|
|
440
|
+
|
|
441
|
+
content = npmignore.read_text()
|
|
442
|
+
for dirname in PRIVATE_STATE_DIRS:
|
|
443
|
+
# Check if the dir or a pattern covering it appears in .npmignore
|
|
444
|
+
if dirname in content or ".delimit" in content or f"**/{dirname}" in content:
|
|
445
|
+
result["covered"].append(dirname)
|
|
446
|
+
else:
|
|
447
|
+
result["missing"].append(dirname)
|
|
448
|
+
|
|
449
|
+
return result
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
# ---- Convenience for other modules ---------------------------------------
|
|
453
|
+
|
|
454
|
+
def get_namespace_root() -> Path:
|
|
455
|
+
"""Return the current namespace root from env or default.
|
|
456
|
+
|
|
457
|
+
Other modules can call this instead of hardcoding Path.home() / ".delimit".
|
|
458
|
+
"""
|
|
459
|
+
env_root = os.environ.get("DELIMIT_NAMESPACE_ROOT", "")
|
|
460
|
+
if env_root:
|
|
461
|
+
return Path(env_root)
|
|
462
|
+
return DELIMIT_HOME
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# This file was generated by Nuitka
|
|
2
|
+
|
|
3
|
+
# Stubs included by default
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import shutil
|
|
11
|
+
import time
|
|
12
|
+
import urllib.error
|
|
13
|
+
import urllib.request
|
|
14
|
+
|
|
15
|
+
DELIBERATION_DIR = Path.home() / '.delimit' / 'deliberations'
|
|
16
|
+
MODELS_CONFIG = Path.home() / '.delimit' / 'models.json'
|
|
17
|
+
DEFAULT_MODELS = {'grok': {'name': 'Grok', 'api_url': 'https://api.x.ai/v1/chat/completions', 'model': 'grok-4-0709', 'env_key': 'XAI_API_KEY', 'enabled': False}, 'gemini': {'name': 'Gemini', 'api_url': 'https://us-central1-aiplatform.googleapis.com/v1/projects/{project}/locations/us-central1/publishers/google/models/gemini-2.5-flash:generateContent', 'model': 'gemini-2.5-flash', 'env_key': 'GOOGLE_APPLICATION_CREDENTIALS', 'enabled': False, 'format': 'vertex_ai', 'prefer_cli': True, 'cli_command': 'gemini'}, 'openai': {'name': 'OpenAI', 'api_url': 'https://api.openai.com/v1/chat/completions', 'model': 'gpt-4o', 'env_key': 'OPENAI_API_KEY', 'enabled': False, 'prefer_cli': True}, 'anthropic': {'name': 'Claude', 'api_url': 'https://api.anthropic.com/v1/messages', 'model': 'claude-sonnet-4-5-20250514', 'env_key': 'ANTHROPIC_API_KEY', 'enabled': False, 'format': 'anthropic', 'prefer_cli': True, 'cli_command': 'claude'}}
|
|
18
|
+
def get_models_config() -> Dict[str, Any]:
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
def configure_models() -> Dict[str, Any]:
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
def _call_cli(prompt: str, system_prompt: str, cli_path: str, cli_command: str) -> str:
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
def _call_model(model_id: str, config: Dict, prompt: str, system_prompt: str) -> str:
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
def deliberate(question: str, context: str, max_rounds: int, mode: str, require_unanimous: bool, save_path: Optional[str]) -> Dict[str, Any]:
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
__name__ = ...
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Modules used internally, to allow implicit dependencies to be seen:
|
|
39
|
+
import json
|
|
40
|
+
import logging
|
|
41
|
+
import os
|
|
42
|
+
import shutil
|
|
43
|
+
import time
|
|
44
|
+
import urllib
|
|
45
|
+
import urllib.request
|
|
46
|
+
import urllib.error
|
|
47
|
+
import pathlib
|
|
48
|
+
import typing
|
|
49
|
+
import subprocess
|
|
50
|
+
import google
|
|
51
|
+
import google.auth
|
|
52
|
+
import google.auth.transport
|
|
53
|
+
import google.auth.transport.requests
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# This file was generated by Nuitka
|
|
2
|
+
|
|
3
|
+
# Stubs included by default
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
RULES = {'test_coverage': {'threshold_key': 'line_coverage', 'threshold': 80, 'comparison': 'below', 'ledger_title': 'Test coverage below {threshold}% — currently {value}%', 'ledger_type': 'fix', 'ledger_priority': 'P1'}, 'security_audit': {'trigger_key': 'vulnerabilities', 'trigger_if_nonempty': True, 'ledger_title': 'Security: {count} vulnerabilities found', 'ledger_type': 'fix', 'ledger_priority': 'P0'}, 'security_scan': {'trigger_key': 'vulnerabilities', 'trigger_if_nonempty': True, 'ledger_title': 'Security scan: {count} issues detected', 'ledger_type': 'fix', 'ledger_priority': 'P0'}, 'lint': {'trigger_key': 'violations', 'trigger_if_nonempty': True, 'ledger_title': 'API lint: {count} violations found', 'ledger_type': 'fix', 'ledger_priority': 'P1'}, 'deliberate': {'trigger_key': 'unanimous', 'trigger_if_true': True, 'extract_actions': True, 'ledger_title': 'Deliberation consensus reached — action items pending', 'ledger_type': 'strategy', 'ledger_priority': 'P1'}, 'gov_health': {'trigger_key': 'status', 'trigger_values': ['not_initialized', 'degraded'], 'ledger_title': 'Governance health: {value} — needs attention', 'ledger_type': 'fix', 'ledger_priority': 'P1'}, 'docs_validate': {'threshold_key': 'coverage_percent', 'threshold': 50, 'comparison': 'below', 'ledger_title': 'Documentation coverage below {threshold}% — currently {value}%', 'ledger_type': 'task', 'ledger_priority': 'P2'}}
|
|
12
|
+
MILESTONES = {'deploy_site': {'trigger_key': 'status', 'trigger_values': ['deployed'], 'ledger_title': 'Deployed: {project}', 'ledger_type': 'feat', 'ledger_priority': 'P1', 'auto_done': True}, 'deploy_npm': {'trigger_key': 'status', 'trigger_values': ['published'], 'ledger_title': 'Published: {package}@{new_version}', 'ledger_type': 'feat', 'ledger_priority': 'P1', 'auto_done': True}, 'deliberate': {'trigger_key': 'status', 'trigger_values': ['unanimous'], 'ledger_title': 'Consensus reached: {question_short}', 'ledger_type': 'strategy', 'ledger_priority': 'P1', 'auto_done': True}, 'test_generate': {'threshold_key': 'tests_generated', 'threshold': 10, 'comparison': 'above', 'ledger_title': 'Generated {value} tests', 'ledger_type': 'feat', 'ledger_priority': 'P2', 'auto_done': True}, 'sensor_github_issue': {'trigger_key': 'has_new_activity', 'trigger_if_true': True, 'ledger_title': 'Outreach response: new activity detected', 'ledger_type': 'task', 'ledger_priority': 'P1', 'auto_done': False}, 'zero_spec': {'trigger_key': 'success', 'trigger_if_true': True, 'ledger_title': 'Zero-spec extracted: {framework} ({paths_count} paths)', 'ledger_type': 'feat', 'ledger_priority': 'P2', 'auto_done': True}}
|
|
13
|
+
NEXT_STEPS = {'lint': [{'tool': 'delimit_explain', 'reason': 'Get migration guide for violations', 'premium': False}, {'tool': 'delimit_semver', 'reason': 'Classify the version bump', 'premium': False}], 'diff': [{'tool': 'delimit_semver', 'reason': 'Classify changes as MAJOR/MINOR/PATCH', 'premium': False}, {'tool': 'delimit_policy', 'reason': 'Check against governance policies', 'premium': False}], 'semver': [{'tool': 'delimit_explain', 'reason': 'Generate human-readable changelog', 'premium': False}, {'tool': 'delimit_deploy_npm', 'reason': 'Publish the new version to npm', 'premium': False}], 'init': [{'tool': 'delimit_gov_health', 'reason': 'Verify governance is set up correctly', 'premium': True}, {'tool': 'delimit_diagnose', 'reason': 'Check for any issues', 'premium': False}], 'deploy_site': [{'tool': 'delimit_deploy_npm', 'reason': 'Publish npm package if applicable', 'premium': False}, {'tool': 'delimit_ledger_context', 'reason': 'Check what else needs deploying', 'premium': False}], 'test_coverage': [{'tool': 'delimit_test_generate', 'reason': 'Generate tests for uncovered files', 'premium': False}], 'security_audit': [{'tool': 'delimit_evidence_collect', 'reason': 'Collect evidence of findings', 'premium': True}], 'gov_health': [{'tool': 'delimit_gov_status', 'reason': 'See detailed governance status', 'premium': True}, {'tool': 'delimit_repo_analyze', 'reason': 'Full repo health report', 'premium': True}], 'deploy_npm': [{'tool': 'delimit_deploy_verify', 'reason': 'Verify the published package', 'premium': True}], 'deploy_plan': [{'tool': 'delimit_deploy_build', 'reason': 'Build the deployment', 'premium': True}], 'deploy_build': [{'tool': 'delimit_deploy_publish', 'reason': 'Publish the build', 'premium': True}], 'deploy_publish': [{'tool': 'delimit_deploy_verify', 'reason': 'Verify the deployment', 'premium': True}], 'deploy_verify': [{'tool': 'delimit_deploy_rollback', 'reason': 'Rollback if unhealthy', 'premium': True}], 'repo_analyze': [{'tool': 'delimit_security_audit', 'reason': 'Scan for security issues', 'premium': False}, {'tool': 'delimit_gov_health', 'reason': 'Check governance status', 'premium': True}], 'deliberate': [{'tool': 'delimit_ledger_context', 'reason': "Review what's on the ledger after consensus", 'premium': False}], 'ledger_add': [{'tool': 'delimit_ledger_context', 'reason': 'See updated ledger state', 'premium': False}], 'diagnose': [{'tool': 'delimit_init', 'reason': 'Initialize governance if not set up', 'premium': False}]}
|
|
14
|
+
def govern(tool_name: str, result: Dict[str, Any], project_path: str) -> Dict[str, Any]:
|
|
15
|
+
...
|
|
16
|
+
|
|
17
|
+
def _deep_get(d: Dict, key: str) -> Any:
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__name__ = ...
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Modules used internally, to allow implicit dependencies to be seen:
|
|
26
|
+
import json
|
|
27
|
+
import logging
|
|
28
|
+
import time
|
|
29
|
+
import pathlib
|
|
30
|
+
import typing
|
|
31
|
+
import ai
|
|
32
|
+
import ai.ledger_manager
|