devtorch-core 3.0.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 (193) hide show
  1. devtorch_core/__init__.py +158 -0
  2. devtorch_core/aggphi_textual.py +275 -0
  3. devtorch_core/alerts/__init__.py +23 -0
  4. devtorch_core/alerts/base.py +46 -0
  5. devtorch_core/alerts/config.py +60 -0
  6. devtorch_core/alerts/dispatcher.py +110 -0
  7. devtorch_core/alerts/jira.py +96 -0
  8. devtorch_core/alerts/linear.py +72 -0
  9. devtorch_core/alerts/pagerduty.py +66 -0
  10. devtorch_core/alerts/slack.py +81 -0
  11. devtorch_core/alerts/teams.py +70 -0
  12. devtorch_core/audit/__init__.py +43 -0
  13. devtorch_core/audit/exporter.py +297 -0
  14. devtorch_core/audit/privacy.py +101 -0
  15. devtorch_core/audit/scrubber.py +149 -0
  16. devtorch_core/audit/service.py +67 -0
  17. devtorch_core/audit/signing.py +127 -0
  18. devtorch_core/broadcast/__init__.py +4 -0
  19. devtorch_core/broadcast/broadcaster.py +100 -0
  20. devtorch_core/broadcast/watcher.py +71 -0
  21. devtorch_core/capability.py +639 -0
  22. devtorch_core/cloud/__init__.py +1 -0
  23. devtorch_core/cloud/client_config.py +472 -0
  24. devtorch_core/cloud/client_configs/.claude-opencode-fallback.json +8 -0
  25. devtorch_core/cloud/client_configs/.claude-stdio.json +13 -0
  26. devtorch_core/cloud/client_configs/.cursor-mcp.json +13 -0
  27. devtorch_core/cloud/client_configs/.opencode-bridge.json +13 -0
  28. devtorch_core/cloud/client_configs/.opencode.json +15 -0
  29. devtorch_core/cloud/client_configs/.vscode-mcp.json +13 -0
  30. devtorch_core/cloud/devtorch-mcp-bridge.js +357 -0
  31. devtorch_core/cloud/mcp_client.py +229 -0
  32. devtorch_core/cloud/setup.py +144 -0
  33. devtorch_core/cloud/sync.py +143 -0
  34. devtorch_core/cloud/sync_bundle.py +603 -0
  35. devtorch_core/cloud/sync_conflicts.py +159 -0
  36. devtorch_core/cloud/sync_state.py +159 -0
  37. devtorch_core/cloud/team_sync.py +283 -0
  38. devtorch_core/codex/__init__.py +9 -0
  39. devtorch_core/codex/__main__.py +97 -0
  40. devtorch_core/codex/capture.py +208 -0
  41. devtorch_core/codex/proxy.py +412 -0
  42. devtorch_core/concept_catalog.py +209 -0
  43. devtorch_core/consolidation/__init__.py +3 -0
  44. devtorch_core/consolidation/synthesizer.py +87 -0
  45. devtorch_core/consolidation/workflow.py +175 -0
  46. devtorch_core/daemon/__init__.py +27 -0
  47. devtorch_core/daemon/supervisor.py +293 -0
  48. devtorch_core/daemon/watcher.py +244 -0
  49. devtorch_core/dashboard_api.py +2012 -0
  50. devtorch_core/deltaf.py +97 -0
  51. devtorch_core/disclosure.py +50 -0
  52. devtorch_core/divergence/__init__.py +3 -0
  53. devtorch_core/divergence/detector.py +166 -0
  54. devtorch_core/gateway/__init__.py +32 -0
  55. devtorch_core/gateway/key_manager.py +124 -0
  56. devtorch_core/gateway/metrics_webhook.py +252 -0
  57. devtorch_core/gateway/policy.py +262 -0
  58. devtorch_core/gateway/server.py +727 -0
  59. devtorch_core/gateway/sso.py +233 -0
  60. devtorch_core/gcc.py +1246 -0
  61. devtorch_core/github/__init__.py +35 -0
  62. devtorch_core/github/app.py +240 -0
  63. devtorch_core/github/comment_builder.py +113 -0
  64. devtorch_core/github/pat.py +76 -0
  65. devtorch_core/github/pr_parser.py +82 -0
  66. devtorch_core/github/pr_reporter.py +555 -0
  67. devtorch_core/gitlab/__init__.py +177 -0
  68. devtorch_core/hitl/__init__.py +4 -0
  69. devtorch_core/hitl/channels.py +129 -0
  70. devtorch_core/hitl/orchestrator.py +95 -0
  71. devtorch_core/hooks/__init__.py +17 -0
  72. devtorch_core/hooks/claude_code.py +228 -0
  73. devtorch_core/hooks/git_capture.py +341 -0
  74. devtorch_core/hooks/git_commit.py +182 -0
  75. devtorch_core/hooks/installer.py +733 -0
  76. devtorch_core/hooks/pre_commit.py +157 -0
  77. devtorch_core/hooks/runner.py +344 -0
  78. devtorch_core/identity/__init__.py +4 -0
  79. devtorch_core/identity/agent.py +86 -0
  80. devtorch_core/identity/providers.py +85 -0
  81. devtorch_core/invariants.py +182 -0
  82. devtorch_core/mcp/__init__.py +10 -0
  83. devtorch_core/mcp/auth.py +177 -0
  84. devtorch_core/mcp/server.py +1049 -0
  85. devtorch_core/metrics/__init__.py +35 -0
  86. devtorch_core/metrics/aggregate.py +215 -0
  87. devtorch_core/metrics/calibrate.py +198 -0
  88. devtorch_core/metrics/calibration.py +125 -0
  89. devtorch_core/metrics/credibility.py +288 -0
  90. devtorch_core/metrics/delivery_time.py +70 -0
  91. devtorch_core/metrics/dhs.py +126 -0
  92. devtorch_core/metrics/mcs.py +96 -0
  93. devtorch_core/metrics/roi.py +88 -0
  94. devtorch_core/metrics/session_writer.py +81 -0
  95. devtorch_core/metrics/shadow_ai.py +117 -0
  96. devtorch_core/metrics/sprint_writer.py +243 -0
  97. devtorch_core/observability/__init__.py +78 -0
  98. devtorch_core/observability/datadog.py +157 -0
  99. devtorch_core/observability/formatter.py +119 -0
  100. devtorch_core/observability/report.py +264 -0
  101. devtorch_core/observability/servicenow.py +147 -0
  102. devtorch_core/observability/splunk.py +218 -0
  103. devtorch_core/observability/webhook.py +227 -0
  104. devtorch_core/parser/__init__.py +30 -0
  105. devtorch_core/parser/blocks.py +216 -0
  106. devtorch_core/parser/inference.py +159 -0
  107. devtorch_core/parser/thinking.py +112 -0
  108. devtorch_core/projects.py +169 -0
  109. devtorch_core/prompt_artifact.py +76 -0
  110. devtorch_core/proxy/__init__.py +9 -0
  111. devtorch_core/proxy/routes/__init__.py +1 -0
  112. devtorch_core/proxy/routes/anthropic.py +264 -0
  113. devtorch_core/proxy/routes/azure_openai.py +336 -0
  114. devtorch_core/proxy/routes/gemini.py +331 -0
  115. devtorch_core/proxy/routes/groq.py +284 -0
  116. devtorch_core/proxy/routes/ollama.py +279 -0
  117. devtorch_core/proxy/routes/openai.py +287 -0
  118. devtorch_core/proxy/server.py +356 -0
  119. devtorch_core/query/__init__.py +15 -0
  120. devtorch_core/query/grep.py +181 -0
  121. devtorch_core/query/hybrid.py +86 -0
  122. devtorch_core/query/semantic.py +157 -0
  123. devtorch_core/rdp.py +105 -0
  124. devtorch_core/reasoning/__init__.py +4 -0
  125. devtorch_core/reasoning/entry.py +31 -0
  126. devtorch_core/reasoning/store.py +122 -0
  127. devtorch_core/reasoning_plus/__init__.py +70 -0
  128. devtorch_core/reasoning_plus/augmenter.py +326 -0
  129. devtorch_core/reasoning_plus/capture.py +51 -0
  130. devtorch_core/reasoning_plus/config.py +256 -0
  131. devtorch_core/reasoning_plus/context.py +262 -0
  132. devtorch_core/reasoning_plus/learning/__init__.py +72 -0
  133. devtorch_core/reasoning_plus/learning/analytics.py +141 -0
  134. devtorch_core/reasoning_plus/learning/api.py +313 -0
  135. devtorch_core/reasoning_plus/learning/chain.py +285 -0
  136. devtorch_core/reasoning_plus/learning/composer.py +74 -0
  137. devtorch_core/reasoning_plus/learning/cross_project.py +234 -0
  138. devtorch_core/reasoning_plus/learning/embeddings.py +209 -0
  139. devtorch_core/reasoning_plus/learning/extractor.py +207 -0
  140. devtorch_core/reasoning_plus/learning/models.py +116 -0
  141. devtorch_core/reasoning_plus/learning/provenance.py +126 -0
  142. devtorch_core/reasoning_plus/learning/recorder.py +81 -0
  143. devtorch_core/reasoning_plus/learning/relevance.py +122 -0
  144. devtorch_core/reasoning_plus/learning/state.py +86 -0
  145. devtorch_core/reasoning_plus/learning/store.py +160 -0
  146. devtorch_core/reasoning_plus/learning/theta_learning_bridge.py +94 -0
  147. devtorch_core/reasoning_plus/prompt.py +90 -0
  148. devtorch_core/rep.py +134 -0
  149. devtorch_core/rep_network/__init__.py +25 -0
  150. devtorch_core/rep_network/merge.py +70 -0
  151. devtorch_core/rep_network/node.py +137 -0
  152. devtorch_core/rep_network/server.py +140 -0
  153. devtorch_core/rep_network/sync.py +207 -0
  154. devtorch_core/sensitivity.py +182 -0
  155. devtorch_core/serve.py +258 -0
  156. devtorch_core/session/__init__.py +39 -0
  157. devtorch_core/session/disagreement.py +188 -0
  158. devtorch_core/session/models.py +114 -0
  159. devtorch_core/session/orchestrator.py +182 -0
  160. devtorch_core/session/planner.py +169 -0
  161. devtorch_core/session/simulator.py +132 -0
  162. devtorch_core/signing.py +290 -0
  163. devtorch_core/sis.py +197 -0
  164. devtorch_core/storage.py +308 -0
  165. devtorch_core/templates/__init__.py +6 -0
  166. devtorch_core/templates/engine.py +122 -0
  167. devtorch_core/templates/go.py +18 -0
  168. devtorch_core/templates/infra.py +19 -0
  169. devtorch_core/templates/library/__init__.py +18 -0
  170. devtorch_core/templates/library/api_design.md +27 -0
  171. devtorch_core/templates/library/bug_fix.md +27 -0
  172. devtorch_core/templates/library/decision_record.md +27 -0
  173. devtorch_core/templates/library/engine.py +228 -0
  174. devtorch_core/templates/library/security_review.md +30 -0
  175. devtorch_core/templates/python.py +19 -0
  176. devtorch_core/templates/react.py +18 -0
  177. devtorch_core/templates/typescript.py +18 -0
  178. devtorch_core/theta.py +221 -0
  179. devtorch_core/theta_synthesis.py +268 -0
  180. devtorch_core/topics.py +320 -0
  181. devtorch_core/variance.py +219 -0
  182. devtorch_core/wrapper/__init__.py +52 -0
  183. devtorch_core/wrapper/anthropic.py +487 -0
  184. devtorch_core/wrapper/base.py +562 -0
  185. devtorch_core/wrapper/bedrock.py +342 -0
  186. devtorch_core/wrapper/gemini.py +422 -0
  187. devtorch_core/wrapper/ollama.py +527 -0
  188. devtorch_core/wrapper/openai.py +461 -0
  189. devtorch_core-3.0.1.dist-info/METADATA +867 -0
  190. devtorch_core-3.0.1.dist-info/RECORD +193 -0
  191. devtorch_core-3.0.1.dist-info/WHEEL +5 -0
  192. devtorch_core-3.0.1.dist-info/entry_points.txt +2 -0
  193. devtorch_core-3.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,308 @@
