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,228 @@
|
|
|
1
|
+
"""Reasoning template library for DevTorch.
|
|
2
|
+
|
|
3
|
+
Provides built-in reasoning templates and a documented extension API for
|
|
4
|
+
project-level custom templates under ``.devtorch/templates/`` or ``templates/``.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"list_builtin_templates",
|
|
14
|
+
"load_builtin_template",
|
|
15
|
+
"discover_custom_templates",
|
|
16
|
+
"apply_template",
|
|
17
|
+
"render_template",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
_BUILTIN_DIR = Path(__file__).resolve().parent
|
|
21
|
+
_PLACEHOLDER_RE = re.compile(r"\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _parse_front_matter(text: str) -> tuple[dict[str, Any], str]:
|
|
25
|
+
"""Parse a minimal YAML-like front-matter block at the top of ``text``.
|
|
26
|
+
|
|
27
|
+
Returns a ``(metadata, body)`` tuple. Only the scalar keys required by
|
|
28
|
+
the library (``name``, ``description``, ``tags``) are parsed, and ``tags``
|
|
29
|
+
is normalised to a list of strings.
|
|
30
|
+
"""
|
|
31
|
+
lines = text.splitlines()
|
|
32
|
+
if not lines or lines[0].strip() != "---":
|
|
33
|
+
return {}, text
|
|
34
|
+
|
|
35
|
+
end = -1
|
|
36
|
+
for i in range(1, len(lines)):
|
|
37
|
+
if lines[i].strip() == "---":
|
|
38
|
+
end = i
|
|
39
|
+
break
|
|
40
|
+
|
|
41
|
+
if end == -1:
|
|
42
|
+
return {}, text
|
|
43
|
+
|
|
44
|
+
meta_text = "\n".join(lines[1:end])
|
|
45
|
+
body = "\n".join(lines[end + 1 :])
|
|
46
|
+
metadata: dict[str, Any] = {}
|
|
47
|
+
|
|
48
|
+
for line in meta_text.splitlines():
|
|
49
|
+
if ":" not in line:
|
|
50
|
+
continue
|
|
51
|
+
key, _, value = line.partition(":")
|
|
52
|
+
key = key.strip()
|
|
53
|
+
value = value.strip()
|
|
54
|
+
if not key:
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
# Strip matching quotes.
|
|
58
|
+
if len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"):
|
|
59
|
+
value = value[1:-1]
|
|
60
|
+
|
|
61
|
+
if key == "tags":
|
|
62
|
+
value = _parse_tags(value)
|
|
63
|
+
|
|
64
|
+
metadata[key] = value
|
|
65
|
+
|
|
66
|
+
return metadata, body
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _parse_tags(value: str) -> list[str]:
|
|
70
|
+
"""Parse ``tags`` as a YAML list ``[a, b]`` or a comma-separated string."""
|
|
71
|
+
value = value.strip()
|
|
72
|
+
if value.startswith("[") and value.endswith("]"):
|
|
73
|
+
inner = value[1:-1].strip()
|
|
74
|
+
if not inner:
|
|
75
|
+
return []
|
|
76
|
+
return [
|
|
77
|
+
item.strip().strip('"\'')
|
|
78
|
+
for item in inner.split(",")
|
|
79
|
+
if item.strip()
|
|
80
|
+
]
|
|
81
|
+
if value:
|
|
82
|
+
return [item.strip() for item in value.split(",") if item.strip()]
|
|
83
|
+
return []
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def list_builtin_templates() -> list[dict[str, Any]]:
|
|
87
|
+
"""Return metadata for every built-in reasoning template.
|
|
88
|
+
|
|
89
|
+
Each entry contains at least ``name``, ``description``, ``tags``, and
|
|
90
|
+
``filename``.
|
|
91
|
+
"""
|
|
92
|
+
templates: list[dict[str, Any]] = []
|
|
93
|
+
if not _BUILTIN_DIR.exists():
|
|
94
|
+
return templates
|
|
95
|
+
|
|
96
|
+
for path in sorted(_BUILTIN_DIR.glob("*.md")):
|
|
97
|
+
text = path.read_text(encoding="utf-8")
|
|
98
|
+
meta, _ = _parse_front_matter(text)
|
|
99
|
+
templates.append(
|
|
100
|
+
{
|
|
101
|
+
"name": meta.get("name", path.stem),
|
|
102
|
+
"description": meta.get("description", ""),
|
|
103
|
+
"tags": meta.get("tags", []),
|
|
104
|
+
"filename": path.name,
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return templates
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def load_builtin_template(name: str) -> str:
|
|
112
|
+
"""Return the raw Markdown text of a built-in template.
|
|
113
|
+
|
|
114
|
+
Raises:
|
|
115
|
+
ValueError: if the template is not built-in.
|
|
116
|
+
"""
|
|
117
|
+
path = _BUILTIN_DIR / f"{name}.md"
|
|
118
|
+
if not path.exists():
|
|
119
|
+
raise ValueError(f"Unknown built-in template: {name}")
|
|
120
|
+
return path.read_text(encoding="utf-8")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def discover_custom_templates(project_root: Path | str) -> list[dict[str, Any]]:
|
|
124
|
+
"""Discover custom templates under ``.devtorch/templates/`` and ``templates/``.
|
|
125
|
+
|
|
126
|
+
Templates are returned in discovery order: ``.devtorch/templates/`` first,
|
|
127
|
+
then project-level ``templates/``.
|
|
128
|
+
"""
|
|
129
|
+
root = Path(project_root).resolve()
|
|
130
|
+
origins = [root / ".devtorch" / "templates", root / "templates"]
|
|
131
|
+
templates: list[dict[str, Any]] = []
|
|
132
|
+
|
|
133
|
+
for origin_dir in origins:
|
|
134
|
+
if not origin_dir.is_dir():
|
|
135
|
+
continue
|
|
136
|
+
for path in sorted(origin_dir.glob("*.md")):
|
|
137
|
+
text = path.read_text(encoding="utf-8")
|
|
138
|
+
meta, _ = _parse_front_matter(text)
|
|
139
|
+
try:
|
|
140
|
+
origin = str(path.parent.relative_to(root))
|
|
141
|
+
except ValueError:
|
|
142
|
+
origin = str(path.parent)
|
|
143
|
+
templates.append(
|
|
144
|
+
{
|
|
145
|
+
"name": meta.get("name", path.stem),
|
|
146
|
+
"description": meta.get("description", ""),
|
|
147
|
+
"tags": meta.get("tags", []),
|
|
148
|
+
"path": str(path),
|
|
149
|
+
"origin": origin,
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return templates
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def render_template(template_text: str, variables: dict[str, Any]) -> str:
|
|
157
|
+
"""Replace ``{{variable}}`` placeholders with values from ``variables``.
|
|
158
|
+
|
|
159
|
+
Unknown placeholders are left untouched so callers can see what is still
|
|
160
|
+
required.
|
|
161
|
+
"""
|
|
162
|
+
string_vars = {str(k): str(v) for k, v in variables.items()}
|
|
163
|
+
|
|
164
|
+
def _replace(match: re.Match[str]) -> str:
|
|
165
|
+
key = match.group(1)
|
|
166
|
+
return string_vars.get(key, match.group(0))
|
|
167
|
+
|
|
168
|
+
return _PLACEHOLDER_RE.sub(_replace, template_text)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _resolve_template(name: str, project_root: Path | str | None = None) -> str:
|
|
172
|
+
"""Load template text from built-ins or, optionally, custom directories.
|
|
173
|
+
|
|
174
|
+
Custom templates take precedence over built-ins when a name collision
|
|
175
|
+
occurs, so projects can extend or override the default reasoning library.
|
|
176
|
+
"""
|
|
177
|
+
if project_root is not None:
|
|
178
|
+
for custom in discover_custom_templates(project_root):
|
|
179
|
+
if custom["name"] == name:
|
|
180
|
+
return Path(custom["path"]).read_text(encoding="utf-8")
|
|
181
|
+
|
|
182
|
+
builtin_path = _BUILTIN_DIR / f"{name}.md"
|
|
183
|
+
if builtin_path.exists():
|
|
184
|
+
return builtin_path.read_text(encoding="utf-8")
|
|
185
|
+
|
|
186
|
+
raise ValueError(f"Unknown template: {name}")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _default_output_path(name: str, project_root: Path | str | None = None) -> Path:
|
|
190
|
+
"""Default render target inside ``.GCC/templates/rendered/``."""
|
|
191
|
+
root = Path(project_root) if project_root else Path.cwd()
|
|
192
|
+
return root / ".GCC" / "templates" / "rendered" / f"{name}.md"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def apply_template(
|
|
196
|
+
name: str,
|
|
197
|
+
variables: dict[str, Any],
|
|
198
|
+
output_path: Path | str | None = None,
|
|
199
|
+
project_root: Path | str | None = None,
|
|
200
|
+
) -> Path:
|
|
201
|
+
"""Render a template (built-in or custom) and write it to ``output_path``.
|
|
202
|
+
|
|
203
|
+
If ``output_path`` is not provided, the rendered output is written to
|
|
204
|
+
``.GCC/templates/rendered/<name>.md`` relative to ``project_root`` (or the
|
|
205
|
+
current working directory if ``project_root`` is not given).
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
name: Template name (matches the filename stem, or the ``name`` field
|
|
209
|
+
in the front matter).
|
|
210
|
+
variables: Mapping of placeholder names to values.
|
|
211
|
+
output_path: Destination path for the rendered template.
|
|
212
|
+
project_root: Optional project root used to resolve custom templates
|
|
213
|
+
and default output paths.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
The absolute path where the rendered template was written.
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
ValueError: if the template cannot be found.
|
|
220
|
+
"""
|
|
221
|
+
template_text = _resolve_template(name, project_root)
|
|
222
|
+
rendered = render_template(template_text, variables)
|
|
223
|
+
|
|
224
|
+
out = Path(output_path) if output_path else _default_output_path(name, project_root)
|
|
225
|
+
out = out.resolve()
|
|
226
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
227
|
+
out.write_text(rendered, encoding="utf-8")
|
|
228
|
+
return out
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: security_review
|
|
3
|
+
description: Structured reasoning for a security review or threat assessment
|
|
4
|
+
tags: [reasoning, security, review]
|
|
5
|
+
---
|
|
6
|
+
# Security Review: {{title}}
|
|
7
|
+
|
|
8
|
+
## Scope
|
|
9
|
+
{{scope}}
|
|
10
|
+
|
|
11
|
+
## Threat model
|
|
12
|
+
{{threat_model}}
|
|
13
|
+
|
|
14
|
+
## Controls evaluated
|
|
15
|
+
{{controls}}
|
|
16
|
+
|
|
17
|
+
## Findings
|
|
18
|
+
{{findings}}
|
|
19
|
+
|
|
20
|
+
## Risk assessment
|
|
21
|
+
{{reasoning}}
|
|
22
|
+
|
|
23
|
+
## Remediation
|
|
24
|
+
{{remediation}}
|
|
25
|
+
|
|
26
|
+
## Verification
|
|
27
|
+
{{verification}}
|
|
28
|
+
|
|
29
|
+
## Disclosure level
|
|
30
|
+
{{disclosure_level}}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Built-in Python project template."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from devtorch_core.templates.engine import Template
|
|
5
|
+
|
|
6
|
+
TEMPLATE = Template(
|
|
7
|
+
name="python",
|
|
8
|
+
display_name="Python",
|
|
9
|
+
project_type="python",
|
|
10
|
+
concepts=[
|
|
11
|
+
("auth", "Authentication, authorization, and identity management."),
|
|
12
|
+
("schema", "Data models, validation schemas, and serialization."),
|
|
13
|
+
("api", "Public interfaces, endpoints, and service contracts."),
|
|
14
|
+
("data", "Persistence, queries, and data lifecycle."),
|
|
15
|
+
("cli", "Command-line interface and entry points."),
|
|
16
|
+
("tests", "Automated tests, fixtures, and coverage."),
|
|
17
|
+
],
|
|
18
|
+
governance_note="Python projects default to explicit schemas, tested APIs, and clear separation between CLI, business logic, and data layers.",
|
|
19
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Built-in React project template."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from devtorch_core.templates.engine import Template
|
|
5
|
+
|
|
6
|
+
TEMPLATE = Template(
|
|
7
|
+
name="react",
|
|
8
|
+
display_name="React",
|
|
9
|
+
project_type="react",
|
|
10
|
+
concepts=[
|
|
11
|
+
("auth", "Authentication, authorization, and session handling."),
|
|
12
|
+
("schema", "Prop types, data shapes, and API response schemas."),
|
|
13
|
+
("api", "Backend integration, hooks, and service clients."),
|
|
14
|
+
("ui", "Screens, layouts, and user interaction design."),
|
|
15
|
+
("components", "Reusable components, composition, and state ownership."),
|
|
16
|
+
],
|
|
17
|
+
governance_note="React projects default to component-driven UI, explicit data schemas, and isolated auth/session concerns separated from presentation logic.",
|
|
18
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Built-in TypeScript project template."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from devtorch_core.templates.engine import Template
|
|
5
|
+
|
|
6
|
+
TEMPLATE = Template(
|
|
7
|
+
name="typescript",
|
|
8
|
+
display_name="TypeScript",
|
|
9
|
+
project_type="typescript",
|
|
10
|
+
concepts=[
|
|
11
|
+
("auth", "Authentication, authorization, and identity management."),
|
|
12
|
+
("schema", "Type definitions, validation schemas, and runtime checks."),
|
|
13
|
+
("api", "HTTP, RPC, and library interfaces."),
|
|
14
|
+
("ui", "User-facing components and interaction patterns."),
|
|
15
|
+
("types", "Shared type definitions and generic contracts."),
|
|
16
|
+
],
|
|
17
|
+
governance_note="TypeScript projects default to strict type contracts, schema-first APIs, and shared type definitions across UI and service boundaries.",
|
|
18
|
+
)
|
devtorch_core/theta.py
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sprint 3 – Coordination vector Θ and pluggable Aggφ aggregation.
|
|
3
|
+
|
|
4
|
+
ThetaStore persists Θ (a concept → summary mapping) at `.GCC/theta.json`.
|
|
5
|
+
AggPhi is an abstract interface; RulesBasedAggPhi is the default implementation
|
|
6
|
+
that averages confidence with recency weighting and escalates disclosure levels.
|
|
7
|
+
|
|
8
|
+
Callers invoke `ThetaStore.ripple(events)` to merge a batch of sensitivity
|
|
9
|
+
event dicts into the stored Θ, using the configured AggPhi.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import abc
|
|
15
|
+
import datetime as _dt
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Dict, List, Optional
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
THETA_FILE = "theta.json"
|
|
23
|
+
|
|
24
|
+
# Ordered disclosure level ranks (higher index = more restrictive)
|
|
25
|
+
_LEVEL_RANK: Dict[str, int] = {"PUBLIC": 0, "PROTECTED": 1, "PRIVATE": 2}
|
|
26
|
+
_RANK_LEVEL: Dict[int, str] = {0: "PUBLIC", 1: "PROTECTED", 2: "PRIVATE"}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AggPhi(abc.ABC):
|
|
30
|
+
"""
|
|
31
|
+
Pluggable aggregation interface (Aggφ).
|
|
32
|
+
|
|
33
|
+
Implementations receive a list of raw sensitivity event dicts and return a
|
|
34
|
+
*partial theta delta*: a mapping of concept → aggregated entry containing
|
|
35
|
+
at minimum:
|
|
36
|
+
mean_confidence : float
|
|
37
|
+
disclosure_level : str ("PUBLIC" | "PROTECTED" | "PRIVATE")
|
|
38
|
+
event_count : int
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@abc.abstractmethod
|
|
42
|
+
def aggregate(self, events: List[dict]) -> Dict[str, dict]:
|
|
43
|
+
"""Aggregate *events* into a per-concept summary delta."""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RulesBasedAggPhi(AggPhi):
|
|
47
|
+
"""
|
|
48
|
+
Default rules-based Aggφ.
|
|
49
|
+
|
|
50
|
+
Aggregation rules:
|
|
51
|
+
1. Group events by `target_concept`.
|
|
52
|
+
2. Within each group, sort by `created_at` descending (newest first).
|
|
53
|
+
3. Apply recency weighting: the newest event has weight 2.0; all others 1.0.
|
|
54
|
+
4. Compute weighted mean confidence.
|
|
55
|
+
5. Escalate disclosure level to the maximum seen in the group.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def aggregate(self, events: List[dict]) -> Dict[str, dict]:
|
|
59
|
+
grouped: Dict[str, List[dict]] = {}
|
|
60
|
+
for ev in events:
|
|
61
|
+
concept = ev.get("target_concept", "unknown")
|
|
62
|
+
grouped.setdefault(concept, []).append(ev)
|
|
63
|
+
|
|
64
|
+
result: Dict[str, dict] = {}
|
|
65
|
+
for concept, group in grouped.items():
|
|
66
|
+
sorted_group = sorted(
|
|
67
|
+
group,
|
|
68
|
+
key=lambda e: e.get("created_at", ""),
|
|
69
|
+
reverse=True,
|
|
70
|
+
)
|
|
71
|
+
total_weight = 0.0
|
|
72
|
+
weighted_conf = 0.0
|
|
73
|
+
max_rank = 0
|
|
74
|
+
|
|
75
|
+
for i, ev in enumerate(sorted_group):
|
|
76
|
+
weight = 2.0 if i == 0 else 1.0
|
|
77
|
+
conf = float(ev.get("confidence", 0.5))
|
|
78
|
+
weighted_conf += weight * conf
|
|
79
|
+
total_weight += weight
|
|
80
|
+
|
|
81
|
+
rank = _LEVEL_RANK.get(ev.get("disclosure_level", "PUBLIC"), 0)
|
|
82
|
+
if rank > max_rank:
|
|
83
|
+
max_rank = rank
|
|
84
|
+
|
|
85
|
+
mean_conf = weighted_conf / total_weight if total_weight else 0.0
|
|
86
|
+
result[concept] = {
|
|
87
|
+
"mean_confidence": round(mean_conf, 6),
|
|
88
|
+
"disclosure_level": _RANK_LEVEL[max_rank],
|
|
89
|
+
"event_count": len(group),
|
|
90
|
+
}
|
|
91
|
+
return result
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class ThetaStore:
|
|
95
|
+
"""
|
|
96
|
+
Persistence layer for the coordination vector Θ.
|
|
97
|
+
|
|
98
|
+
The on-disk format is a single JSON object at `.GCC/theta.json`:
|
|
99
|
+
{
|
|
100
|
+
"version": 1,
|
|
101
|
+
"updated_at": "<ISO UTC>",
|
|
102
|
+
"coordination_vector": {
|
|
103
|
+
"<concept>": {
|
|
104
|
+
"mean_confidence": 0.85,
|
|
105
|
+
"disclosure_level": "PROTECTED",
|
|
106
|
+
"event_count": 5,
|
|
107
|
+
"last_updated": "<ISO UTC>"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(self, gcc_dir: Path, aggregator: Optional[AggPhi] = None) -> None:
|
|
114
|
+
self.gcc_dir = gcc_dir
|
|
115
|
+
self.theta_path = gcc_dir / THETA_FILE
|
|
116
|
+
self.aggregator: AggPhi = aggregator if aggregator is not None else RulesBasedAggPhi()
|
|
117
|
+
|
|
118
|
+
# ------------------------------------------------------------------
|
|
119
|
+
# Load / save
|
|
120
|
+
# ------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
def load(self) -> dict:
|
|
123
|
+
"""Load the current Θ from disk; return a fresh structure if absent."""
|
|
124
|
+
if not self.theta_path.exists():
|
|
125
|
+
return {"version": 1, "updated_at": None, "coordination_vector": {}}
|
|
126
|
+
try:
|
|
127
|
+
return json.loads(self.theta_path.read_text(encoding="utf-8"))
|
|
128
|
+
except (json.JSONDecodeError, OSError):
|
|
129
|
+
return {"version": 1, "updated_at": None, "coordination_vector": {}}
|
|
130
|
+
|
|
131
|
+
def save(self, theta: dict) -> None:
|
|
132
|
+
self.gcc_dir.mkdir(parents=True, exist_ok=True)
|
|
133
|
+
self.theta_path.write_text(
|
|
134
|
+
json.dumps(theta, indent=2, sort_keys=True) + "\n",
|
|
135
|
+
encoding="utf-8",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# ------------------------------------------------------------------
|
|
139
|
+
# Ripple propagation
|
|
140
|
+
# ------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
def ripple(self, events: List[dict]) -> dict:
|
|
143
|
+
"""
|
|
144
|
+
Aggregate *events* via Aggφ and merge the delta into the stored Θ.
|
|
145
|
+
|
|
146
|
+
Merge rules per concept:
|
|
147
|
+
- confidence: weighted blend of existing + new (weighted by event counts).
|
|
148
|
+
- disclosure_level: escalate to the maximum of existing and incoming.
|
|
149
|
+
- event_count: cumulative sum.
|
|
150
|
+
|
|
151
|
+
Returns the updated Θ dict.
|
|
152
|
+
"""
|
|
153
|
+
delta = self.aggregator.aggregate(events)
|
|
154
|
+
theta = self.load()
|
|
155
|
+
cv: dict = theta.setdefault("coordination_vector", {})
|
|
156
|
+
now = _dt.datetime.now(tz=_dt.timezone.utc).isoformat()
|
|
157
|
+
|
|
158
|
+
for concept, entry in delta.items():
|
|
159
|
+
existing = cv.get(concept, {})
|
|
160
|
+
old_count = int(existing.get("event_count", 0))
|
|
161
|
+
new_count = int(entry["event_count"])
|
|
162
|
+
total = old_count + new_count
|
|
163
|
+
|
|
164
|
+
if total:
|
|
165
|
+
old_conf = float(existing.get("mean_confidence", entry["mean_confidence"]))
|
|
166
|
+
new_conf = float(entry["mean_confidence"])
|
|
167
|
+
blended = (old_conf * old_count + new_conf * new_count) / total
|
|
168
|
+
else:
|
|
169
|
+
blended = float(entry["mean_confidence"])
|
|
170
|
+
|
|
171
|
+
existing_rank = _LEVEL_RANK.get(existing.get("disclosure_level", "PUBLIC"), 0)
|
|
172
|
+
new_rank = _LEVEL_RANK.get(entry["disclosure_level"], 0)
|
|
173
|
+
merged_level = _RANK_LEVEL[max(existing_rank, new_rank)]
|
|
174
|
+
|
|
175
|
+
cv[concept] = {
|
|
176
|
+
"mean_confidence": round(blended, 6),
|
|
177
|
+
"disclosure_level": merged_level,
|
|
178
|
+
"event_count": total,
|
|
179
|
+
"last_updated": now,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
theta["updated_at"] = now
|
|
183
|
+
self.save(theta)
|
|
184
|
+
|
|
185
|
+
# S8: near-duplicate concept pairs for I3 handshake (Textual Mode Aggφ only)
|
|
186
|
+
if getattr(self.aggregator, "records_i3_merge_candidates", False):
|
|
187
|
+
try:
|
|
188
|
+
from .aggphi_textual import merge_i3_records, near_duplicate_pairs
|
|
189
|
+
|
|
190
|
+
thr = float(getattr(self.aggregator, "similarity_threshold", 0.82))
|
|
191
|
+
theta = self.load()
|
|
192
|
+
cv_keys = list(theta.setdefault("coordination_vector", {}).keys())
|
|
193
|
+
pairs = near_duplicate_pairs(cv_keys, threshold=thr)
|
|
194
|
+
if pairs:
|
|
195
|
+
prev = theta.get("pending_i3_merges")
|
|
196
|
+
if not isinstance(prev, list):
|
|
197
|
+
prev = []
|
|
198
|
+
theta["pending_i3_merges"] = merge_i3_records(prev, pairs)
|
|
199
|
+
self.save(theta)
|
|
200
|
+
except Exception:
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
return self.load()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def make_theta_store(gcc_dir: Path) -> ThetaStore:
|
|
207
|
+
"""
|
|
208
|
+
Construct a :class:`ThetaStore` for *gcc_dir*, respecting ``DEVTORCH_AGGPHI``:
|
|
209
|
+
|
|
210
|
+
- ``rules`` (default): :class:`RulesBasedAggPhi`
|
|
211
|
+
- ``textual`` / ``s8`` / ``text``: :class:`TextualModeAggPhi` (Sprint 8)
|
|
212
|
+
"""
|
|
213
|
+
mode = os.environ.get("DEVTORCH_AGGPHI", "rules").strip().lower()
|
|
214
|
+
if mode in ("textual", "s8", "text"):
|
|
215
|
+
try:
|
|
216
|
+
from .aggphi_textual import TextualModeAggPhi
|
|
217
|
+
|
|
218
|
+
return ThetaStore(gcc_dir, aggregator=TextualModeAggPhi())
|
|
219
|
+
except Exception:
|
|
220
|
+
return ThetaStore(gcc_dir)
|
|
221
|
+
return ThetaStore(gcc_dir)
|