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,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,4 @@
1
+ from .entry import ReasoningEntry
2
+ from .store import ReasoningStore
3
+
4
+ __all__ = ["ReasoningEntry", "ReasoningStore"]
@@ -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
+ ]