clink-cli 0.2.1__py3-none-any.whl
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.
- clink/__init__.py +19 -0
- clink/agents/__init__.py +30 -0
- clink/agents/base.py +70 -0
- clink/agents/claude.py +7 -0
- clink/agents/codex.py +7 -0
- clink/agents/gemini.py +7 -0
- clink/allowed_roots.py +356 -0
- clink/backend.py +765 -0
- clink/cli.py +594 -0
- clink/constants.py +78 -0
- clink/contracts.py +167 -0
- clink/files.py +70 -0
- clink/generation.py +274 -0
- clink/generation_capabilities.py +207 -0
- clink/generation_contracts.py +214 -0
- clink/generation_policy.py +174 -0
- clink/models.py +243 -0
- clink/native.py +409 -0
- clink/native_adapters.py +244 -0
- clink/native_contracts.py +68 -0
- clink/native_generation.py +421 -0
- clink/native_generation_claude_policy.py +59 -0
- clink/native_generation_contracts.py +264 -0
- clink/native_generation_families.py +218 -0
- clink/native_generation_family_policy.py +14 -0
- clink/native_generation_files.py +153 -0
- clink/native_generation_policy.py +82 -0
- clink/native_policy.py +86 -0
- clink/parsers/__init__.py +34 -0
- clink/parsers/agy.py +53 -0
- clink/parsers/agy_native.py +161 -0
- clink/parsers/agy_native_generation.py +189 -0
- clink/parsers/base.py +209 -0
- clink/parsers/claude.py +104 -0
- clink/parsers/claude_native.py +113 -0
- clink/parsers/claude_native_generation.py +346 -0
- clink/parsers/codex.py +172 -0
- clink/parsers/codex_generation.py +212 -0
- clink/parsers/codex_native.py +236 -0
- clink/parsers/codex_native_generation.py +367 -0
- clink/parsers/gemini.py +110 -0
- clink/parsers/grok.py +63 -0
- clink/parsers/grok_native.py +91 -0
- clink/parsers/grok_native_generation.py +157 -0
- clink/policy.py +180 -0
- clink/registry.py +544 -0
- clink/resources/CODEX-LICENSE +201 -0
- clink/resources/CODEX-NOTICE +6 -0
- clink/resources/PROVENANCE.md +16 -0
- clink/resources/codex-0.166.0-models-direct.json +1284 -0
- clink/resources/codex-0.166.0-models-generation.json +1269 -0
- clink/resources/native-agy-1.4.12-admission.json +746 -0
- clink/resources/native-claude-2.1.304-admission.json +99 -0
- clink/resources/native-generation-claude-2.1.304-admission.json +277 -0
- clink/resources/native-generation-codex-0.166.0-admission.json +309 -0
- clink/resources/native-generation-grok-1.0.70-admission.json +329 -0
- clink/resources/native-grok-1.0.70-admission.json +644 -0
- clink/runner.py +303 -0
- clink/setup.py +399 -0
- clink/tasks.py +695 -0
- clink_cli-0.2.1.dist-info/METADATA +225 -0
- clink_cli-0.2.1.dist-info/RECORD +141 -0
- clink_cli-0.2.1.dist-info/WHEEL +5 -0
- clink_cli-0.2.1.dist-info/entry_points.txt +3 -0
- clink_cli-0.2.1.dist-info/licenses/LICENSE +197 -0
- clink_cli-0.2.1.dist-info/licenses/clink/resources/CODEX-LICENSE +201 -0
- clink_cli-0.2.1.dist-info/licenses/clink/resources/CODEX-NOTICE +6 -0
- clink_cli-0.2.1.dist-info/top_level.txt +7 -0
- conf/__init__.py +1 -0
- conf/cli_clients/agy.json +40 -0
- conf/cli_clients/claude.json +39 -0
- conf/cli_clients/codex.json +40 -0
- conf/cli_clients/gemini.json +23 -0
- conf/cli_clients/grok.json +40 -0
- config.py +40 -0
- server.py +896 -0
- systemprompts/__init__.py +35 -0
- systemprompts/analyze_prompt.py +87 -0
- systemprompts/chat_prompt.py +57 -0
- systemprompts/clink/codex_codereviewer.txt +8 -0
- systemprompts/clink/default.txt +8 -0
- systemprompts/clink/default_codereviewer.txt +8 -0
- systemprompts/clink/default_planner.txt +7 -0
- systemprompts/clink/native_codereviewer.txt +1 -0
- systemprompts/clink/native_default.txt +1 -0
- systemprompts/clink/native_planner.txt +1 -0
- systemprompts/codereview_prompt.py +91 -0
- systemprompts/consensus_prompt.py +120 -0
- systemprompts/debug_prompt.py +150 -0
- systemprompts/docgen_prompt.py +252 -0
- systemprompts/generate_code_prompt.py +181 -0
- systemprompts/planner_prompt.py +127 -0
- systemprompts/precommit_prompt.py +179 -0
- systemprompts/refactor_prompt.py +323 -0
- systemprompts/secaudit_prompt.py +420 -0
- systemprompts/testgen_prompt.py +133 -0
- systemprompts/thinkdeep_prompt.py +55 -0
- systemprompts/tracer_prompt.py +145 -0
- tools/__init__.py +43 -0
- tools/analyze.py +583 -0
- tools/apilookup.py +97 -0
- tools/challenge.py +198 -0
- tools/chat.py +187 -0
- tools/clink.py +130 -0
- tools/codereview.py +724 -0
- tools/consensus.py +790 -0
- tools/debug.py +573 -0
- tools/docgen.py +577 -0
- tools/listmodels.py +48 -0
- tools/models.py +373 -0
- tools/planner.py +533 -0
- tools/precommit.py +776 -0
- tools/refactor.py +645 -0
- tools/secaudit.py +685 -0
- tools/shared/__init__.py +19 -0
- tools/shared/base_models.py +167 -0
- tools/shared/base_tool.py +659 -0
- tools/shared/exceptions.py +20 -0
- tools/shared/schema_builders.py +159 -0
- tools/simple/__init__.py +18 -0
- tools/simple/base.py +539 -0
- tools/testgen.py +513 -0
- tools/thinkdeep.py +586 -0
- tools/tracer.py +763 -0
- tools/version.py +53 -0
- tools/workflow/__init__.py +22 -0
- tools/workflow/base.py +448 -0
- tools/workflow/schema_builders.py +166 -0
- tools/workflow/workflow_mixin.py +1195 -0
- utils/__init__.py +21 -0
- utils/call_state.py +349 -0
- utils/client_info.py +293 -0
- utils/consensus_manifest.py +131 -0
- utils/conversation_memory.py +582 -0
- utils/env.py +111 -0
- utils/file_types.py +271 -0
- utils/file_utils.py +803 -0
- utils/image_utils.py +94 -0
- utils/security_config.py +163 -0
- utils/storage_backend.py +177 -0
- utils/token_utils.py +54 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Bounded change detection for explicitly declared consensus context files.
|
|
2
|
+
|
|
3
|
+
Hashes describe observations at checks. They are not persistent source snapshots,
|
|
4
|
+
read isolation, or proof that a file was unchanged between two checks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import os
|
|
11
|
+
import stat
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from clink.contracts import BackendError
|
|
16
|
+
|
|
17
|
+
MAX_CONSENSUS_FILES = 256
|
|
18
|
+
MAX_CONSENSUS_BYTES = 64 * 1024 * 1024
|
|
19
|
+
_HASH_CHUNK_BYTES = 1024 * 1024
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _inside(path: Path, roots: tuple[Path, ...]) -> bool:
|
|
23
|
+
return any(path == root or root in path.parents for root in roots)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _signature(info: os.stat_result) -> tuple[int, ...]:
|
|
27
|
+
return (info.st_dev, info.st_ino, info.st_size, info.st_mtime_ns, info.st_ctime_ns)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def build_manifest(paths: tuple[str, ...], root_sets: tuple[tuple[Path, ...], ...]) -> list[dict[str, Any]]:
|
|
31
|
+
"""Hash regular files incrementally, checking limits, roots, symlinks, and races."""
|
|
32
|
+
if len(paths) > MAX_CONSENSUS_FILES:
|
|
33
|
+
raise BackendError(
|
|
34
|
+
"input_limit", "Consensus accepts at most 256 declared files and images.", "Declare fewer files."
|
|
35
|
+
)
|
|
36
|
+
entries: list[dict[str, Any]] = []
|
|
37
|
+
total_bytes = 0
|
|
38
|
+
for raw_path in paths:
|
|
39
|
+
# Keep '..' until resolve: lexical normalization before symlink resolution
|
|
40
|
+
# can hash a different file than the native CLI will read.
|
|
41
|
+
logical = Path(raw_path)
|
|
42
|
+
if not logical.is_absolute():
|
|
43
|
+
raise BackendError(
|
|
44
|
+
"path_not_allowed", "Consensus context paths must be absolute.", "Use an approved absolute path."
|
|
45
|
+
)
|
|
46
|
+
try:
|
|
47
|
+
canonical = logical.resolve(strict=True)
|
|
48
|
+
if not root_sets or any(
|
|
49
|
+
not _inside(logical, roots) or not _inside(canonical, roots) for roots in root_sets
|
|
50
|
+
):
|
|
51
|
+
raise BackendError(
|
|
52
|
+
"path_not_allowed",
|
|
53
|
+
"Declared context is outside a participant's approved roots.",
|
|
54
|
+
"Choose files inside every participant's configured allowed_roots.",
|
|
55
|
+
)
|
|
56
|
+
before = canonical.stat()
|
|
57
|
+
if not stat.S_ISREG(before.st_mode):
|
|
58
|
+
raise BackendError(
|
|
59
|
+
"path_not_allowed",
|
|
60
|
+
"Consensus accepts explicit regular files only.",
|
|
61
|
+
"Declare files instead of directories or special files.",
|
|
62
|
+
)
|
|
63
|
+
if before.st_size > MAX_CONSENSUS_BYTES - total_bytes:
|
|
64
|
+
raise BackendError("input_limit", "Consensus context exceeds 64 MiB.", "Declare less context.")
|
|
65
|
+
descriptor = os.open(canonical, os.O_RDONLY | os.O_NONBLOCK)
|
|
66
|
+
with os.fdopen(descriptor, "rb") as source:
|
|
67
|
+
opened = os.fstat(source.fileno())
|
|
68
|
+
if not stat.S_ISREG(opened.st_mode) or _signature(opened) != _signature(before):
|
|
69
|
+
raise BackendError(
|
|
70
|
+
"consensus_invalidated",
|
|
71
|
+
"A context file changed during admission.",
|
|
72
|
+
"Start a new consensus with stable files.",
|
|
73
|
+
)
|
|
74
|
+
digest = hashlib.sha256()
|
|
75
|
+
size = 0
|
|
76
|
+
while True:
|
|
77
|
+
chunk = source.read(_HASH_CHUNK_BYTES)
|
|
78
|
+
if not chunk:
|
|
79
|
+
break
|
|
80
|
+
size += len(chunk)
|
|
81
|
+
if size > MAX_CONSENSUS_BYTES - total_bytes:
|
|
82
|
+
raise BackendError("input_limit", "Consensus context exceeds 64 MiB.", "Declare less context.")
|
|
83
|
+
digest.update(chunk)
|
|
84
|
+
after = os.fstat(source.fileno())
|
|
85
|
+
if (
|
|
86
|
+
_signature(before) != _signature(after)
|
|
87
|
+
or _signature(after) != _signature(canonical.stat())
|
|
88
|
+
or logical.resolve(strict=True) != canonical
|
|
89
|
+
or size != after.st_size
|
|
90
|
+
):
|
|
91
|
+
raise BackendError(
|
|
92
|
+
"consensus_invalidated",
|
|
93
|
+
"A context file changed while being hashed.",
|
|
94
|
+
"Start a new consensus with stable files.",
|
|
95
|
+
)
|
|
96
|
+
except BackendError:
|
|
97
|
+
raise
|
|
98
|
+
except (OSError, RuntimeError, ValueError) as exc:
|
|
99
|
+
raise BackendError(
|
|
100
|
+
"path_not_allowed",
|
|
101
|
+
"A declared consensus file is unavailable.",
|
|
102
|
+
"Declare readable regular files in approved roots.",
|
|
103
|
+
) from exc
|
|
104
|
+
total_bytes += size
|
|
105
|
+
entries.append(
|
|
106
|
+
{
|
|
107
|
+
"logical_path": str(logical),
|
|
108
|
+
"canonical_path": str(canonical),
|
|
109
|
+
"sha256": digest.hexdigest(),
|
|
110
|
+
"size_bytes": size,
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
return entries
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def check_manifest(manifest: list[dict[str, Any]], root_sets: tuple[tuple[Path, ...], ...]) -> None:
|
|
117
|
+
"""Invalidate when any declared file changes, disappears, or becomes inaccessible."""
|
|
118
|
+
try:
|
|
119
|
+
observed = build_manifest(tuple(entry["logical_path"] for entry in manifest), root_sets)
|
|
120
|
+
except BackendError as exc:
|
|
121
|
+
raise BackendError(
|
|
122
|
+
"consensus_invalidated",
|
|
123
|
+
"Declared consensus context changed or became unavailable.",
|
|
124
|
+
"Start a new consensus; previous responses are invalid.",
|
|
125
|
+
) from exc
|
|
126
|
+
if observed != manifest:
|
|
127
|
+
raise BackendError(
|
|
128
|
+
"consensus_invalidated",
|
|
129
|
+
"Declared consensus context changed.",
|
|
130
|
+
"Start a new consensus; previous responses are invalid.",
|
|
131
|
+
)
|