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,127 @@
1
+ """Optional asymmetric signing of exported audit reports.
2
+
3
+ The existing ``devtorch_core.signing`` module signs individual event-log lines
4
+ with HMAC-SHA256. This module is for exported JSON/PDF audit reports: it uses
5
+ asymmetric Ed25519 so anyone with the public key can verify the report without
6
+ accessing the private signing key.
7
+
8
+ ``cryptography`` is an optional dependency. If it is unavailable, signing
9
+ functions raise ``AuditSignatureError``.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import base64
15
+ import json
16
+ from pathlib import Path
17
+ from typing import Any, Dict, Optional
18
+
19
+
20
+ class AuditSignatureError(RuntimeError):
21
+ """Raised when signing or verification cannot proceed."""
22
+
23
+
24
+ class ExportSigner:
25
+ """Ed25519 signer for exported audit JSON."""
26
+
27
+ ALGORITHM = "ed25519"
28
+
29
+ def __init__(self) -> None:
30
+ self._cryptography = self._load_cryptography()
31
+
32
+ @staticmethod
33
+ def _load_cryptography():
34
+ try:
35
+ import cryptography # noqa: F401
36
+ from cryptography.hazmat.primitives.asymmetric import ed25519
37
+ from cryptography.hazmat.primitives import serialization
38
+ return (ed25519, serialization)
39
+ except ImportError as exc:
40
+ raise AuditSignatureError(
41
+ "cryptography is required for export signing; install devtorch-core[audit]"
42
+ ) from exc
43
+
44
+ def generate_keypair(self) -> tuple[bytes, bytes]:
45
+ """Return (private_key_pem, public_key_pem) as bytes."""
46
+ ed25519, serialization = self._cryptography
47
+ private_key = ed25519.Ed25519PrivateKey.generate()
48
+ private_pem = private_key.private_bytes(
49
+ encoding=serialization.Encoding.PEM,
50
+ format=serialization.PrivateFormat.PKCS8,
51
+ encryption_algorithm=serialization.NoEncryption(),
52
+ )
53
+ public_pem = private_key.public_key().public_bytes(
54
+ encoding=serialization.Encoding.PEM,
55
+ format=serialization.PublicFormat.SubjectPublicKeyInfo,
56
+ )
57
+ return private_pem, public_pem
58
+
59
+ def save_key(self, path: Path, key_pem: bytes) -> None:
60
+ """Persist a PEM key with restrictive permissions."""
61
+ path.write_bytes(key_pem)
62
+ try:
63
+ path.chmod(0o600)
64
+ except (OSError, NotImplementedError):
65
+ pass
66
+
67
+ def load_private_key(self, path: Path):
68
+ """Load an Ed25519 private key from a PEM file."""
69
+ _, serialization = self._cryptography
70
+ data = path.read_bytes()
71
+ return serialization.load_pem_private_key(data, password=None)
72
+
73
+ def load_public_key(self, path: Path):
74
+ """Load an Ed25519 public key from a PEM file."""
75
+ _, serialization = self._cryptography
76
+ data = path.read_bytes()
77
+ return serialization.load_pem_public_key(data)
78
+
79
+ def sign(self, data: bytes, private_key) -> bytes:
80
+ """Sign bytes and return a base64-encoded signature."""
81
+ signature = private_key.sign(data)
82
+ return base64.b64encode(signature)
83
+
84
+ def verify(self, data: bytes, signature_b64: bytes, public_key) -> bool:
85
+ """Verify a base64-encoded signature against data."""
86
+ try:
87
+ signature = base64.b64decode(signature_b64)
88
+ public_key.verify(signature, data)
89
+ return True
90
+ except Exception:
91
+ return False
92
+
93
+ def sign_json(self, data: Dict[str, Any], private_key) -> Dict[str, Any]:
94
+ """Return a signed envelope wrapping the JSON data."""
95
+ payload_bytes = json.dumps(data, sort_keys=True, separators=(",", ":")).encode("utf-8")
96
+ signature = self.sign(payload_bytes, private_key)
97
+ return {
98
+ "algorithm": self.ALGORITHM,
99
+ "payload": data,
100
+ "signature": signature.decode("utf-8"),
101
+ }
102
+
103
+ def verify_json(self, envelope: Dict[str, Any], public_key) -> bool:
104
+ """Verify a signed envelope."""
105
+ if envelope.get("algorithm") != self.ALGORITHM:
106
+ return False
107
+ payload = envelope.get("payload")
108
+ if payload is None:
109
+ return False
110
+ signature_b64 = envelope.get("signature", "").encode("utf-8")
111
+ payload_bytes = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
112
+ return self.verify(payload_bytes, signature_b64, public_key)
113
+
114
+ def sign_file(self, input_path: Path, output_path: Path, private_key_path: Path) -> None:
115
+ """Sign a JSON file and write a signed envelope to ``output_path``."""
116
+ data = json.loads(input_path.read_text(encoding="utf-8"))
117
+ private_key = self.load_private_key(private_key_path)
118
+ envelope = self.sign_json(data, private_key)
119
+ output_path.write_text(json.dumps(envelope, indent=2), encoding="utf-8")
120
+
121
+ def verify_file(self, envelope_path: Path, public_key_path: Path) -> bool:
122
+ """Verify a signed envelope file."""
123
+ envelope = json.loads(envelope_path.read_text(encoding="utf-8"))
124
+ public_key = self.load_public_key(public_key_path)
125
+ return self.verify_json(envelope, public_key)
126
+
127
+
@@ -0,0 +1,4 @@
1
+ from .broadcaster import EventBroadcaster, create_broadcast_app
2
+ from .watcher import GCCWatcher
3
+
4
+ __all__ = ["EventBroadcaster", "create_broadcast_app", "GCCWatcher"]
@@ -0,0 +1,100 @@
1
+ """
2
+ Local SSE broadcaster for DevTorch coordination events.
3
+
4
+ Agents subscribe to GET /events/stream and receive coordination-relevant
5
+ events as they are appended to .GCC/events.log.jsonl.
6
+
7
+ Only broadcasts events whose event_type contains one of:
8
+ DIVERGENCE, CONSOLIDATION, HITL, CONSENSUS
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ import json
14
+ import threading
15
+ from pathlib import Path
16
+ from typing import List, Optional, Set
17
+
18
+ COORDINATION_TYPES: Set[str] = {"DIVERGENCE", "CONSOLIDATION", "HITL", "CONSENSUS"}
19
+
20
+
21
+ def _is_coordination_event(event_type: str) -> bool:
22
+ """Return True if any coordination keyword appears in the event_type string."""
23
+ upper = event_type.upper()
24
+ return any(kw in upper for kw in COORDINATION_TYPES)
25
+
26
+
27
+ class EventBroadcaster:
28
+ """Thread-safe in-process event broadcaster backed by asyncio queues."""
29
+
30
+ def __init__(self) -> None:
31
+ self._queues: List[asyncio.Queue] = []
32
+ self._lock = threading.Lock()
33
+
34
+ def subscribe(self) -> asyncio.Queue:
35
+ # asyncio.Queue.put_nowait() and get_nowait() are safe to call from threads
36
+ # under CPython's GIL (deque operations are atomic). The async await q.get()
37
+ # path in create_broadcast_app is only reached from within the FastAPI event loop.
38
+ q: asyncio.Queue = asyncio.Queue()
39
+ with self._lock:
40
+ self._queues.append(q)
41
+ return q
42
+
43
+ def unsubscribe(self, q: asyncio.Queue) -> None:
44
+ with self._lock:
45
+ try:
46
+ self._queues.remove(q)
47
+ except ValueError:
48
+ pass
49
+
50
+ def publish(self, event: dict) -> None:
51
+ """Publish event to all subscriber queues (non-blocking)."""
52
+ event_type = event.get("event_type", "")
53
+ if not _is_coordination_event(event_type):
54
+ return
55
+ with self._lock:
56
+ targets = list(self._queues)
57
+ for q in targets:
58
+ try:
59
+ q.put_nowait(event)
60
+ except asyncio.QueueFull:
61
+ pass
62
+
63
+
64
+ def create_broadcast_app(broadcaster: EventBroadcaster):
65
+ """
66
+ Build a FastAPI app exposing GET /events/stream (SSE).
67
+ Returns None if FastAPI is not installed.
68
+ """
69
+ try:
70
+ from fastapi import FastAPI
71
+ from fastapi.responses import StreamingResponse
72
+ except ImportError:
73
+ return None
74
+
75
+ app = FastAPI(title="DevTorch Event Broadcaster", version="0.1.0")
76
+
77
+ @app.get("/events/stream")
78
+ async def event_stream():
79
+ q = broadcaster.subscribe()
80
+
81
+ async def generate():
82
+ try:
83
+ # Send keepalive immediately
84
+ yield "data: {\"type\": \"connected\"}\n\n"
85
+ while True:
86
+ try:
87
+ event = await asyncio.wait_for(q.get(), timeout=30.0)
88
+ yield f"data: {json.dumps(event)}\n\n"
89
+ except asyncio.TimeoutError:
90
+ yield ": keepalive\n\n"
91
+ finally:
92
+ broadcaster.unsubscribe(q)
93
+
94
+ return StreamingResponse(generate(), media_type="text/event-stream")
95
+
96
+ @app.get("/events/health")
97
+ def health():
98
+ return {"status": "ok", "subscribers": len(broadcaster._queues)}
99
+
100
+ return app
@@ -0,0 +1,71 @@
1
+ """
2
+ File watcher that monitors .GCC/events.log.jsonl for new lines
3
+ and publishes coordination events to an EventBroadcaster.
4
+
5
+ Uses watchdog if available; falls back to polling.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import threading
11
+ import time
12
+ from pathlib import Path
13
+ from typing import Optional
14
+
15
+ from .broadcaster import EventBroadcaster
16
+
17
+
18
+ class GCCWatcher:
19
+ """
20
+ Watches .GCC/events.log.jsonl for new lines and publishes
21
+ coordination-relevant events to the broadcaster.
22
+ """
23
+
24
+ def __init__(self, gcc_dir: Path, broadcaster: EventBroadcaster,
25
+ poll_interval: float = 0.5) -> None:
26
+ self._log_path = gcc_dir / "events.log.jsonl"
27
+ self._broadcaster = broadcaster
28
+ self._poll_interval = poll_interval
29
+ self._offset: int = 0
30
+ self._thread: Optional[threading.Thread] = None
31
+ self._stop_event = threading.Event()
32
+
33
+ def _read_new_lines(self) -> None:
34
+ """Read any new lines appended since last check and publish them."""
35
+ if not self._log_path.exists():
36
+ return
37
+ try:
38
+ with self._log_path.open("r", encoding="utf-8") as f:
39
+ f.seek(self._offset)
40
+ for line in f:
41
+ line = line.strip()
42
+ if not line:
43
+ continue
44
+ try:
45
+ event = json.loads(line)
46
+ self._broadcaster.publish(event)
47
+ except json.JSONDecodeError:
48
+ pass
49
+ self._offset = f.tell()
50
+ except OSError:
51
+ pass
52
+
53
+ def _poll_loop(self) -> None:
54
+ while not self._stop_event.is_set():
55
+ self._read_new_lines()
56
+ self._stop_event.wait(self._poll_interval)
57
+
58
+ def start(self) -> None:
59
+ """Start the watcher background thread."""
60
+ if self._log_path.exists():
61
+ self._offset = self._log_path.stat().st_size
62
+ self._stop_event.clear()
63
+ self._thread = threading.Thread(target=self._poll_loop, daemon=True)
64
+ self._thread.start()
65
+
66
+ def stop(self) -> None:
67
+ """Stop the watcher background thread."""
68
+ self._stop_event.set()
69
+ if self._thread is not None:
70
+ self._thread.join(timeout=2.0)
71
+ self._thread = None