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,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Entry point for: python -m devtorch_core.codex
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python -m devtorch_core.codex start [--port PORT]
|
|
6
|
+
python -m devtorch_core.codex install [--project PATH]
|
|
7
|
+
python -m devtorch_core.codex status [--project PATH]
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _cmd_start(args: argparse.Namespace) -> None:
|
|
17
|
+
from devtorch_core.codex.proxy import CodexProxy
|
|
18
|
+
|
|
19
|
+
port: int = args.port
|
|
20
|
+
print(f"DevTorch Codex proxy running on http://localhost:{port}")
|
|
21
|
+
print(f"Configure Codex CLI: set OPENAI_BASE_URL=http://localhost:{port}/v1")
|
|
22
|
+
|
|
23
|
+
proxy = CodexProxy(port=port)
|
|
24
|
+
proxy.start() # blocks
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _cmd_install(args: argparse.Namespace) -> None:
|
|
28
|
+
import os
|
|
29
|
+
from devtorch_core.codex.proxy import install_codex_hook
|
|
30
|
+
|
|
31
|
+
project = args.project or os.getcwd()
|
|
32
|
+
ok, result = install_codex_hook(project)
|
|
33
|
+
if ok:
|
|
34
|
+
print(f"Codex config installed: {result}")
|
|
35
|
+
else:
|
|
36
|
+
print(f"Install failed: {result}", file=sys.stderr)
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _cmd_status(args: argparse.Namespace) -> None:
|
|
41
|
+
import os
|
|
42
|
+
from devtorch_core.codex.proxy import get_codex_hook_status
|
|
43
|
+
|
|
44
|
+
project = args.project or os.getcwd()
|
|
45
|
+
status = get_codex_hook_status(project)
|
|
46
|
+
print(json.dumps(status, indent=2))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def main() -> None:
|
|
50
|
+
parser = argparse.ArgumentParser(
|
|
51
|
+
prog="python -m devtorch_core.codex",
|
|
52
|
+
description="DevTorch OpenAI Codex CLI proxy",
|
|
53
|
+
)
|
|
54
|
+
sub = parser.add_subparsers(dest="command")
|
|
55
|
+
|
|
56
|
+
# start
|
|
57
|
+
start_p = sub.add_parser("start", help="Start the Codex proxy (blocking)")
|
|
58
|
+
start_p.add_argument(
|
|
59
|
+
"--port",
|
|
60
|
+
type=int,
|
|
61
|
+
default=8766,
|
|
62
|
+
help="Port to listen on (default: 8766)",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# install
|
|
66
|
+
install_p = sub.add_parser(
|
|
67
|
+
"install", help="Install .codex/config.json for the current project"
|
|
68
|
+
)
|
|
69
|
+
install_p.add_argument(
|
|
70
|
+
"--project",
|
|
71
|
+
default=None,
|
|
72
|
+
help="Project root directory (default: cwd)",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# status
|
|
76
|
+
status_p = sub.add_parser("status", help="Show proxy and hook install status")
|
|
77
|
+
status_p.add_argument(
|
|
78
|
+
"--project",
|
|
79
|
+
default=None,
|
|
80
|
+
help="Project root directory (default: cwd)",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
args = parser.parse_args()
|
|
84
|
+
|
|
85
|
+
if args.command == "start":
|
|
86
|
+
_cmd_start(args)
|
|
87
|
+
elif args.command == "install":
|
|
88
|
+
_cmd_install(args)
|
|
89
|
+
elif args.command == "status":
|
|
90
|
+
_cmd_status(args)
|
|
91
|
+
else:
|
|
92
|
+
parser.print_help()
|
|
93
|
+
sys.exit(1)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
if __name__ == "__main__":
|
|
97
|
+
main()
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""
|
|
2
|
+
devtorch_core.codex.capture
|
|
3
|
+
===========================
|
|
4
|
+
Handles RACP prefix injection and reasoning capture for the Codex CLI proxy.
|
|
5
|
+
|
|
6
|
+
CodexCaptureHandler:
|
|
7
|
+
- pre_execute — injects RACP system prefix into the request body before
|
|
8
|
+
forwarding to OpenAI.
|
|
9
|
+
- post_execute — fires CaptureOrchestrator and appends a POST_TOOL_USE event
|
|
10
|
+
to .GCC/ after the response arrives.
|
|
11
|
+
- find_gcc_repo — walks cwd upward to find an initialised .GCC/ directory.
|
|
12
|
+
|
|
13
|
+
All methods degrade silently; never raises.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import threading
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from devtorch_core.wrapper.base import (
|
|
23
|
+
CaptureOrchestrator,
|
|
24
|
+
RACPInjector,
|
|
25
|
+
is_disabled,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger("devtorch.codex.capture")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CodexCaptureHandler:
|
|
32
|
+
"""Injects RACP context into Codex CLI requests and captures reasoning."""
|
|
33
|
+
|
|
34
|
+
# ------------------------------------------------------------------
|
|
35
|
+
# Public API
|
|
36
|
+
# ------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
def pre_execute(self, request_body: dict, session_id: str) -> dict:
|
|
39
|
+
"""
|
|
40
|
+
Inject the RACP system prefix into *request_body* before forwarding.
|
|
41
|
+
|
|
42
|
+
Returns the modified request_body with a system message prepended, or
|
|
43
|
+
the original request_body unchanged if:
|
|
44
|
+
- DEVTORCH_DISABLE is set, or
|
|
45
|
+
- no initialised .GCC/ repo is found, or
|
|
46
|
+
- any error occurs.
|
|
47
|
+
"""
|
|
48
|
+
if is_disabled():
|
|
49
|
+
return request_body
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
repo = self.find_gcc_repo()
|
|
53
|
+
if repo is None:
|
|
54
|
+
return request_body
|
|
55
|
+
|
|
56
|
+
injector = RACPInjector(repo)
|
|
57
|
+
prefix = injector.build_system_prefix()
|
|
58
|
+
if not prefix:
|
|
59
|
+
return request_body
|
|
60
|
+
|
|
61
|
+
import copy
|
|
62
|
+
modified = copy.deepcopy(request_body)
|
|
63
|
+
messages = modified.get("messages", [])
|
|
64
|
+
|
|
65
|
+
# Prepend a system message with the RACP prefix
|
|
66
|
+
racp_msg = {"role": "system", "content": prefix}
|
|
67
|
+
# If the first message is already a system message, prepend our
|
|
68
|
+
# prefix to its content rather than inserting a second system msg
|
|
69
|
+
if messages and messages[0].get("role") == "system":
|
|
70
|
+
existing_content = messages[0].get("content", "")
|
|
71
|
+
messages[0]["content"] = prefix + "\n\n" + existing_content
|
|
72
|
+
else:
|
|
73
|
+
messages.insert(0, racp_msg)
|
|
74
|
+
|
|
75
|
+
modified["messages"] = messages
|
|
76
|
+
return modified
|
|
77
|
+
|
|
78
|
+
except Exception as exc:
|
|
79
|
+
logger.warning("devtorch codex pre_execute error — %s", exc)
|
|
80
|
+
return request_body
|
|
81
|
+
|
|
82
|
+
def post_execute(
|
|
83
|
+
self,
|
|
84
|
+
request_body: dict,
|
|
85
|
+
response_body: dict,
|
|
86
|
+
session_id: str,
|
|
87
|
+
) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Capture reasoning from the response into .GCC/ and append a
|
|
90
|
+
POST_TOOL_USE event to the event log.
|
|
91
|
+
|
|
92
|
+
Silent on all errors — never raises.
|
|
93
|
+
"""
|
|
94
|
+
if is_disabled():
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
repo = self.find_gcc_repo()
|
|
99
|
+
if repo is None:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
# Extract response text from OpenAI chat completion format
|
|
103
|
+
response_text = _extract_response_text(response_body)
|
|
104
|
+
thinking_blocks: list = []
|
|
105
|
+
|
|
106
|
+
# Fire CaptureOrchestrator
|
|
107
|
+
orchestrator = CaptureOrchestrator(repo)
|
|
108
|
+
orchestrator.capture(response_text, thinking_blocks, session_id)
|
|
109
|
+
|
|
110
|
+
# Append POST_TOOL_USE event
|
|
111
|
+
_append_post_tool_use_event(repo, session_id, request_body)
|
|
112
|
+
|
|
113
|
+
except Exception as exc:
|
|
114
|
+
logger.warning("devtorch codex post_execute error — %s", exc)
|
|
115
|
+
|
|
116
|
+
def find_gcc_repo(self) -> Any | None:
|
|
117
|
+
"""
|
|
118
|
+
Walk cwd upward looking for an initialised .GCC/ directory.
|
|
119
|
+
Returns a GCCRepository instance or None if not found.
|
|
120
|
+
"""
|
|
121
|
+
try:
|
|
122
|
+
from devtorch_core import GCCRepository
|
|
123
|
+
except ImportError:
|
|
124
|
+
logger.warning("devtorch: devtorch_core not importable — codex wrapper disabled")
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
for parent in [Path.cwd()] + list(Path.cwd().parents):
|
|
128
|
+
try:
|
|
129
|
+
candidate = GCCRepository.at(parent)
|
|
130
|
+
if candidate.is_initialized():
|
|
131
|
+
return candidate
|
|
132
|
+
except Exception:
|
|
133
|
+
continue
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ---------------------------------------------------------------------------
|
|
138
|
+
# Module-level helpers
|
|
139
|
+
# ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
def _extract_response_text(response_body: dict) -> str:
|
|
142
|
+
"""
|
|
143
|
+
Extract text content from an OpenAI chat completion response body.
|
|
144
|
+
Returns empty string on any error.
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
choices = response_body.get("choices", [])
|
|
148
|
+
if not choices:
|
|
149
|
+
return ""
|
|
150
|
+
first_choice = choices[0]
|
|
151
|
+
message = first_choice.get("message", {})
|
|
152
|
+
content = message.get("content", "")
|
|
153
|
+
return content or ""
|
|
154
|
+
except Exception:
|
|
155
|
+
return ""
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _append_post_tool_use_event(repo: Any, session_id: str, request_body: dict) -> None:
|
|
159
|
+
"""
|
|
160
|
+
Append a POST_TOOL_USE event to the repo's event log.
|
|
161
|
+
Mirrors the pattern in devtorch_core.hooks.runner.handle_post_tool_use.
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
import json
|
|
165
|
+
import hashlib
|
|
166
|
+
import time
|
|
167
|
+
from pathlib import Path as _Path
|
|
168
|
+
|
|
169
|
+
payload = {
|
|
170
|
+
"tool_name": "codex_cli",
|
|
171
|
+
"session_id": session_id,
|
|
172
|
+
"model": request_body.get("model", "unknown"),
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# Try the public _append_event method first
|
|
176
|
+
try:
|
|
177
|
+
repo._append_event("POST_TOOL_USE", payload)
|
|
178
|
+
return
|
|
179
|
+
except Exception:
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
# Fallback: write directly to events.log.jsonl
|
|
183
|
+
log_path = _Path(repo.gcc_dir) / "events.log.jsonl"
|
|
184
|
+
prev_hash = "0" * 64
|
|
185
|
+
if log_path.exists():
|
|
186
|
+
lines = log_path.read_text(encoding="utf-8").strip().splitlines()
|
|
187
|
+
if lines:
|
|
188
|
+
try:
|
|
189
|
+
last = json.loads(lines[-1])
|
|
190
|
+
prev_hash = last.get("hash", prev_hash)
|
|
191
|
+
except Exception:
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
entry = {
|
|
195
|
+
"type": "POST_TOOL_USE",
|
|
196
|
+
"ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
197
|
+
**payload,
|
|
198
|
+
}
|
|
199
|
+
entry_str = json.dumps(entry, sort_keys=True)
|
|
200
|
+
entry["hash"] = hashlib.sha256(
|
|
201
|
+
(prev_hash + entry_str).encode()
|
|
202
|
+
).hexdigest()
|
|
203
|
+
|
|
204
|
+
with log_path.open("a", encoding="utf-8") as f:
|
|
205
|
+
f.write(json.dumps(entry) + "\n")
|
|
206
|
+
|
|
207
|
+
except Exception as exc:
|
|
208
|
+
logger.warning("devtorch codex append_event failed — %s", exc)
|