delimit-cli 4.1.44 → 4.1.47

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.
@@ -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