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.
- devtorch_core/__init__.py +158 -0
- devtorch_core/aggphi_textual.py +275 -0
- devtorch_core/alerts/__init__.py +23 -0
- devtorch_core/alerts/base.py +46 -0
- devtorch_core/alerts/config.py +60 -0
- devtorch_core/alerts/dispatcher.py +110 -0
- devtorch_core/alerts/jira.py +96 -0
- devtorch_core/alerts/linear.py +72 -0
- devtorch_core/alerts/pagerduty.py +66 -0
- devtorch_core/alerts/slack.py +81 -0
- devtorch_core/alerts/teams.py +70 -0
- devtorch_core/audit/__init__.py +43 -0
- devtorch_core/audit/exporter.py +297 -0
- devtorch_core/audit/privacy.py +101 -0
- devtorch_core/audit/scrubber.py +149 -0
- devtorch_core/audit/service.py +67 -0
- devtorch_core/audit/signing.py +127 -0
- devtorch_core/broadcast/__init__.py +4 -0
- devtorch_core/broadcast/broadcaster.py +100 -0
- devtorch_core/broadcast/watcher.py +71 -0
- devtorch_core/capability.py +639 -0
- devtorch_core/cloud/__init__.py +1 -0
- devtorch_core/cloud/client_config.py +472 -0
- devtorch_core/cloud/client_configs/.claude-opencode-fallback.json +8 -0
- devtorch_core/cloud/client_configs/.claude-stdio.json +13 -0
- devtorch_core/cloud/client_configs/.cursor-mcp.json +13 -0
- devtorch_core/cloud/client_configs/.opencode-bridge.json +13 -0
- devtorch_core/cloud/client_configs/.opencode.json +15 -0
- devtorch_core/cloud/client_configs/.vscode-mcp.json +13 -0
- devtorch_core/cloud/devtorch-mcp-bridge.js +357 -0
- devtorch_core/cloud/mcp_client.py +229 -0
- devtorch_core/cloud/setup.py +144 -0
- devtorch_core/cloud/sync.py +143 -0
- devtorch_core/cloud/sync_bundle.py +603 -0
- devtorch_core/cloud/sync_conflicts.py +159 -0
- devtorch_core/cloud/sync_state.py +159 -0
- devtorch_core/cloud/team_sync.py +283 -0
- devtorch_core/codex/__init__.py +9 -0
- devtorch_core/codex/__main__.py +97 -0
- devtorch_core/codex/capture.py +208 -0
- devtorch_core/codex/proxy.py +412 -0
- devtorch_core/concept_catalog.py +209 -0
- devtorch_core/consolidation/__init__.py +3 -0
- devtorch_core/consolidation/synthesizer.py +87 -0
- devtorch_core/consolidation/workflow.py +175 -0
- devtorch_core/daemon/__init__.py +27 -0
- devtorch_core/daemon/supervisor.py +293 -0
- devtorch_core/daemon/watcher.py +244 -0
- devtorch_core/dashboard_api.py +2012 -0
- devtorch_core/deltaf.py +97 -0
- devtorch_core/disclosure.py +50 -0
- devtorch_core/divergence/__init__.py +3 -0
- devtorch_core/divergence/detector.py +166 -0
- devtorch_core/gateway/__init__.py +32 -0
- devtorch_core/gateway/key_manager.py +124 -0
- devtorch_core/gateway/metrics_webhook.py +252 -0
- devtorch_core/gateway/policy.py +262 -0
- devtorch_core/gateway/server.py +727 -0
- devtorch_core/gateway/sso.py +233 -0
- devtorch_core/gcc.py +1246 -0
- devtorch_core/github/__init__.py +35 -0
- devtorch_core/github/app.py +240 -0
- devtorch_core/github/comment_builder.py +113 -0
- devtorch_core/github/pat.py +76 -0
- devtorch_core/github/pr_parser.py +82 -0
- devtorch_core/github/pr_reporter.py +555 -0
- devtorch_core/gitlab/__init__.py +177 -0
- devtorch_core/hitl/__init__.py +4 -0
- devtorch_core/hitl/channels.py +129 -0
- devtorch_core/hitl/orchestrator.py +95 -0
- devtorch_core/hooks/__init__.py +17 -0
- devtorch_core/hooks/claude_code.py +228 -0
- devtorch_core/hooks/git_capture.py +341 -0
- devtorch_core/hooks/git_commit.py +182 -0
- devtorch_core/hooks/installer.py +733 -0
- devtorch_core/hooks/pre_commit.py +157 -0
- devtorch_core/hooks/runner.py +344 -0
- devtorch_core/identity/__init__.py +4 -0
- devtorch_core/identity/agent.py +86 -0
- devtorch_core/identity/providers.py +85 -0
- devtorch_core/invariants.py +182 -0
- devtorch_core/mcp/__init__.py +10 -0
- devtorch_core/mcp/auth.py +177 -0
- devtorch_core/mcp/server.py +1049 -0
- devtorch_core/metrics/__init__.py +35 -0
- devtorch_core/metrics/aggregate.py +215 -0
- devtorch_core/metrics/calibrate.py +198 -0
- devtorch_core/metrics/calibration.py +125 -0
- devtorch_core/metrics/credibility.py +288 -0
- devtorch_core/metrics/delivery_time.py +70 -0
- devtorch_core/metrics/dhs.py +126 -0
- devtorch_core/metrics/mcs.py +96 -0
- devtorch_core/metrics/roi.py +88 -0
- devtorch_core/metrics/session_writer.py +81 -0
- devtorch_core/metrics/shadow_ai.py +117 -0
- devtorch_core/metrics/sprint_writer.py +243 -0
- devtorch_core/observability/__init__.py +78 -0
- devtorch_core/observability/datadog.py +157 -0
- devtorch_core/observability/formatter.py +119 -0
- devtorch_core/observability/report.py +264 -0
- devtorch_core/observability/servicenow.py +147 -0
- devtorch_core/observability/splunk.py +218 -0
- devtorch_core/observability/webhook.py +227 -0
- devtorch_core/parser/__init__.py +30 -0
- devtorch_core/parser/blocks.py +216 -0
- devtorch_core/parser/inference.py +159 -0
- devtorch_core/parser/thinking.py +112 -0
- devtorch_core/projects.py +169 -0
- devtorch_core/prompt_artifact.py +76 -0
- devtorch_core/proxy/__init__.py +9 -0
- devtorch_core/proxy/routes/__init__.py +1 -0
- devtorch_core/proxy/routes/anthropic.py +264 -0
- devtorch_core/proxy/routes/azure_openai.py +336 -0
- devtorch_core/proxy/routes/gemini.py +331 -0
- devtorch_core/proxy/routes/groq.py +284 -0
- devtorch_core/proxy/routes/ollama.py +279 -0
- devtorch_core/proxy/routes/openai.py +287 -0
- devtorch_core/proxy/server.py +356 -0
- devtorch_core/query/__init__.py +15 -0
- devtorch_core/query/grep.py +181 -0
- devtorch_core/query/hybrid.py +86 -0
- devtorch_core/query/semantic.py +157 -0
- devtorch_core/rdp.py +105 -0
- devtorch_core/reasoning/__init__.py +4 -0
- devtorch_core/reasoning/entry.py +31 -0
- devtorch_core/reasoning/store.py +122 -0
- devtorch_core/reasoning_plus/__init__.py +70 -0
- devtorch_core/reasoning_plus/augmenter.py +326 -0
- devtorch_core/reasoning_plus/capture.py +51 -0
- devtorch_core/reasoning_plus/config.py +256 -0
- devtorch_core/reasoning_plus/context.py +262 -0
- devtorch_core/reasoning_plus/learning/__init__.py +72 -0
- devtorch_core/reasoning_plus/learning/analytics.py +141 -0
- devtorch_core/reasoning_plus/learning/api.py +313 -0
- devtorch_core/reasoning_plus/learning/chain.py +285 -0
- devtorch_core/reasoning_plus/learning/composer.py +74 -0
- devtorch_core/reasoning_plus/learning/cross_project.py +234 -0
- devtorch_core/reasoning_plus/learning/embeddings.py +209 -0
- devtorch_core/reasoning_plus/learning/extractor.py +207 -0
- devtorch_core/reasoning_plus/learning/models.py +116 -0
- devtorch_core/reasoning_plus/learning/provenance.py +126 -0
- devtorch_core/reasoning_plus/learning/recorder.py +81 -0
- devtorch_core/reasoning_plus/learning/relevance.py +122 -0
- devtorch_core/reasoning_plus/learning/state.py +86 -0
- devtorch_core/reasoning_plus/learning/store.py +160 -0
- devtorch_core/reasoning_plus/learning/theta_learning_bridge.py +94 -0
- devtorch_core/reasoning_plus/prompt.py +90 -0
- devtorch_core/rep.py +134 -0
- devtorch_core/rep_network/__init__.py +25 -0
- devtorch_core/rep_network/merge.py +70 -0
- devtorch_core/rep_network/node.py +137 -0
- devtorch_core/rep_network/server.py +140 -0
- devtorch_core/rep_network/sync.py +207 -0
- devtorch_core/sensitivity.py +182 -0
- devtorch_core/serve.py +258 -0
- devtorch_core/session/__init__.py +39 -0
- devtorch_core/session/disagreement.py +188 -0
- devtorch_core/session/models.py +114 -0
- devtorch_core/session/orchestrator.py +182 -0
- devtorch_core/session/planner.py +169 -0
- devtorch_core/session/simulator.py +132 -0
- devtorch_core/signing.py +290 -0
- devtorch_core/sis.py +197 -0
- devtorch_core/storage.py +308 -0
- devtorch_core/templates/__init__.py +6 -0
- devtorch_core/templates/engine.py +122 -0
- devtorch_core/templates/go.py +18 -0
- devtorch_core/templates/infra.py +19 -0
- devtorch_core/templates/library/__init__.py +18 -0
- devtorch_core/templates/library/api_design.md +27 -0
- devtorch_core/templates/library/bug_fix.md +27 -0
- devtorch_core/templates/library/decision_record.md +27 -0
- devtorch_core/templates/library/engine.py +228 -0
- devtorch_core/templates/library/security_review.md +30 -0
- devtorch_core/templates/python.py +19 -0
- devtorch_core/templates/react.py +18 -0
- devtorch_core/templates/typescript.py +18 -0
- devtorch_core/theta.py +221 -0
- devtorch_core/theta_synthesis.py +268 -0
- devtorch_core/topics.py +320 -0
- devtorch_core/variance.py +219 -0
- devtorch_core/wrapper/__init__.py +52 -0
- devtorch_core/wrapper/anthropic.py +487 -0
- devtorch_core/wrapper/base.py +562 -0
- devtorch_core/wrapper/bedrock.py +342 -0
- devtorch_core/wrapper/gemini.py +422 -0
- devtorch_core/wrapper/ollama.py +527 -0
- devtorch_core/wrapper/openai.py +461 -0
- devtorch_core-3.0.1.dist-info/METADATA +867 -0
- devtorch_core-3.0.1.dist-info/RECORD +193 -0
- devtorch_core-3.0.1.dist-info/WHEEL +5 -0
- devtorch_core-3.0.1.dist-info/entry_points.txt +2 -0
- devtorch_core-3.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Optional semantic search over .GCC/ events and concepts.
|
|
2
|
+
|
|
3
|
+
The import of `sentence-transformers` is delayed and optional. If the package
|
|
4
|
+
is not installed, the backend falls back to keyword search so that `devtorch ask`
|
|
5
|
+
still works without extra dependencies.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import math
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Iterable
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=False)
|
|
18
|
+
class SemanticResult:
|
|
19
|
+
path: Path
|
|
20
|
+
line: int
|
|
21
|
+
snippet: str
|
|
22
|
+
score: float
|
|
23
|
+
|
|
24
|
+
def to_dict(self) -> dict:
|
|
25
|
+
return {
|
|
26
|
+
"path": str(self.path),
|
|
27
|
+
"line": self.line,
|
|
28
|
+
"snippet": self.snippet,
|
|
29
|
+
"score": self.score,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SemanticBackend:
|
|
34
|
+
"""Encapsulates the optional embedding model."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, model_name: str | None = None):
|
|
37
|
+
self.model_name = model_name
|
|
38
|
+
self._model = None
|
|
39
|
+
self._tokenizer = None
|
|
40
|
+
|
|
41
|
+
def available(self) -> bool:
|
|
42
|
+
try:
|
|
43
|
+
import sentence_transformers # type: ignore
|
|
44
|
+
except Exception:
|
|
45
|
+
return False
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
def _load(self):
|
|
49
|
+
if self._model is not None:
|
|
50
|
+
return self._model
|
|
51
|
+
from sentence_transformers import SentenceTransformer # type: ignore
|
|
52
|
+
name = self.model_name or "all-MiniLM-L6-v2"
|
|
53
|
+
self._model = SentenceTransformer(name)
|
|
54
|
+
return self._model
|
|
55
|
+
|
|
56
|
+
def encode(self, texts: list[str]) -> list[list[float]]:
|
|
57
|
+
if not texts:
|
|
58
|
+
return []
|
|
59
|
+
model = self._load()
|
|
60
|
+
embeddings = model.encode(texts, convert_to_numpy=True, show_progress_bar=False)
|
|
61
|
+
return embeddings.tolist()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _cosine(a: list[float], b: list[float]) -> float:
|
|
65
|
+
dot = sum(x * y for x, y in zip(a, b))
|
|
66
|
+
norm_a = math.sqrt(sum(x * x for x in a))
|
|
67
|
+
norm_b = math.sqrt(sum(x * x for x in b))
|
|
68
|
+
if norm_a == 0 or norm_b == 0:
|
|
69
|
+
return 0.0
|
|
70
|
+
return max(0.0, min(1.0, dot / (norm_a * norm_b)))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _keyword_score(query: str, text: str) -> float:
|
|
74
|
+
"""Fallback keyword overlap used when no embedding model is available."""
|
|
75
|
+
query_terms = set(query.lower().split())
|
|
76
|
+
text_lower = text.lower()
|
|
77
|
+
if not query_terms:
|
|
78
|
+
return 0.0
|
|
79
|
+
matches = sum(1 for term in query_terms if term in text_lower)
|
|
80
|
+
return matches / len(query_terms)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _collect_documents(gcc_dir: Path) -> list[tuple[Path, int, str]]:
|
|
84
|
+
"""Collect (path, line, text) documents from .GCC/ events and concepts."""
|
|
85
|
+
docs: list[tuple[Path, int, str]] = []
|
|
86
|
+
events_path = gcc_dir / "events.log.jsonl"
|
|
87
|
+
if events_path.exists():
|
|
88
|
+
try:
|
|
89
|
+
for line_no, raw in enumerate(events_path.read_text(encoding="utf-8", errors="replace").splitlines(), start=1):
|
|
90
|
+
try:
|
|
91
|
+
payload = json.loads(raw)
|
|
92
|
+
except Exception:
|
|
93
|
+
continue
|
|
94
|
+
text = json.dumps(payload, ensure_ascii=False, separators=(", ", ": "))
|
|
95
|
+
docs.append((events_path, line_no, text))
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
for subdir in ("concepts", "sensitivities"):
|
|
100
|
+
sub = gcc_dir / subdir
|
|
101
|
+
if not sub.exists():
|
|
102
|
+
continue
|
|
103
|
+
for path in sub.rglob("*"):
|
|
104
|
+
if not path.is_file() or path.suffix not in (".md", ".json", ".txt"):
|
|
105
|
+
continue
|
|
106
|
+
try:
|
|
107
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
108
|
+
except Exception:
|
|
109
|
+
continue
|
|
110
|
+
for line_no, line in enumerate(text.splitlines(), start=1):
|
|
111
|
+
if line.strip():
|
|
112
|
+
docs.append((path, line_no, line.strip()))
|
|
113
|
+
return docs
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def semantic_search(
|
|
117
|
+
query: str,
|
|
118
|
+
project_root: Path,
|
|
119
|
+
top_n: int = 5,
|
|
120
|
+
backend: SemanticBackend | None = None,
|
|
121
|
+
) -> list[SemanticResult]:
|
|
122
|
+
"""Run semantic search over .GCC/ events and concepts.
|
|
123
|
+
|
|
124
|
+
If `sentence-transformers` is unavailable, fall back to keyword overlap.
|
|
125
|
+
"""
|
|
126
|
+
gcc_dir = project_root / ".GCC"
|
|
127
|
+
if not gcc_dir.exists():
|
|
128
|
+
return []
|
|
129
|
+
|
|
130
|
+
docs = _collect_documents(gcc_dir)
|
|
131
|
+
if not docs:
|
|
132
|
+
return []
|
|
133
|
+
|
|
134
|
+
backend = backend or SemanticBackend()
|
|
135
|
+
|
|
136
|
+
if not backend.available():
|
|
137
|
+
# Keyword fallback
|
|
138
|
+
scored: list[tuple[float, SemanticResult]] = []
|
|
139
|
+
for path, line, text in docs:
|
|
140
|
+
score = _keyword_score(query, text)
|
|
141
|
+
if score > 0:
|
|
142
|
+
scored.append((score, SemanticResult(path, line, text[:240], score)))
|
|
143
|
+
scored.sort(key=lambda t: t[0], reverse=True)
|
|
144
|
+
return [r for _, r in scored[:top_n]]
|
|
145
|
+
|
|
146
|
+
query_embedding = backend.encode([query])[0]
|
|
147
|
+
doc_texts = [text for _, _, text in docs]
|
|
148
|
+
doc_embeddings = backend.encode(doc_texts)
|
|
149
|
+
|
|
150
|
+
results: list[SemanticResult] = []
|
|
151
|
+
for (path, line, text), embedding in zip(docs, doc_embeddings):
|
|
152
|
+
score = _cosine(query_embedding, embedding)
|
|
153
|
+
if score > 0.0:
|
|
154
|
+
results.append(SemanticResult(path, line, text[:240], score))
|
|
155
|
+
|
|
156
|
+
results.sort(key=lambda r: r.score, reverse=True)
|
|
157
|
+
return results[:top_n]
|
devtorch_core/rdp.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sprint 5 – A4: RDP (Rényi Differential Privacy) accountant.
|
|
3
|
+
|
|
4
|
+
Per-round spend checks; PRIVACY_BUDGET_EXHAUSTED when budget exceeded; read-only mode.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional, Tuple
|
|
13
|
+
|
|
14
|
+
RDP_DIR_NAME = "rdp"
|
|
15
|
+
RDP_STATE_NAME = "rdp_state.json"
|
|
16
|
+
PRIVACY_BUDGET_EXHAUSTED_EVENT = "PRIVACY_BUDGET_EXHAUSTED"
|
|
17
|
+
|
|
18
|
+
# Default epsilon budget (total)
|
|
19
|
+
DEFAULT_EPSILON_BUDGET = 1.0
|
|
20
|
+
DEFAULT_ALPHA = 1.5 # Rényi order for composition
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class RDPState:
|
|
25
|
+
"""RDP accountant state: remaining budget and spend history."""
|
|
26
|
+
epsilon_budget: float
|
|
27
|
+
epsilon_spent: float
|
|
28
|
+
alpha: float
|
|
29
|
+
round_ids: list # rounds that consumed budget
|
|
30
|
+
read_only: bool # True after PRIVACY_BUDGET_EXHAUSTED
|
|
31
|
+
|
|
32
|
+
def remaining(self) -> float:
|
|
33
|
+
return max(0.0, self.epsilon_budget - self.epsilon_spent)
|
|
34
|
+
|
|
35
|
+
def to_dict(self) -> dict:
|
|
36
|
+
return {
|
|
37
|
+
"epsilon_budget": self.epsilon_budget,
|
|
38
|
+
"epsilon_spent": self.epsilon_spent,
|
|
39
|
+
"alpha": self.alpha,
|
|
40
|
+
"round_ids": list(self.round_ids),
|
|
41
|
+
"read_only": self.read_only,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_dict(cls, d: dict) -> "RDPState":
|
|
46
|
+
return cls(
|
|
47
|
+
epsilon_budget=float(d.get("epsilon_budget", DEFAULT_EPSILON_BUDGET)),
|
|
48
|
+
epsilon_spent=float(d.get("epsilon_spent", 0)),
|
|
49
|
+
alpha=float(d.get("alpha", DEFAULT_ALPHA)),
|
|
50
|
+
round_ids=list(d.get("round_ids", [])),
|
|
51
|
+
read_only=bool(d.get("read_only", False)),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def rdp_compose(epsilon_per_round: float, alpha: float, k_rounds: int) -> float:
|
|
56
|
+
"""Simple RDP composition: k rounds of (epsilon_per_round, alpha) -> total epsilon (approx)."""
|
|
57
|
+
# RDP composition: (alpha, eps) * k -> (alpha, k*eps) for same alpha
|
|
58
|
+
return k_rounds * epsilon_per_round
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class RDPAccountant:
|
|
62
|
+
"""Persist RDP state and enforce per-round spend."""
|
|
63
|
+
def __init__(self, gcc_dir: Path) -> None:
|
|
64
|
+
self.rdp_dir = gcc_dir / RDP_DIR_NAME
|
|
65
|
+
self.state_path = self.rdp_dir / RDP_STATE_NAME
|
|
66
|
+
|
|
67
|
+
def ensure_dir(self) -> None:
|
|
68
|
+
self.rdp_dir.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
|
|
70
|
+
def load_state(self) -> RDPState:
|
|
71
|
+
if not self.state_path.exists():
|
|
72
|
+
return RDPState(
|
|
73
|
+
epsilon_budget=DEFAULT_EPSILON_BUDGET,
|
|
74
|
+
epsilon_spent=0.0,
|
|
75
|
+
alpha=DEFAULT_ALPHA,
|
|
76
|
+
round_ids=[],
|
|
77
|
+
read_only=False,
|
|
78
|
+
)
|
|
79
|
+
return RDPState.from_dict(json.loads(self.state_path.read_text(encoding="utf-8")))
|
|
80
|
+
|
|
81
|
+
def save_state(self, state: RDPState) -> None:
|
|
82
|
+
self.ensure_dir()
|
|
83
|
+
self.state_path.write_text(json.dumps(state.to_dict(), indent=2) + "\n", encoding="utf-8")
|
|
84
|
+
|
|
85
|
+
def spend(self, round_id: int, epsilon_cost: float) -> Tuple[bool, str]:
|
|
86
|
+
"""
|
|
87
|
+
Attempt to spend epsilon_cost for round_id. Returns (allowed, event_or_empty).
|
|
88
|
+
If allowed: state updated, return (True, "").
|
|
89
|
+
If budget exceeded: state set read_only, return (False, PRIVACY_BUDGET_EXHAUSTED_EVENT).
|
|
90
|
+
"""
|
|
91
|
+
state = self.load_state()
|
|
92
|
+
if state.read_only:
|
|
93
|
+
return False, PRIVACY_BUDGET_EXHAUSTED_EVENT
|
|
94
|
+
if state.remaining() < epsilon_cost:
|
|
95
|
+
state.read_only = True
|
|
96
|
+
state.epsilon_spent = state.epsilon_budget
|
|
97
|
+
self.save_state(state)
|
|
98
|
+
return False, PRIVACY_BUDGET_EXHAUSTED_EVENT
|
|
99
|
+
state.epsilon_spent += epsilon_cost
|
|
100
|
+
state.round_ids.append(round_id)
|
|
101
|
+
self.save_state(state)
|
|
102
|
+
return True, ""
|
|
103
|
+
|
|
104
|
+
def is_read_only(self) -> bool:
|
|
105
|
+
return self.load_state().read_only
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclasses.dataclass
|
|
8
|
+
class ReasoningEntry:
|
|
9
|
+
"""
|
|
10
|
+
A single cross-agent-queryable reasoning record.
|
|
11
|
+
|
|
12
|
+
Combines a SensitivityEvent (the why) with its associated commit message
|
|
13
|
+
(the decision), token-budgeted and structured for Agent B to consume.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
entry_id: str
|
|
17
|
+
agent_id: str
|
|
18
|
+
concept: str
|
|
19
|
+
reasoning_text: str # counterfactuals / why text from SensitivityEvent
|
|
20
|
+
decision_text: str # commit message from nearest preceding commit
|
|
21
|
+
confidence: float
|
|
22
|
+
disclosure_level: str
|
|
23
|
+
session_id: Optional[str]
|
|
24
|
+
branch: str
|
|
25
|
+
timestamp: str
|
|
26
|
+
source_type: str # "sensitivity" | "commit"
|
|
27
|
+
|
|
28
|
+
def approx_tokens(self) -> int:
|
|
29
|
+
"""Rough token estimate: 4 chars ≈ 1 token."""
|
|
30
|
+
text = self.reasoning_text + " " + self.decision_text
|
|
31
|
+
return max(1, len(text) // 4)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
from .entry import ReasoningEntry
|
|
8
|
+
|
|
9
|
+
_SENSITIVITIES_FILE = "sensitivities/events.jsonl"
|
|
10
|
+
_COMMITS_DIR = "commits"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ReasoningStore:
|
|
14
|
+
"""
|
|
15
|
+
Query Agent reasoning from the .GCC/ event store.
|
|
16
|
+
|
|
17
|
+
Combines SensitivityEvents (the why) with the nearest commit on the same
|
|
18
|
+
branch (the decision), filtered by agent_id and concept, token-budgeted.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, repo) -> None:
|
|
22
|
+
self._repo = repo
|
|
23
|
+
self._gcc_dir = repo.gcc_dir
|
|
24
|
+
|
|
25
|
+
def query(
|
|
26
|
+
self,
|
|
27
|
+
concept: str,
|
|
28
|
+
agent_id: Optional[str] = None,
|
|
29
|
+
scope: str = "branch",
|
|
30
|
+
k_tokens: int = 4000,
|
|
31
|
+
) -> List[ReasoningEntry]:
|
|
32
|
+
"""
|
|
33
|
+
Return reasoning entries for the given concept, optionally filtered by agent_id.
|
|
34
|
+
|
|
35
|
+
Falls back to source_node as agent_id for events without an explicit agent_id
|
|
36
|
+
(backward compatibility with pre-S18 events).
|
|
37
|
+
"""
|
|
38
|
+
events = self._load_sensitivity_events(concept)
|
|
39
|
+
commits = self._load_commits_index()
|
|
40
|
+
|
|
41
|
+
entries: List[ReasoningEntry] = []
|
|
42
|
+
for ev in events:
|
|
43
|
+
effective_agent = ev.get("agent_id") or ev.get("source_node", "unknown")
|
|
44
|
+
if agent_id is not None and effective_agent != agent_id:
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
decision_text = self._nearest_commit_message(
|
|
48
|
+
ev.get("created_at", ""), ev.get("branch", "main"), commits
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
entry = ReasoningEntry(
|
|
52
|
+
entry_id=ev.get("event_id", ""),
|
|
53
|
+
agent_id=effective_agent,
|
|
54
|
+
concept=concept,
|
|
55
|
+
reasoning_text=ev.get("counterfactuals", "") or ev.get("message", ""),
|
|
56
|
+
decision_text=decision_text,
|
|
57
|
+
confidence=float(ev.get("confidence", 0.0)),
|
|
58
|
+
disclosure_level=ev.get("disclosure_level", "PROTECTED"),
|
|
59
|
+
session_id=ev.get("session_id"),
|
|
60
|
+
branch=ev.get("branch", "main"),
|
|
61
|
+
timestamp=ev.get("created_at", ""),
|
|
62
|
+
source_type="sensitivity",
|
|
63
|
+
)
|
|
64
|
+
entries.append(entry)
|
|
65
|
+
|
|
66
|
+
# Sort by timestamp descending (most recent first), then apply token budget
|
|
67
|
+
entries.sort(key=lambda e: e.timestamp, reverse=True)
|
|
68
|
+
return self._apply_token_budget(entries, k_tokens)
|
|
69
|
+
|
|
70
|
+
def _load_sensitivity_events(self, concept: str) -> List[dict]:
|
|
71
|
+
path = self._gcc_dir / _SENSITIVITIES_FILE
|
|
72
|
+
if not path.exists():
|
|
73
|
+
return []
|
|
74
|
+
results = []
|
|
75
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
76
|
+
line = line.strip()
|
|
77
|
+
if not line:
|
|
78
|
+
continue
|
|
79
|
+
try:
|
|
80
|
+
d = json.loads(line)
|
|
81
|
+
if d.get("target_concept") == concept:
|
|
82
|
+
results.append(d)
|
|
83
|
+
except json.JSONDecodeError:
|
|
84
|
+
continue
|
|
85
|
+
return results
|
|
86
|
+
|
|
87
|
+
def _load_commits_index(self) -> List[dict]:
|
|
88
|
+
commits_dir = self._gcc_dir / _COMMITS_DIR
|
|
89
|
+
if not commits_dir.exists():
|
|
90
|
+
return []
|
|
91
|
+
commits = []
|
|
92
|
+
for f in sorted(commits_dir.glob("*.json")):
|
|
93
|
+
try:
|
|
94
|
+
commits.append(json.loads(f.read_text(encoding="utf-8")))
|
|
95
|
+
except (json.JSONDecodeError, OSError):
|
|
96
|
+
continue
|
|
97
|
+
return sorted(commits, key=lambda c: c.get("timestamp", ""))
|
|
98
|
+
|
|
99
|
+
def _nearest_commit_message(
|
|
100
|
+
self, event_ts: str, branch: str, commits: List[dict]
|
|
101
|
+
) -> str:
|
|
102
|
+
"""Find the commit message from the latest commit at or before event_ts on branch."""
|
|
103
|
+
branch_commits = [c for c in commits if c.get("branch") == branch]
|
|
104
|
+
before = [c for c in branch_commits if c.get("timestamp", "") <= event_ts]
|
|
105
|
+
if before:
|
|
106
|
+
return before[-1].get("message", "")
|
|
107
|
+
# Fall back to nearest commit regardless of branch
|
|
108
|
+
if commits:
|
|
109
|
+
return commits[-1].get("message", "")
|
|
110
|
+
return ""
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def _apply_token_budget(entries: List[ReasoningEntry], k_tokens: int) -> List[ReasoningEntry]:
|
|
114
|
+
result = []
|
|
115
|
+
used = 0
|
|
116
|
+
for e in entries:
|
|
117
|
+
t = e.approx_tokens()
|
|
118
|
+
if used + t > k_tokens:
|
|
119
|
+
break
|
|
120
|
+
result.append(e)
|
|
121
|
+
used += t
|
|
122
|
+
return result
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
devtorch_core.reasoning_plus
|
|
3
|
+
============================
|
|
4
|
+
Reasoning Plus: smart workspace context + inline reasoning capture.
|
|
5
|
+
|
|
6
|
+
Public API:
|
|
7
|
+
ReasoningPlusAugmenter — orchestrates context, prompt, and capture
|
|
8
|
+
ReasoningPlusConfig — dataclass for settings
|
|
9
|
+
SettingsStore — read/write project and global settings
|
|
10
|
+
load_config — convenience loader
|
|
11
|
+
is_enabled — quick enablement check
|
|
12
|
+
gather_smart_context — extract keywords, search repo, read files
|
|
13
|
+
build_reasoning_plus_system_prompt — prompt builder
|
|
14
|
+
extract_inline_thinking, strip_inline_thinking, commit_reasoning
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from devtorch_core.reasoning_plus.augmenter import (
|
|
19
|
+
ReasoningPlusAugmenter,
|
|
20
|
+
is_enabled,
|
|
21
|
+
)
|
|
22
|
+
from devtorch_core.reasoning_plus.capture import (
|
|
23
|
+
commit_reasoning,
|
|
24
|
+
extract_inline_thinking,
|
|
25
|
+
strip_inline_thinking,
|
|
26
|
+
)
|
|
27
|
+
from devtorch_core.reasoning_plus.config import (
|
|
28
|
+
ReasoningPlusConfig,
|
|
29
|
+
SettingsStore,
|
|
30
|
+
load_config,
|
|
31
|
+
)
|
|
32
|
+
from devtorch_core.reasoning_plus.context import (
|
|
33
|
+
extract_keywords,
|
|
34
|
+
gather_smart_context,
|
|
35
|
+
read_files_at_commit,
|
|
36
|
+
search_repo,
|
|
37
|
+
)
|
|
38
|
+
from devtorch_core.reasoning_plus.learning import (
|
|
39
|
+
Learning,
|
|
40
|
+
PromptComposer,
|
|
41
|
+
ReasoningCall,
|
|
42
|
+
ReasoningPlusLearning,
|
|
43
|
+
)
|
|
44
|
+
from devtorch_core.reasoning_plus.prompt import (
|
|
45
|
+
DEFAULT_REASONING_DIRECTIVE,
|
|
46
|
+
PATCH_REASONING_DIRECTIVE,
|
|
47
|
+
build_reasoning_plus_system_prompt,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
"ReasoningPlusAugmenter",
|
|
52
|
+
"ReasoningPlusConfig",
|
|
53
|
+
"SettingsStore",
|
|
54
|
+
"load_config",
|
|
55
|
+
"is_enabled",
|
|
56
|
+
"extract_keywords",
|
|
57
|
+
"search_repo",
|
|
58
|
+
"read_files_at_commit",
|
|
59
|
+
"gather_smart_context",
|
|
60
|
+
"build_reasoning_plus_system_prompt",
|
|
61
|
+
"DEFAULT_REASONING_DIRECTIVE",
|
|
62
|
+
"PATCH_REASONING_DIRECTIVE",
|
|
63
|
+
"extract_inline_thinking",
|
|
64
|
+
"strip_inline_thinking",
|
|
65
|
+
"commit_reasoning",
|
|
66
|
+
"ReasoningPlusLearning",
|
|
67
|
+
"ReasoningCall",
|
|
68
|
+
"Learning",
|
|
69
|
+
"PromptComposer",
|
|
70
|
+
]
|