1
+ from __future__ import annotations
2
+
3
+ import fcntl
4
+ from pathlib import Path
5
+ from typing import Any, Callable, Protocol, runtime_checkable
6
+
7
+
8
+ @runtime_checkable
9
+ class StorageBackend(Protocol):
10
+ def read_bytes(self, key: str) -> bytes: ...
11
+ def write_bytes(self, key: str, data: bytes) -> None: ...
12
+ def append_bytes_atomic(self, key: str, data: bytes) -> None: ...
13
+ def read_and_append_atomic(self, key: str, transform: Callable[[bytes], bytes]) -> None: ...
14
+ def exists(self, key: str) -> bool: ...
15
+ def list_keys(self, prefix: str) -> list[str]: ...
16
+ def delete(self, key: str) -> None: ...
17
+
18
+
19
+ class LocalFileBackend:
20
+ """StorageBackend backed by the local filesystem. Thread-safe via fcntl.flock."""
21
+
22
+ def __init__(self, root: Path) -> None:
23
+ self._root = root
24
+
25
+ def _path(self, key: str) -> Path:
26
+ p = (self._root / key).resolve()
27
+ if not str(p).startswith(str(self._root.resolve())):
28
+ raise ValueError(f"Key escapes root: {key!r}")
29
+ return p
30
+
31
+ def read_bytes(self, key: str) -> bytes:
32
+ return self._path(key).read_bytes()
33
+
34
+ def write_bytes(self, key: str, data: bytes) -> None:
35
+ p = self._path(key)
36
+ p.parent.mkdir(parents=True, exist_ok=True)
37
+ tmp = p.with_suffix(p.suffix + ".tmp")
38
+ tmp.write_bytes(data)
39
+ tmp.replace(p)
40
+
41
+ def append_bytes_atomic(self, key: str, data: bytes) -> None:
42
+ p = self._path(key)
43
+ p.parent.mkdir(parents=True, exist_ok=True)
44
+ with p.open("ab") as f:
45
+ fcntl.flock(f, fcntl.LOCK_EX)
46
+ try:
47
+ f.write(data)
48
+ f.flush()
49
+ finally:
50
+ fcntl.flock(f, fcntl.LOCK_UN)
51
+
52
+ def read_and_append_atomic(self, key: str, transform: Callable[[bytes], bytes]) -> None:
53
+ """Read current content, call transform(content) to get bytes to append, write atomically."""
54
+ p = self._path(key)
55
+ p.parent.mkdir(parents=True, exist_ok=True)
56
+ with p.open("a+b") as f:
57
+ fcntl.flock(f, fcntl.LOCK_EX)
58
+ try:
59
+ f.seek(0)
60
+ current = f.read()
61
+ new_data = transform(current)
62
+ f.seek(0, 2)
63
+ f.write(new_data)
64
+ f.flush()
65
+ finally:
66
+ fcntl.flock(f, fcntl.LOCK_UN)
67
+
68
+ def exists(self, key: str) -> bool:
69
+ return self._path(key).exists()
70
+
71
+ def list_keys(self, prefix: str) -> list[str]:
72
+ base = self._root / prefix
73
+ if not base.exists():
74
+ return []
75
+ return sorted(
76
+ str(p.relative_to(self._root))
77
+ for p in base.rglob("*")
78
+ if p.is_file()
79
+ )
80
+
81
+ def delete(self, key: str) -> None:
82
+ self._path(key).unlink(missing_ok=True)
83
+
84
+
85
+ class R2Backend:
86
+ """StorageBackend backed by Cloudflare R2 (S3-compatible). Uses conditional PUTs for atomic append."""
87
+
88
+ def __init__(self, client: Any, bucket: str, prefix: str) -> None:
89
+ # prefix = "{org_id}/{repo_id}/.GCC" (no trailing slash)
90
+ self._s3 = client
91
+ self._bucket = bucket
92
+ self._prefix = prefix.rstrip("/")
93
+
94
+ def _key(self, relative: str) -> str:
95
+ return f"{self._prefix}/{relative}"
96
+
97
+ def read_bytes(self, key: str) -> bytes:
98
+ try:
99
+ resp = self._s3.get_object(Bucket=self._bucket, Key=self._key(key))
100
+ return resp["Body"].read()
101
+ except self._s3.exceptions.ClientError as e:
102
+ if e.response["Error"]["Code"] in ("NoSuchKey", "404"):
103
+ raise FileNotFoundError(key)
104
+ raise
105
+
106
+ def write_bytes(self, key: str, data: bytes) -> None:
107
+ self._s3.put_object(Bucket=self._bucket, Key=self._key(key), Body=data)
108
+
109
+ def append_bytes_atomic(self, key: str, data: bytes) -> None:
110
+ self.read_and_append_atomic(key, lambda current: data)
111
+
112
+ def read_and_append_atomic(self, key: str, transform: Callable[[bytes], bytes]) -> None:
113
+ full_key = self._key(key)
114
+ for attempt in range(10):
115
+ try:
116
+ resp = self._s3.get_object(Bucket=self._bucket, Key=full_key)
117
+ current = resp["Body"].read()
118
+ etag = resp["ETag"].strip('"')
119
+ except self._s3.exceptions.ClientError as e:
120
+ if e.response["Error"]["Code"] in ("NoSuchKey", "404"):
121
+ current, etag = b"", None
122
+ else:
123
+ raise
124
+
125
+ new_data = transform(current)
126
+ new_content = current + new_data
127
+
128
+ try:
129
+ kwargs: dict = {"Bucket": self._bucket, "Key": full_key, "Body": new_content}
130
+ if etag:
131
+ kwargs["IfMatch"] = f'"{etag}"'
132
+ else:
133
+ # Blob did not exist — use If-None-Match: * to guard concurrent creates.
134
+ kwargs["IfNoneMatch"] = "*"
135
+ self._s3.put_object(**kwargs)
136
+ return
137
+ except self._s3.exceptions.ClientError as e:
138
+ if e.response["Error"]["Code"] in ("PreconditionFailed", "ConditionalRequestConflict"):
139
+ continue
140
+ raise
141
+ raise RuntimeError(f"R2Backend: failed to atomically update {key} after 10 retries")
142
+
143
+ def exists(self, key: str) -> bool:
144
+ try:
145
+ self.read_bytes(key)
146
+ return True
147
+ except FileNotFoundError:
148
+ return False
149
+
150
+ def list_keys(self, prefix: str) -> list[str]:
151
+ full_prefix = self._key(prefix)
152
+ resp = self._s3.list_objects_v2(Bucket=self._bucket, Prefix=full_prefix)
153
+ contents = resp.get("Contents", [])
154
+ strip = self._prefix + "/"
155
+ return sorted(item["Key"].removeprefix(strip) for item in contents)
156
+
157
+ def delete(self, key: str) -> None:
158
+ self._s3.delete_object(Bucket=self._bucket, Key=self._key(key))
159
+
160
+
161
+ class AzureBlobBackend:
162
+ """StorageBackend backed by Azure Blob Storage. Uses ETag conditional PUTs for atomic append."""
163
+
164
+ def __init__(self, client: Any, container: str, prefix: str) -> None:
165
+ # prefix = "{org_id}/{repo_id}/.GCC" (no trailing slash)
166
+ self._service = client
167
+ self._container = container
168
+ self._prefix = prefix.rstrip("/")
169
+ self._container_client = client.get_container_client(container)
170
+
171
+ def _blob_name(self, relative: str) -> str:
172
+ return f"{self._prefix}/{relative}" if self._prefix else relative
173
+
174
+ def read_bytes(self, key: str) -> bytes:
175
+ from azure.core.exceptions import ResourceNotFoundError
176
+ try:
177
+ blob_client = self._container_client.get_blob_client(self._blob_name(key))
178
+ downloader = blob_client.download_blob()
179
+ return downloader.readall()
180
+ except ResourceNotFoundError as exc:
181
+ raise FileNotFoundError(key) from exc
182
+
183
+ def write_bytes(self, key: str, data: bytes) -> None:
184
+ blob_client = self._container_client.get_blob_client(self._blob_name(key))
185
+ blob_client.upload_blob(data, overwrite=True)
186
+
187
+ def append_bytes_atomic(self, key: str, data: bytes) -> None:
188
+ self.read_and_append_atomic(key, lambda _current: data)
189
+
190
+ def read_and_append_atomic(self, key: str, transform: Callable[[bytes], bytes]) -> None:
191
+ from azure.core.exceptions import ResourceExistsError, ResourceModifiedError, ResourceNotFoundError
192
+
193
+ try:
194
+ from azure.core import MatchConditions
195
+ except ImportError:
196
+ from azure.core.conditions import MatchConditions # type: ignore[no-redef]
197
+ blob_client = self._container_client.get_blob_client(self._blob_name(key))
198
+
199
+ for _attempt in range(10):
200
+ try:
201
+ downloader = blob_client.download_blob()
202
+ current = downloader.readall()
203
+ etag = downloader.properties["etag"].strip('"')
204
+ except ResourceNotFoundError:
205
+ current, etag = b"", None
206
+
207
+ new_data = transform(current)
208
+ new_content = current + new_data
209
+
210
+ try:
211
+ if etag:
212
+ blob_client.upload_blob(
213
+ new_content,
214
+ overwrite=True,
215
+ etag=etag,
216
+ match_condition=MatchConditions.IfNotModified,
217
+ )
218
+ else:
219
+ # Blob did not exist — use overwrite=False to guard concurrent creates.
220
+ blob_client.upload_blob(new_content, overwrite=False)
221
+ return
222
+ except (ResourceModifiedError, ResourceExistsError):
223
+ continue
224
+ raise RuntimeError(f"AzureBlobBackend: failed to atomically update {key} after 10 retries")
225
+
226
+ def exists(self, key: str) -> bool:
227
+ from azure.core.exceptions import ResourceNotFoundError
228
+ try:
229
+ self._container_client.get_blob_client(self._blob_name(key)).get_blob_properties()
230
+ return True
231
+ except ResourceNotFoundError:
232
+ return False
233
+
234
+ def list_keys(self, prefix: str) -> list[str]:
235
+ if self._prefix:
236
+ full_prefix = f"{self._prefix}/{prefix}" if prefix else self._prefix + "/"
237
+ else:
238
+ full_prefix = prefix
239
+ blobs = self._container_client.list_blobs(name_starts_with=full_prefix)
240
+ strip = self._prefix + "/" if self._prefix else ""
241
+ return sorted(b.name.removeprefix(strip) for b in blobs)
242
+
243
+ def delete(self, key: str) -> None:
244
+ from azure.core.exceptions import ResourceNotFoundError
245
+ blob_client = self._container_client.get_blob_client(self._blob_name(key))
246
+ try:
247
+ blob_client.delete_blob()
248
+ except ResourceNotFoundError:
249
+ pass
250
+
251
+
252
+ def create_backend_from_env(prefix: str = "") -> "StorageBackend":
253
+ """
254
+ Create a StorageBackend from environment variables.
255
+
256
+ DEVTORCH_STORAGE controls which backend to use:
257
+ r2 — Cloudflare R2 (boto3, requires R2_ACCOUNT_ID, R2_BUCKET, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY)
258
+ s3 — Native AWS S3 (boto3, requires AWS_S3_BUCKET; uses IAM/env auth automatically)
259
+ azure — Azure Blob Storage (requires DEVTORCH_AZURE_CONTAINER + AZURE_STORAGE_CONNECTION_STRING
260
+ or AZURE_STORAGE_ACCOUNT_URL)
261
+ local — Local filesystem (uses DEVTORCH_GCC_PATH or current directory)
262
+
263
+ prefix is prepended to all storage keys (e.g. "{org_id}" for auth backends).
264
+ """
265
+ import os
266
+
267
+ storage = os.environ.get("DEVTORCH_STORAGE", "r2").lower()
268
+
269
+ if storage == "r2":
270
+ import boto3
271
+ bucket = os.environ.get("DEVTORCH_BUCKET") or os.environ["R2_BUCKET"]
272
+ account_id = os.environ["R2_ACCOUNT_ID"]
273
+ access_key = os.environ.get("DEVTORCH_ACCESS_KEY_ID") or os.environ["R2_ACCESS_KEY_ID"]
274
+ secret_key = os.environ.get("DEVTORCH_SECRET_ACCESS_KEY") or os.environ["R2_SECRET_ACCESS_KEY"]
275
+ s3 = boto3.client(
276
+ "s3",
277
+ endpoint_url=f"https://{account_id}.r2.cloudflarestorage.com",
278
+ aws_access_key_id=access_key,
279
+ aws_secret_access_key=secret_key,
280
+ region_name="auto",
281
+ )
282
+ return R2Backend(client=s3, bucket=bucket, prefix=prefix)
283
+
284
+ elif storage == "s3":
285
+ import boto3
286
+ bucket = os.environ.get("DEVTORCH_BUCKET") or os.environ["AWS_S3_BUCKET"]
287
+ s3 = boto3.client("s3") # uses IAM role, env vars, or ~/.aws/credentials
288
+ return R2Backend(client=s3, bucket=bucket, prefix=prefix)
289
+
290
+ elif storage == "azure":
291
+ from azure.storage.blob import BlobServiceClient
292
+ container = os.environ["DEVTORCH_AZURE_CONTAINER"]
293
+ conn_str = os.environ.get("AZURE_STORAGE_CONNECTION_STRING")
294
+ if conn_str:
295
+ service = BlobServiceClient.from_connection_string(conn_str)
296
+ else:
297
+ account_url = os.environ["AZURE_STORAGE_ACCOUNT_URL"]
298
+ service = BlobServiceClient(account_url=account_url)
299
+ return AzureBlobBackend(client=service, container=container, prefix=prefix)
300
+
301
+ elif storage == "local":
302
+ gcc_path = Path(os.environ.get("DEVTORCH_GCC_PATH", "."))
303
+ return LocalFileBackend(gcc_path)
304
+
305
+ else:
306
+ raise ValueError(
307
+ f"Unknown DEVTORCH_STORAGE: {storage!r}. Must be one of: r2, s3, azure, local."
308
+ )
@@ -0,0 +1,6 @@
1
+ """Project templates for `devtorch init --template`."""
2
+ from __future__ import annotations
3
+
4
+ from devtorch_core.templates.engine import apply_template, cmd_init_template, list_templates
5
+
6
+ __all__ = ["apply_template", "cmd_init_template", "list_templates"]
@@ -0,0 +1,122 @@
1
+ """Built-in project templates for `devtorch init --template`."""
2
+ from __future__ import annotations
3
+
4
+ import sys
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from devtorch_core import GCCRepository
10
+
11
+
12
+ @dataclass
13
+ class Template:
14
+ """A project template defines default concepts and a governance note."""
15
+
16
+ name: str
17
+ display_name: str
18
+ project_type: str
19
+ concepts: list[tuple[str, str]]
20
+ governance_note: str
21
+
22
+
23
+ # Built-in template registry keyed by template name.
24
+ _TEMPLATES: dict[str, Template] = {}
25
+
26
+
27
+ def register_template(template: Template) -> None:
28
+ """Register a built-in or custom template."""
29
+ _TEMPLATES[template.name] = template
30
+
31
+
32
+ def _load_builtin_templates() -> None:
33
+ """Lazily import and register built-in templates."""
34
+ if _TEMPLATES:
35
+ return
36
+ from devtorch_core.templates import python, typescript, react, go, infra
37
+
38
+ for module in (python, typescript, react, go, infra):
39
+ register_template(module.TEMPLATE)
40
+
41
+
42
+ def list_templates() -> list[dict[str, Any]]:
43
+ """Return a list of available templates with metadata."""
44
+ _load_builtin_templates()
45
+ return [
46
+ {
47
+ "name": t.name,
48
+ "display_name": t.display_name,
49
+ "project_type": t.project_type,
50
+ }
51
+ for t in _TEMPLATES.values()
52
+ ]
53
+
54
+
55
+ def _get_template(name: str) -> Template:
56
+ """Look up a template by name; raise ValueError if unknown."""
57
+ _load_builtin_templates()
58
+ try:
59
+ return _TEMPLATES[name]
60
+ except KeyError as exc:
61
+ raise ValueError(f"Unknown template: {name}") from exc
62
+
63
+
64
+ def apply_template(
65
+ gcc_dir: Path | str,
66
+ template_name: str,
67
+ project_type: str | None = None,
68
+ ) -> None:
69
+ """
70
+ Apply a project template to a GCC repository.
71
+
72
+ The repository at `gcc_dir` is initialized if it has not been already.
73
+ Default concepts are written to `.GCC/concepts/` and the template's
74
+ governance note is persisted to `.GCC/templates/policy.md`.
75
+ """
76
+ template = _get_template(template_name)
77
+ repo = GCCRepository.at(Path(gcc_dir).resolve())
78
+
79
+ if not repo.is_initialized():
80
+ repo.init()
81
+
82
+ for concept_name, definition in template.concepts:
83
+ repo.concept_add(concept_name, definition)
84
+
85
+ policy_dir = repo.gcc_dir / "templates"
86
+ policy_dir.mkdir(parents=True, exist_ok=True)
87
+ policy_path = policy_dir / "policy.md"
88
+
89
+ effective_type = project_type or template.project_type
90
+ note = f"""# Template: {template.display_name}
91
+
92
+ Project type: {effective_type}
93
+ Default governance focus: {template.governance_note}
94
+
95
+ Applied concepts: {', '.join(name for name, _ in template.concepts)}
96
+ """
97
+ policy_path.write_text(note, encoding="utf-8")
98
+
99
+
100
+ def cmd_init_template(args: Any) -> int:
101
+ """
102
+ Helper for CLI integration with `devtorch init --template <type>`.
103
+
104
+ `args` is a Namespace-like object with at least `path` and `template`.
105
+ Optional `project_type` customizes the template's declared project type.
106
+ """
107
+ template_name = getattr(args, "template", None)
108
+ if not template_name:
109
+ print("error: --template is required", file=sys.stderr)
110
+ return 1
111
+
112
+ project_root = Path(getattr(args, "path", ".")).resolve()
113
+ project_type = getattr(args, "project_type", None)
114
+
115
+ try:
116
+ apply_template(project_root, template_name, project_type)
117
+ except ValueError as e:
118
+ print(f"error: {e}", file=sys.stderr)
119
+ return 1
120
+
121
+ print(f"Applied template '{template_name}' to {project_root}")
122
+ return 0
@@ -0,0 +1,18 @@
1
+ """Built-in Go project template."""
2
+ from __future__ import annotations
3
+
4
+ from devtorch_core.templates.engine import Template
5
+
6
+ TEMPLATE = Template(
7
+ name="go",
8
+ display_name="Go",
9
+ project_type="go",
10
+ concepts=[
11
+ ("auth", "Authentication, authorization, and identity management."),
12
+ ("schema", "Structs, validation, and serialization contracts."),
13
+ ("api", "HTTP handlers, gRPC services, and public interfaces."),
14
+ ("data", "Storage, repositories, and data access patterns."),
15
+ ("cmd", "Command-line entry points and service binaries."),
16
+ ],
17
+ governance_note="Go projects default to explicit interfaces, small command entry points, and clear separation between API handlers, business logic, and data repositories.",
18
+ )
@@ -0,0 +1,19 @@
1
+ """Built-in infrastructure project template."""
2
+ from __future__ import annotations
3
+
4
+ from devtorch_core.templates.engine import Template
5
+
6
+ TEMPLATE = Template(
7
+ name="infra",
8
+ display_name="Infrastructure",
9
+ project_type="infra",
10
+ concepts=[
11
+ ("auth", "Identity, access control, and least-privilege policies."),
12
+ ("schema", "Resource definitions, configuration schemas, and versioning."),
13
+ ("api", "Gateway, load balancer, and service mesh configuration."),
14
+ ("data", "Databases, object storage, and backup policies."),
15
+ ("networking", "VPCs, subnets, DNS, and firewall rules."),
16
+ ("secrets", "Secret management, rotation, and encryption at rest."),
17
+ ],
18
+ governance_note="Infrastructure projects default to declarative resource definitions, least-privilege access, explicit networking boundaries, and auditable secret management.",
19
+ )
@@ -0,0 +1,18 @@
1
+ """Reasoning template library package."""
2
+ from __future__ import annotations
3
+
4
+ from devtorch_core.templates.library.engine import (
5
+ apply_template,
6
+ discover_custom_templates,
7
+ list_builtin_templates,
8
+ load_builtin_template,
9
+ render_template,
10
+ )
11
+
12
+ __all__ = [
13
+ "apply_template",
14
+ "discover_custom_templates",
15
+ "list_builtin_templates",
16
+ "load_builtin_template",
17
+ "render_template",
18
+ ]
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: api_design
3
+ description: Design rationale for an API or interface change
4
+ tags: [reasoning, api, design]
5
+ ---
6
+ # API Design: {{title}}
7
+
8
+ ## Goal
9
+ {{goal}}
10
+
11
+ ## Constraints
12
+ {{constraints}}
13
+
14
+ ## Proposed interface
15
+ {{interface}}
16
+
17
+ ## Design rationale
18
+ {{reasoning}}
19
+
20
+ ## Alternatives considered
21
+ {{alternatives}}
22
+
23
+ ## Risks
24
+ {{risks}}
25
+
26
+ ## Migration plan
27
+ {{migration}}
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: bug_fix
3
+ description: Capture the investigation, root cause, and fix for a bug
4
+ tags: [reasoning, bug, debugging]
5
+ ---
6
+ # Bug Fix: {{title}}
7
+
8
+ ## Observed behaviour
9
+ {{observed}}
10
+
11
+ ## Expected behaviour
12
+ {{expected}}
13
+
14
+ ## Reproduction steps
15
+ {{repro_steps}}
16
+
17
+ ## Root cause analysis
18
+ {{reasoning}}
19
+
20
+ ## Fix
21
+ {{fix}}
22
+
23
+ ## Verification
24
+ {{verification}}
25
+
26
+ ## Prevention
27
+ {{prevention}}
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: decision_record
3
+ description: Structured reasoning record for architectural or product decisions
4
+ tags: [reasoning, decision, architecture]
5
+ ---
6
+ # Decision Record: {{title}}
7
+
8
+ ## Context
9
+ {{context}}
10
+
11
+ ## Problem statement
12
+ {{problem}}
13
+
14
+ ## Options considered
15
+ {{options}}
16
+
17
+ ## Decision
18
+ {{decision}}
19
+
20
+ ## Rationale
21
+ {{reasoning}}
22
+
23
+ ## Consequences
24
+ {{consequences}}
25
+
26
+ ## Related concepts
27
+ {{related_concepts}}