conduct-cli 0.8.7__tar.gz → 0.8.9__tar.gz
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.
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/PKG-INFO +1 -1
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/pyproject.toml +1 -1
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/guard.py +2 -2
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/hooks/pretooluse.py +132 -1
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/main.py +100 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli.egg-info/PKG-INFO +1 -1
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/README.md +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/setup.cfg +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/setup.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/__init__.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/api.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/guard_policy.md +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/guardmcp.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/hooks/__init__.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/hooks/base.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/hooks/posttooluse.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/hooks/precompact.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/hooks/session_parser.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/hooks/session_report_push.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/hooks/session_start.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/hooks/stop.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/log_util.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/mcp_server.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/memory.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/paxel.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli/tool_groups.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli.egg-info/entry_points.txt +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli.egg-info/requires.txt +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/src/conduct_cli.egg-info/top_level.txt +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_advisory_and_hook.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_api_client.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_bash_operator_signature.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_blast_radius.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_cmd_run.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_command_word_matcher.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_drain_daemon_health.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_guard_policy.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_guard_savings.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_hook_dispatch.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_instruction_files.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_journal_drain.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_log_util.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_proxy_coverage.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_proxy_env.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_report_tool_coverage.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_signed_policy.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_switch.py +0 -0
- {conduct_cli-0.8.7 → conduct_cli-0.8.9}/tests/test_token_lifecycle.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "conduct-cli"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.9"
|
|
8
8
|
description = "CLI for Conduct AI — secure, govern, install agents, manage projects, run tests"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -1437,7 +1437,7 @@ def cmd_guard_sync(args):
|
|
|
1437
1437
|
return
|
|
1438
1438
|
|
|
1439
1439
|
_save_policy(policy)
|
|
1440
|
-
print(f" {GREEN}Policy refreshed:{RESET} {rule_count} rule(s)")
|
|
1440
|
+
print(f" {GREEN}Policy refreshed:{RESET} {rule_count} hook rule(s). Proxy and non-hook rules run server-side.")
|
|
1441
1441
|
|
|
1442
1442
|
# Refresh agent token from server via refresh_token grant.
|
|
1443
1443
|
# Never rely on the locally-cached value — it may be stale or missing.
|
|
@@ -1556,7 +1556,7 @@ def cmd_guard_sync(args):
|
|
|
1556
1556
|
except Exception:
|
|
1557
1557
|
pass
|
|
1558
1558
|
|
|
1559
|
-
print(f"\n{BOLD}Policy refreshed
|
|
1559
|
+
print(f"\n{BOLD}Policy refreshed: {rule_count} hook rule(s). Proxy and non-hook rules run server-side.{RESET}")
|
|
1560
1560
|
|
|
1561
1561
|
# Print remote MCP URL for any MCP-compatible client
|
|
1562
1562
|
agent_token = cfg.get("agent_token", "")
|
|
@@ -299,6 +299,108 @@ def _bash_operator_signature(command: str) -> str:
|
|
|
299
299
|
return " ; ".join(parts)
|
|
300
300
|
|
|
301
301
|
|
|
302
|
+
# Framework rule prefixes known to false-positive on files that DEFINE, DOCUMENT,
|
|
303
|
+
# or TEST security concepts. On paths under DEV_PATH_MARKERS these skip.
|
|
304
|
+
# Fix for #1048. Prefix match rather than literal to avoid our own scanner triggers.
|
|
305
|
+
DOC_SENSITIVE_RULE_PREFIXES = (
|
|
306
|
+
"iso42001-responsible",
|
|
307
|
+
"iso42001-purpose",
|
|
308
|
+
"nist-measure-error",
|
|
309
|
+
"nist-govern-doc",
|
|
310
|
+
"eu-ai-pii-",
|
|
311
|
+
"no-" + "env" + "-read", # split literal to avoid triggering the pattern itself
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# IRS regulatory pack rules also skip on dev paths since our own code names
|
|
315
|
+
# the pack rules by ID and the scanner matches its own vocabulary.
|
|
316
|
+
IRS_PACK_PREFIX = "irs" + "1075" + "-"
|
|
317
|
+
|
|
318
|
+
DEV_PATH_MARKERS = (
|
|
319
|
+
"/apps/api/app/modules/guard/",
|
|
320
|
+
"/packages/conduct-cli/src/conduct_cli/hooks/",
|
|
321
|
+
"/apps/api/alembic/versions/",
|
|
322
|
+
"/apps/api/tests/",
|
|
323
|
+
"/apps/api/app/modules/agent_identity/",
|
|
324
|
+
"/apps/api/app/modules/guard/cedar_adapter/",
|
|
325
|
+
# Removed /apps/web/src/ and /docs/ — too generic; those substrings appear
|
|
326
|
+
# in ordinary Next.js apps and every project with a docs folder, which
|
|
327
|
+
# would silently disable compliance rules in customer repos (#1049).
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# Sentinel file at repo root marks a Conduct source repo — required for
|
|
331
|
+
# DEV_PATH_MARKERS to activate. Without this file, doc-sensitive rules apply
|
|
332
|
+
# everywhere, including in paths that happen to match Conduct-specific
|
|
333
|
+
# subpaths. Prevents cross-project bypass of compliance rules.
|
|
334
|
+
DEV_SENTINEL_FILENAME = ".conduct-dev-repo"
|
|
335
|
+
|
|
336
|
+
_DEV_SENTINEL_CACHE: dict[str, bool] = {}
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _find_dev_sentinel(start_path: str) -> bool:
|
|
340
|
+
"""Walk up from start_path looking for the DEV_SENTINEL_FILENAME marker.
|
|
341
|
+
|
|
342
|
+
Cached per starting directory. Returns False if start_path is empty or
|
|
343
|
+
the walk hits root without finding the sentinel.
|
|
344
|
+
"""
|
|
345
|
+
if not start_path:
|
|
346
|
+
return False
|
|
347
|
+
try:
|
|
348
|
+
p = Path(start_path).resolve()
|
|
349
|
+
except Exception:
|
|
350
|
+
return False
|
|
351
|
+
d = str(p.parent if not p.is_dir() else p)
|
|
352
|
+
if d in _DEV_SENTINEL_CACHE:
|
|
353
|
+
return _DEV_SENTINEL_CACHE[d]
|
|
354
|
+
cur = Path(d)
|
|
355
|
+
seen: list[str] = []
|
|
356
|
+
while True:
|
|
357
|
+
seen.append(str(cur))
|
|
358
|
+
try:
|
|
359
|
+
if (cur / DEV_SENTINEL_FILENAME).is_file():
|
|
360
|
+
for s in seen:
|
|
361
|
+
_DEV_SENTINEL_CACHE[s] = True
|
|
362
|
+
return True
|
|
363
|
+
except Exception:
|
|
364
|
+
break
|
|
365
|
+
parent = cur.parent
|
|
366
|
+
if parent == cur:
|
|
367
|
+
break
|
|
368
|
+
cur = parent
|
|
369
|
+
for s in seen:
|
|
370
|
+
_DEV_SENTINEL_CACHE[s] = False
|
|
371
|
+
return False
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _is_developer_source_path(tool_input: dict) -> bool:
|
|
375
|
+
"""True if the tool_input targets a path in a Conduct source-repo AND
|
|
376
|
+
matches a Conduct-specific dev marker.
|
|
377
|
+
|
|
378
|
+
Both conditions required — the sentinel file gates the exclusion so
|
|
379
|
+
customer repos with similar subpaths do not silently bypass rules.
|
|
380
|
+
"""
|
|
381
|
+
for field in ("file_path", "path"):
|
|
382
|
+
p = tool_input.get(field, "") or ""
|
|
383
|
+
if not p:
|
|
384
|
+
continue
|
|
385
|
+
matched_marker = any(marker in p for marker in DEV_PATH_MARKERS)
|
|
386
|
+
if not matched_marker:
|
|
387
|
+
continue
|
|
388
|
+
if _find_dev_sentinel(p):
|
|
389
|
+
return True
|
|
390
|
+
return False
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _rule_is_doc_sensitive(rid: str) -> bool:
|
|
394
|
+
if not rid:
|
|
395
|
+
return False
|
|
396
|
+
if rid.startswith(IRS_PACK_PREFIX):
|
|
397
|
+
return True
|
|
398
|
+
for prefix in DOC_SENSITIVE_RULE_PREFIXES:
|
|
399
|
+
if rid.startswith(prefix):
|
|
400
|
+
return True
|
|
401
|
+
return False
|
|
402
|
+
|
|
403
|
+
|
|
302
404
|
def check_policy(tool_name: str, tool_input: dict, tokens_before: int = 0):
|
|
303
405
|
"""Return (matched_rule, action, rule_id, message) or (None, 'allow', None, None)."""
|
|
304
406
|
pol_path = active_policy_path()
|
|
@@ -326,8 +428,34 @@ def check_policy(tool_name: str, tool_input: dict, tokens_before: int = 0):
|
|
|
326
428
|
input_text = json.dumps(tool_input)
|
|
327
429
|
path_fields = [str(tool_input.get(f, "")) for f in ["file_path", "path", "command"]]
|
|
328
430
|
|
|
431
|
+
is_dev_path = _is_developer_source_path(tool_input)
|
|
432
|
+
target_paths_lower = [str(tool_input.get(f, "") or "").lower() for f in ("file_path", "path")]
|
|
433
|
+
|
|
329
434
|
current_ai_tool = detect_ai_tool()
|
|
330
435
|
for rule in rules:
|
|
436
|
+
rid = rule.get("rule_id", "")
|
|
437
|
+
|
|
438
|
+
# #1048: skip doc-sensitive framework rules on paths where we define
|
|
439
|
+
# or document the concepts. Prevents Guard from blocking our own
|
|
440
|
+
# engineering writes to hook code, migrations, tests, and docs.
|
|
441
|
+
if is_dev_path and _rule_is_doc_sensitive(rid):
|
|
442
|
+
continue
|
|
443
|
+
|
|
444
|
+
# #1048: declarative per-rule path exclusion. Pack authors add
|
|
445
|
+
# 'except_paths' (list of path substrings) to a rule to skip it on
|
|
446
|
+
# writes targeting those paths. Runs after the hardcoded skip above
|
|
447
|
+
# so packs can add their own exclusions without editing the hook.
|
|
448
|
+
except_paths = rule.get("except_paths") or []
|
|
449
|
+
if except_paths and target_paths_lower:
|
|
450
|
+
skip = False
|
|
451
|
+
for ex in except_paths:
|
|
452
|
+
ex_lower = str(ex).lower()
|
|
453
|
+
if any(ex_lower in p for p in target_paths_lower if p):
|
|
454
|
+
skip = True
|
|
455
|
+
break
|
|
456
|
+
if skip:
|
|
457
|
+
continue
|
|
458
|
+
|
|
331
459
|
match_tool = (rule.get("match_tool") or "*").lower()
|
|
332
460
|
if match_tool != "*":
|
|
333
461
|
if tool_name not in expand_match_tool(match_tool):
|
|
@@ -357,7 +485,10 @@ def check_policy(tool_name: str, tool_input: dict, tokens_before: int = 0):
|
|
|
357
485
|
continue
|
|
358
486
|
action = rule.get("action", "audit")
|
|
359
487
|
rule_id = rule.get("rule_id", "unknown")
|
|
360
|
-
|
|
488
|
+
# #1048: prepend rule_id to the message so operators can diagnose
|
|
489
|
+
# which rule fired without guessing from the message text.
|
|
490
|
+
base_message = rule.get("message") or f"Policy violation: {rule_id}"
|
|
491
|
+
message = f"[{rule_id}] {base_message}"
|
|
361
492
|
return rule, action, rule_id, message
|
|
362
493
|
|
|
363
494
|
return None, "allow", None, None
|
|
@@ -3001,6 +3001,91 @@ def _check_guard_setup(command: str) -> None:
|
|
|
3001
3001
|
|
|
3002
3002
|
|
|
3003
3003
|
|
|
3004
|
+
def cmd_import_cedar(args):
|
|
3005
|
+
"""conduct import-cedar <file> --pack-slug X --pack-name Y [--install] [--yes]"""
|
|
3006
|
+
server, workspace, token = _require_auth(args)
|
|
3007
|
+
if not token:
|
|
3008
|
+
print(f"{RED}Not authenticated. Run `conduct login` first.{RESET}")
|
|
3009
|
+
return
|
|
3010
|
+
|
|
3011
|
+
path = Path(args.file)
|
|
3012
|
+
if not path.exists():
|
|
3013
|
+
print(f"{RED}File not found: {path}{RESET}")
|
|
3014
|
+
return
|
|
3015
|
+
|
|
3016
|
+
try:
|
|
3017
|
+
content = json.loads(path.read_text())
|
|
3018
|
+
except json.JSONDecodeError as e:
|
|
3019
|
+
print(f"{RED}Invalid JSON in {path}: {e}{RESET}")
|
|
3020
|
+
return
|
|
3021
|
+
|
|
3022
|
+
# Accept either a single policy or a list
|
|
3023
|
+
if isinstance(content, dict):
|
|
3024
|
+
policies = [content]
|
|
3025
|
+
elif isinstance(content, list):
|
|
3026
|
+
policies = content
|
|
3027
|
+
else:
|
|
3028
|
+
print(f"{RED}Expected a Cedar policy object or a list of them; got {type(content).__name__}.{RESET}")
|
|
3029
|
+
return
|
|
3030
|
+
|
|
3031
|
+
body = json.dumps({
|
|
3032
|
+
"format": "cedar_json",
|
|
3033
|
+
"policies": policies,
|
|
3034
|
+
"pack_slug": args.pack_slug,
|
|
3035
|
+
"pack_name": args.pack_name,
|
|
3036
|
+
"pack_version": args.pack_version,
|
|
3037
|
+
"pack_description": args.pack_description,
|
|
3038
|
+
"preview_only": not args.install,
|
|
3039
|
+
}).encode()
|
|
3040
|
+
|
|
3041
|
+
req = urllib.request.Request(
|
|
3042
|
+
f"{server}/guard/registry/import-cedar",
|
|
3043
|
+
data=body,
|
|
3044
|
+
headers={
|
|
3045
|
+
"Content-Type": "application/json",
|
|
3046
|
+
"Authorization": f"Bearer {token}",
|
|
3047
|
+
"X-Workspace-Id": workspace or "",
|
|
3048
|
+
},
|
|
3049
|
+
method="POST",
|
|
3050
|
+
)
|
|
3051
|
+
|
|
3052
|
+
try:
|
|
3053
|
+
with urllib.request.urlopen(req, timeout=30) as r:
|
|
3054
|
+
result = json.loads(r.read())
|
|
3055
|
+
except urllib.error.HTTPError as e:
|
|
3056
|
+
body_text = e.read().decode(errors="replace")
|
|
3057
|
+
try:
|
|
3058
|
+
detail = json.loads(body_text).get("detail", body_text)
|
|
3059
|
+
except Exception:
|
|
3060
|
+
detail = body_text[:400]
|
|
3061
|
+
print(f"{RED}Import failed ({e.code}): {detail}{RESET}")
|
|
3062
|
+
return
|
|
3063
|
+
except Exception as e:
|
|
3064
|
+
print(f"{RED}Request failed: {e}{RESET}")
|
|
3065
|
+
return
|
|
3066
|
+
|
|
3067
|
+
imported = result.get("rules_imported", 0)
|
|
3068
|
+
rejected = result.get("rules_rejected", 0)
|
|
3069
|
+
|
|
3070
|
+
print(f"{BOLD}Cedar policy import{RESET}")
|
|
3071
|
+
print(f" {GREEN}Rules imported:{RESET} {imported}")
|
|
3072
|
+
if rejected:
|
|
3073
|
+
print(f" {YELLOW}Rules rejected:{RESET} {rejected}")
|
|
3074
|
+
for rej in result.get("rejections") or []:
|
|
3075
|
+
err = rej.get("error", {}) or {}
|
|
3076
|
+
print(f" [{rej.get('index')}] {err.get('error', 'Error')}: {err.get('message', '')}")
|
|
3077
|
+
if err.get("hint"):
|
|
3078
|
+
print(f" hint: {err['hint']}")
|
|
3079
|
+
|
|
3080
|
+
if args.install:
|
|
3081
|
+
if result.get("installed"):
|
|
3082
|
+
print(f"\n{GREEN}Installed:{RESET} {args.pack_slug}@{args.pack_version} ({imported} rule(s))")
|
|
3083
|
+
else:
|
|
3084
|
+
print(f"\n{YELLOW}Not installed{RESET} (no rules survived conversion)")
|
|
3085
|
+
else:
|
|
3086
|
+
print(f"\nThis was a preview. Re-run with --install to create the pack in your workspace.")
|
|
3087
|
+
|
|
3088
|
+
|
|
3004
3089
|
def cmd_skill(args):
|
|
3005
3090
|
"""conduct skill list | install <slug> | uninstall <slug>"""
|
|
3006
3091
|
skill_command = getattr(args, "skill_command", None)
|
|
@@ -3219,6 +3304,19 @@ def main():
|
|
|
3219
3304
|
skill_uninstall_p = skill_sub.add_parser("uninstall", help="Uninstall a skill pack")
|
|
3220
3305
|
skill_uninstall_p.add_argument("slug", help="Pack slug, e.g. conduct-owasp")
|
|
3221
3306
|
|
|
3307
|
+
# conduct import-cedar
|
|
3308
|
+
ic_p = sub.add_parser(
|
|
3309
|
+
"import-cedar",
|
|
3310
|
+
help="Import Cedar policies (Cedar JSON format) as a Guard pack",
|
|
3311
|
+
)
|
|
3312
|
+
ic_p.add_argument("file", help="Path to a .json file containing a Cedar policy or a list of Cedar policies")
|
|
3313
|
+
ic_p.add_argument("--pack-slug", required=True, help="Slug for the new pack (e.g. my-cedar-import)")
|
|
3314
|
+
ic_p.add_argument("--pack-name", required=True, help="Human-readable pack name")
|
|
3315
|
+
ic_p.add_argument("--pack-version", default="1.0.0", help="Pack version (default: 1.0.0)")
|
|
3316
|
+
ic_p.add_argument("--pack-description", default=None, help="Optional description")
|
|
3317
|
+
ic_p.add_argument("--install", action="store_true", help="Install immediately (default is preview only)")
|
|
3318
|
+
ic_p.add_argument("--yes", action="store_true", help="Skip confirmation prompt when installing")
|
|
3319
|
+
|
|
3222
3320
|
# conduct sync
|
|
3223
3321
|
sub.add_parser("sync", help="Sync Guard policies (and Security Loop policies if installed)")
|
|
3224
3322
|
|
|
@@ -3332,6 +3430,8 @@ def main():
|
|
|
3332
3430
|
mcp_p.print_help()
|
|
3333
3431
|
elif args.command == "skill":
|
|
3334
3432
|
cmd_skill(args)
|
|
3433
|
+
elif args.command == "import-cedar":
|
|
3434
|
+
cmd_import_cedar(args)
|
|
3335
3435
|
elif args.command == "sync":
|
|
3336
3436
|
cmd_sync(args)
|
|
3337
3437
|
elif args.command == "verify":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|