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.
Files changed (141) hide show
  1. clink/__init__.py +19 -0
  2. clink/agents/__init__.py +30 -0
  3. clink/agents/base.py +70 -0
  4. clink/agents/claude.py +7 -0
  5. clink/agents/codex.py +7 -0
  6. clink/agents/gemini.py +7 -0
  7. clink/allowed_roots.py +356 -0
  8. clink/backend.py +765 -0
  9. clink/cli.py +594 -0
  10. clink/constants.py +78 -0
  11. clink/contracts.py +167 -0
  12. clink/files.py +70 -0
  13. clink/generation.py +274 -0
  14. clink/generation_capabilities.py +207 -0
  15. clink/generation_contracts.py +214 -0
  16. clink/generation_policy.py +174 -0
  17. clink/models.py +243 -0
  18. clink/native.py +409 -0
  19. clink/native_adapters.py +244 -0
  20. clink/native_contracts.py +68 -0
  21. clink/native_generation.py +421 -0
  22. clink/native_generation_claude_policy.py +59 -0
  23. clink/native_generation_contracts.py +264 -0
  24. clink/native_generation_families.py +218 -0
  25. clink/native_generation_family_policy.py +14 -0
  26. clink/native_generation_files.py +153 -0
  27. clink/native_generation_policy.py +82 -0
  28. clink/native_policy.py +86 -0
  29. clink/parsers/__init__.py +34 -0
  30. clink/parsers/agy.py +53 -0
  31. clink/parsers/agy_native.py +161 -0
  32. clink/parsers/agy_native_generation.py +189 -0
  33. clink/parsers/base.py +209 -0
  34. clink/parsers/claude.py +104 -0
  35. clink/parsers/claude_native.py +113 -0
  36. clink/parsers/claude_native_generation.py +346 -0
  37. clink/parsers/codex.py +172 -0
  38. clink/parsers/codex_generation.py +212 -0
  39. clink/parsers/codex_native.py +236 -0
  40. clink/parsers/codex_native_generation.py +367 -0
  41. clink/parsers/gemini.py +110 -0
  42. clink/parsers/grok.py +63 -0
  43. clink/parsers/grok_native.py +91 -0
  44. clink/parsers/grok_native_generation.py +157 -0
  45. clink/policy.py +180 -0
  46. clink/registry.py +544 -0
  47. clink/resources/CODEX-LICENSE +201 -0
  48. clink/resources/CODEX-NOTICE +6 -0
  49. clink/resources/PROVENANCE.md +16 -0
  50. clink/resources/codex-0.166.0-models-direct.json +1284 -0
  51. clink/resources/codex-0.166.0-models-generation.json +1269 -0
  52. clink/resources/native-agy-1.4.12-admission.json +746 -0
  53. clink/resources/native-claude-2.1.304-admission.json +99 -0
  54. clink/resources/native-generation-claude-2.1.304-admission.json +277 -0
  55. clink/resources/native-generation-codex-0.166.0-admission.json +309 -0
  56. clink/resources/native-generation-grok-1.0.70-admission.json +329 -0
  57. clink/resources/native-grok-1.0.70-admission.json +644 -0
  58. clink/runner.py +303 -0
  59. clink/setup.py +399 -0
  60. clink/tasks.py +695 -0
  61. clink_cli-0.2.1.dist-info/METADATA +225 -0
  62. clink_cli-0.2.1.dist-info/RECORD +141 -0
  63. clink_cli-0.2.1.dist-info/WHEEL +5 -0
  64. clink_cli-0.2.1.dist-info/entry_points.txt +3 -0
  65. clink_cli-0.2.1.dist-info/licenses/LICENSE +197 -0
  66. clink_cli-0.2.1.dist-info/licenses/clink/resources/CODEX-LICENSE +201 -0
  67. clink_cli-0.2.1.dist-info/licenses/clink/resources/CODEX-NOTICE +6 -0
  68. clink_cli-0.2.1.dist-info/top_level.txt +7 -0
  69. conf/__init__.py +1 -0
  70. conf/cli_clients/agy.json +40 -0
  71. conf/cli_clients/claude.json +39 -0
  72. conf/cli_clients/codex.json +40 -0
  73. conf/cli_clients/gemini.json +23 -0
  74. conf/cli_clients/grok.json +40 -0
  75. config.py +40 -0
  76. server.py +896 -0
  77. systemprompts/__init__.py +35 -0
  78. systemprompts/analyze_prompt.py +87 -0
  79. systemprompts/chat_prompt.py +57 -0
  80. systemprompts/clink/codex_codereviewer.txt +8 -0
  81. systemprompts/clink/default.txt +8 -0
  82. systemprompts/clink/default_codereviewer.txt +8 -0
  83. systemprompts/clink/default_planner.txt +7 -0
  84. systemprompts/clink/native_codereviewer.txt +1 -0
  85. systemprompts/clink/native_default.txt +1 -0
  86. systemprompts/clink/native_planner.txt +1 -0
  87. systemprompts/codereview_prompt.py +91 -0
  88. systemprompts/consensus_prompt.py +120 -0
  89. systemprompts/debug_prompt.py +150 -0
  90. systemprompts/docgen_prompt.py +252 -0
  91. systemprompts/generate_code_prompt.py +181 -0
  92. systemprompts/planner_prompt.py +127 -0
  93. systemprompts/precommit_prompt.py +179 -0
  94. systemprompts/refactor_prompt.py +323 -0
  95. systemprompts/secaudit_prompt.py +420 -0
  96. systemprompts/testgen_prompt.py +133 -0
  97. systemprompts/thinkdeep_prompt.py +55 -0
  98. systemprompts/tracer_prompt.py +145 -0
  99. tools/__init__.py +43 -0
  100. tools/analyze.py +583 -0
  101. tools/apilookup.py +97 -0
  102. tools/challenge.py +198 -0
  103. tools/chat.py +187 -0
  104. tools/clink.py +130 -0
  105. tools/codereview.py +724 -0
  106. tools/consensus.py +790 -0
  107. tools/debug.py +573 -0
  108. tools/docgen.py +577 -0
  109. tools/listmodels.py +48 -0
  110. tools/models.py +373 -0
  111. tools/planner.py +533 -0
  112. tools/precommit.py +776 -0
  113. tools/refactor.py +645 -0
  114. tools/secaudit.py +685 -0
  115. tools/shared/__init__.py +19 -0
  116. tools/shared/base_models.py +167 -0
  117. tools/shared/base_tool.py +659 -0
  118. tools/shared/exceptions.py +20 -0
  119. tools/shared/schema_builders.py +159 -0
  120. tools/simple/__init__.py +18 -0
  121. tools/simple/base.py +539 -0
  122. tools/testgen.py +513 -0
  123. tools/thinkdeep.py +586 -0
  124. tools/tracer.py +763 -0
  125. tools/version.py +53 -0
  126. tools/workflow/__init__.py +22 -0
  127. tools/workflow/base.py +448 -0
  128. tools/workflow/schema_builders.py +166 -0
  129. tools/workflow/workflow_mixin.py +1195 -0
  130. utils/__init__.py +21 -0
  131. utils/call_state.py +349 -0
  132. utils/client_info.py +293 -0
  133. utils/consensus_manifest.py +131 -0
  134. utils/conversation_memory.py +582 -0
  135. utils/env.py +111 -0
  136. utils/file_types.py +271 -0
  137. utils/file_utils.py +803 -0
  138. utils/image_utils.py +94 -0
  139. utils/security_config.py +163 -0
  140. utils/storage_backend.py +177 -0
  141. 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
+ )