sin-code-bundle 0.9.2__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.
- sin_code_bundle/__init__.py +6 -0
- sin_code_bundle/agents_md.py +245 -0
- sin_code_bundle/ast_edit.py +323 -0
- sin_code_bundle/bench.py +506 -0
- sin_code_bundle/budget.py +51 -0
- sin_code_bundle/cache.py +131 -0
- sin_code_bundle/checkpoint.py +230 -0
- sin_code_bundle/cli.py +1943 -0
- sin_code_bundle/codocs.py +328 -0
- sin_code_bundle/dap_bridge.py +135 -0
- sin_code_bundle/data/codocs/SKILL.md +280 -0
- sin_code_bundle/gitnexus.py +368 -0
- sin_code_bundle/hashline.py +216 -0
- sin_code_bundle/hooks.py +249 -0
- sin_code_bundle/immortal_commit.py +288 -0
- sin_code_bundle/interceptor.py +119 -0
- sin_code_bundle/lsp_backend.py +303 -0
- sin_code_bundle/lsp_bootstrap.py +85 -0
- sin_code_bundle/markitdown.py +254 -0
- sin_code_bundle/mcp_config.py +455 -0
- sin_code_bundle/mcp_server.py +963 -0
- sin_code_bundle/memory.py +208 -0
- sin_code_bundle/merge_safety.py +313 -0
- sin_code_bundle/orchestration_worktrees.py +102 -0
- sin_code_bundle/policy.py +224 -0
- sin_code_bundle/preflight.py +152 -0
- sin_code_bundle/programming_workflow.py +541 -0
- sin_code_bundle/rtk.py +154 -0
- sin_code_bundle/safety.py +52 -0
- sin_code_bundle/session_warmup.py +247 -0
- sin_code_bundle/skills.py +188 -0
- sin_code_bundle/symbol_resolve.py +166 -0
- sin_code_bundle/tools/__init__.py +4 -0
- sin_code_bundle/tools/pypi_setup.py +289 -0
- sin_code_bundle/vfs.py +264 -0
- sin_code_bundle-0.9.2.dist-info/METADATA +470 -0
- sin_code_bundle-0.9.2.dist-info/RECORD +41 -0
- sin_code_bundle-0.9.2.dist-info/WHEEL +5 -0
- sin_code_bundle-0.9.2.dist-info/entry_points.txt +4 -0
- sin_code_bundle-0.9.2.dist-info/licenses/LICENSE +21 -0
- sin_code_bundle-0.9.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""CoDocs — Co-located Docs Standard validator.
|
|
3
|
+
|
|
4
|
+
Each code file may declare a companion ``.doc.md`` file via a first-line
|
|
5
|
+
reference comment, e.g.::
|
|
6
|
+
|
|
7
|
+
# Docs: router.doc.md (Python, shell, YAML, Makefile, ...)
|
|
8
|
+
// Docs: types.doc.md (TypeScript, Rust, Go, C, ...)
|
|
9
|
+
|
|
10
|
+
This module finds those references and verifies the referenced doc file
|
|
11
|
+
actually exists next to the source file. It replaces the original fragile
|
|
12
|
+
``grep | sed`` one-liner with a robust, testable implementation that ignores
|
|
13
|
+
matches inside multi-line strings/heredocs by only inspecting the first
|
|
14
|
+
non-shebang lines of each file.
|
|
15
|
+
|
|
16
|
+
It is intentionally dependency-free (stdlib only) so it works even when the
|
|
17
|
+
optional SIN-Code subsystems are not installed.
|
|
18
|
+
|
|
19
|
+
Docs: codocs.doc.md
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import re
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
# ── Scanner Configuration ────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
# Directories never scanned. Mirrors common build/VCS/tooling caches so the
|
|
31
|
+
# scanner stays fast on large repos (these folders balloon quickly).
|
|
32
|
+
DEFAULT_EXCLUDE = {
|
|
33
|
+
".git",
|
|
34
|
+
".hg",
|
|
35
|
+
".svn",
|
|
36
|
+
"__pycache__",
|
|
37
|
+
"node_modules",
|
|
38
|
+
"venv",
|
|
39
|
+
".venv",
|
|
40
|
+
"dist",
|
|
41
|
+
"build",
|
|
42
|
+
".mypy_cache",
|
|
43
|
+
".pytest_cache",
|
|
44
|
+
".ruff_cache",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# File extensions we consider "code" and therefore eligible for a Docs: ref.
|
|
48
|
+
# Limited to languages the SIN-Code stack actively targets; add carefully
|
|
49
|
+
# because the regex below is tuned to C-style/line/Python comment leaders.
|
|
50
|
+
# Makefile and Dockerfile are matched by name in ``_is_code_file`` (no suffix).
|
|
51
|
+
CODE_SUFFIXES = {
|
|
52
|
+
".py",
|
|
53
|
+
".pyi",
|
|
54
|
+
".ts",
|
|
55
|
+
".tsx",
|
|
56
|
+
".js",
|
|
57
|
+
".jsx",
|
|
58
|
+
".mjs",
|
|
59
|
+
".cjs",
|
|
60
|
+
".rs",
|
|
61
|
+
".go",
|
|
62
|
+
".java",
|
|
63
|
+
".kt",
|
|
64
|
+
".kts",
|
|
65
|
+
".scala",
|
|
66
|
+
".c",
|
|
67
|
+
".h",
|
|
68
|
+
".cc",
|
|
69
|
+
".cpp",
|
|
70
|
+
".hpp",
|
|
71
|
+
".cs",
|
|
72
|
+
".rb",
|
|
73
|
+
".php",
|
|
74
|
+
".swift",
|
|
75
|
+
".sh",
|
|
76
|
+
".bash",
|
|
77
|
+
".zsh",
|
|
78
|
+
".yaml",
|
|
79
|
+
".yml",
|
|
80
|
+
".toml",
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Extensionless files that still count as code (matched by exact basename).
|
|
84
|
+
CODE_FILENAMES = {"Makefile", "Dockerfile", "Justfile"}
|
|
85
|
+
|
|
86
|
+
# How many leading lines to inspect for a reference. The standard places it on
|
|
87
|
+
# the first line; we allow a small window (5) to tolerate a shebang / encoding
|
|
88
|
+
# cookie / license header line above it. Keep small — false positives grow
|
|
89
|
+
# linearly with this value.
|
|
90
|
+
_HEAD_LINES = 5
|
|
91
|
+
|
|
92
|
+
# Matches: optional comment leader, then "Docs:" then a path ending in .doc.md.
|
|
93
|
+
# Regex is VERBOSE so the comment leaders are easy to extend when new
|
|
94
|
+
# languages are added. The final `\*?/?\s*$` swallows closing block-comment
|
|
95
|
+
# tokens like `*/` so ``/* Docs: foo.doc.md */`` matches.
|
|
96
|
+
_DOCS_RE = re.compile(
|
|
97
|
+
r"""^\s*
|
|
98
|
+
(?:\#|//|/\*|\*|--|;)? # optional comment leader
|
|
99
|
+
\s*Docs:\s*
|
|
100
|
+
(?P<doc>[^\s*]+?\.doc\.md) # the referenced doc path
|
|
101
|
+
\s*\*?/?\s*$ # optional closing comment
|
|
102
|
+
""",
|
|
103
|
+
re.VERBOSE,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ── CoDocsReference: Parsed Reference ────────────────────────────────
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass(frozen=True)
|
|
111
|
+
class DocReference:
|
|
112
|
+
"""A ``Docs:`` reference discovered in a source file.
|
|
113
|
+
|
|
114
|
+
Attributes:
|
|
115
|
+
source: Path of the code file containing the reference, relative to
|
|
116
|
+
the scan root.
|
|
117
|
+
doc: The raw referenced path as written in the source (e.g.
|
|
118
|
+
``"router.doc.md"``). Unvalidated.
|
|
119
|
+
resolved: Absolute path the reference resolves to (source parent +
|
|
120
|
+
doc), computed at scan time.
|
|
121
|
+
exists: Whether ``resolved`` points to a regular file on disk.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
source: Path
|
|
125
|
+
doc: str # raw referenced path, as written
|
|
126
|
+
resolved: Path # absolute path the reference resolves to
|
|
127
|
+
exists: bool
|
|
128
|
+
|
|
129
|
+
def to_dict(self) -> dict:
|
|
130
|
+
"""Serialize to a JSON-friendly dict for CLI/JSON output."""
|
|
131
|
+
return {
|
|
132
|
+
"source": str(self.source),
|
|
133
|
+
"doc": self.doc,
|
|
134
|
+
"resolved": str(self.resolved),
|
|
135
|
+
"exists": self.exists,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# ── Scanner: Find All # Docs: References ──────────────────────────────
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _is_code_file(path: Path) -> bool:
|
|
143
|
+
"""True if ``path`` is a code file eligible for CoDocs scanning."""
|
|
144
|
+
if path.name in CODE_FILENAMES:
|
|
145
|
+
return True
|
|
146
|
+
return path.suffix in CODE_SUFFIXES
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _iter_code_files(root: Path, exclude: set[str]):
|
|
150
|
+
"""Yield eligible code files under ``root``, skipping ``exclude`` dirs."""
|
|
151
|
+
for path in sorted(root.rglob("*")):
|
|
152
|
+
if not path.is_file():
|
|
153
|
+
continue
|
|
154
|
+
if any(part in exclude for part in path.parts):
|
|
155
|
+
continue
|
|
156
|
+
if _is_code_file(path):
|
|
157
|
+
yield path
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _extract_reference(path: Path) -> str | None:
|
|
161
|
+
"""Return the referenced ``.doc.md`` path from a file's head, or None.
|
|
162
|
+
|
|
163
|
+
Reads at most ``_HEAD_LINES`` lines (shebang/encoding tolerant). Returns
|
|
164
|
+
None on either "no match" or "file unreadable" so the scanner keeps going.
|
|
165
|
+
"""
|
|
166
|
+
try:
|
|
167
|
+
with path.open("r", encoding="utf-8", errors="ignore") as fh:
|
|
168
|
+
for _ in range(_HEAD_LINES):
|
|
169
|
+
line = fh.readline()
|
|
170
|
+
if line == "":
|
|
171
|
+
break
|
|
172
|
+
match = _DOCS_RE.match(line)
|
|
173
|
+
if match:
|
|
174
|
+
return match.group("doc")
|
|
175
|
+
except OSError:
|
|
176
|
+
# Permission denied, binary file, etc. — treat as "no reference" so
|
|
177
|
+
# one bad file does not abort the whole scan.
|
|
178
|
+
return None
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# ── Validation: Check References Resolve ──────────────────────────────
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def scan(root: str | Path = ".", exclude: set[str] | None = None) -> list[DocReference]:
|
|
186
|
+
"""Scan ``root`` and return every CoDocs reference found.
|
|
187
|
+
|
|
188
|
+
Walks the tree, reads each code file's head, parses the ``Docs:`` line,
|
|
189
|
+
and resolves the target relative to the source file's directory. Files
|
|
190
|
+
without a reference are skipped silently; unreachable references are
|
|
191
|
+
still returned with ``exists=False`` so callers can report them.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
root: Filesystem path to scan. Defaults to current working directory.
|
|
195
|
+
exclude: Additional directory basenames to skip (merged with
|
|
196
|
+
``DEFAULT_EXCLUDE``).
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
A list of ``DocReference`` (one per file that declares a Docs line),
|
|
200
|
+
sorted by source path.
|
|
201
|
+
"""
|
|
202
|
+
root_path = Path(root).resolve()
|
|
203
|
+
excl = DEFAULT_EXCLUDE | (exclude or set())
|
|
204
|
+
references: list[DocReference] = []
|
|
205
|
+
for source in _iter_code_files(root_path, excl):
|
|
206
|
+
doc = _extract_reference(source)
|
|
207
|
+
if doc is None:
|
|
208
|
+
continue
|
|
209
|
+
resolved = (source.parent / doc).resolve()
|
|
210
|
+
references.append(
|
|
211
|
+
DocReference(
|
|
212
|
+
source=source.relative_to(root_path),
|
|
213
|
+
doc=doc,
|
|
214
|
+
resolved=resolved,
|
|
215
|
+
exists=resolved.is_file(),
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
return references
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def find_broken(root: str | Path = ".", exclude: set[str] | None = None) -> list[DocReference]:
|
|
222
|
+
"""Return only the references whose target doc file is missing."""
|
|
223
|
+
return [ref for ref in scan(root, exclude) if not ref.exists]
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# ── SOTA Inline Doc checks ─────────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@dataclass(frozen=True)
|
|
230
|
+
class InlineDocIssue:
|
|
231
|
+
"""A missing or deficient inline doc element.
|
|
232
|
+
|
|
233
|
+
Attributes:
|
|
234
|
+
path: Source file the issue was found in, relative to the scan root.
|
|
235
|
+
kind: Machine-readable issue category. Currently one of:
|
|
236
|
+
``"missing_purpose"`` — file lacks a Purpose/module-docstring
|
|
237
|
+
header in its first few lines.
|
|
238
|
+
detail: Human-readable explanation suitable for CLI output.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
path: Path
|
|
242
|
+
kind: str # "missing_purpose", "missing_docstring", "missing_section"
|
|
243
|
+
detail: str
|
|
244
|
+
|
|
245
|
+
def to_dict(self) -> dict:
|
|
246
|
+
"""Serialize to a JSON-friendly dict for CLI/JSON output."""
|
|
247
|
+
return {"path": str(self.path), "kind": self.kind, "detail": self.detail}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# Detects a SOTA-compliant file header: ``# Purpose: ...`` line or a
|
|
251
|
+
# Python module docstring (triple-single or triple-double quotes) appearing
|
|
252
|
+
# in the first _HEAD_LINES lines of the file.
|
|
253
|
+
_INLINE_HEAD_RE = re.compile(r"^\s*(?:#\s*Purpose\s*:|'''|\"\"\")")
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def check_inline_docs(
|
|
257
|
+
root: str | Path = ".",
|
|
258
|
+
exclude: set[str] | None = None,
|
|
259
|
+
) -> list[InlineDocIssue]:
|
|
260
|
+
"""Check files for SOTA inline doc compliance.
|
|
261
|
+
|
|
262
|
+
Currently checks:
|
|
263
|
+
- File header with ``Purpose`` line or module docstring.
|
|
264
|
+
|
|
265
|
+
The check is intentionally narrow: false positives in a docs linter
|
|
266
|
+
create noise, so we only flag files with no Purpose line AND no
|
|
267
|
+
Python module docstring in the first ``_HEAD_LINES`` lines.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
root: Filesystem path to scan. Defaults to current working directory.
|
|
271
|
+
exclude: Additional directory basenames to skip (merged with
|
|
272
|
+
``DEFAULT_EXCLUDE`` plus ``debug``/``tmp``).
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
A list of ``InlineDocIssue`` (one per non-compliant file), sorted by
|
|
276
|
+
path.
|
|
277
|
+
"""
|
|
278
|
+
root_path = Path(root).resolve()
|
|
279
|
+
# ``debug``/``tmp`` are extra ignores on top of the standard excludes —
|
|
280
|
+
# these folders are explicitly for throwaway experiments and are exempt
|
|
281
|
+
# from the docs standard per the AGENTS.md exception list.
|
|
282
|
+
excl = DEFAULT_EXCLUDE | {"debug", "tmp"} | (exclude or set())
|
|
283
|
+
issues: list[InlineDocIssue] = []
|
|
284
|
+
|
|
285
|
+
for path in sorted(root_path.rglob("*")):
|
|
286
|
+
if not path.is_file():
|
|
287
|
+
continue
|
|
288
|
+
if any(part in excl for part in path.parts):
|
|
289
|
+
continue
|
|
290
|
+
if not _is_code_file(path):
|
|
291
|
+
continue
|
|
292
|
+
# Inline doc header is currently only defined for the languages with
|
|
293
|
+
# line comments or Python module docstrings. YAML/TOML/SH etc. are
|
|
294
|
+
# scanned for Docs: refs but not for inline headers.
|
|
295
|
+
if path.suffix not in (".py", ".pyi", ".ts", ".tsx", ".js", ".jsx", ".rs", ".go"):
|
|
296
|
+
continue
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
300
|
+
except OSError:
|
|
301
|
+
continue
|
|
302
|
+
|
|
303
|
+
head = "\n".join(text.splitlines()[:_HEAD_LINES])
|
|
304
|
+
rel = path.relative_to(root_path)
|
|
305
|
+
if not _INLINE_HEAD_RE.search(head):
|
|
306
|
+
issues.append(
|
|
307
|
+
InlineDocIssue(
|
|
308
|
+
path=rel,
|
|
309
|
+
kind="missing_purpose",
|
|
310
|
+
detail="Missing Purpose/header comment in first lines",
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
return issues
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _check_inline_docs_json(root: str = ".", exclude: set[str] | None = None) -> str:
|
|
318
|
+
"""Inline doc check as JSON string, for CLI use.
|
|
319
|
+
|
|
320
|
+
Wraps :func:`check_inline_docs` in a JSON serialization so the CLI can
|
|
321
|
+
pipe the result without each caller re-importing :mod:`json`.
|
|
322
|
+
"""
|
|
323
|
+
import json
|
|
324
|
+
|
|
325
|
+
return json.dumps(
|
|
326
|
+
[issue.to_dict() for issue in check_inline_docs(root, exclude)],
|
|
327
|
+
indent=2,
|
|
328
|
+
)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Purpose: DAP runtime bridge for SIN-Code — attach debuggers, store runtime facts.
|
|
2
|
+
|
|
3
|
+
Docs: dap_bridge.doc.md
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import subprocess
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DAPSession:
|
|
14
|
+
"""Manages a single DAP debugging session."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, language: str, target: str, repo_root: Path):
|
|
17
|
+
self.language = language
|
|
18
|
+
self.target = target
|
|
19
|
+
self.repo_root = repo_root
|
|
20
|
+
self.process: Optional[subprocess.Popen] = None
|
|
21
|
+
self.port: Optional[int] = None
|
|
22
|
+
|
|
23
|
+
def start(self) -> dict:
|
|
24
|
+
try:
|
|
25
|
+
if self.language == "python":
|
|
26
|
+
self.port = 5678 # debugpy default port (https://github.com/microsoft/debugpy)
|
|
27
|
+
self.process = subprocess.Popen(
|
|
28
|
+
[
|
|
29
|
+
"python",
|
|
30
|
+
"-m",
|
|
31
|
+
"debugpy",
|
|
32
|
+
"--listen",
|
|
33
|
+
str(self.port),
|
|
34
|
+
"--wait-for-client",
|
|
35
|
+
self.target,
|
|
36
|
+
],
|
|
37
|
+
cwd=self.repo_root,
|
|
38
|
+
stdout=subprocess.PIPE,
|
|
39
|
+
stderr=subprocess.PIPE,
|
|
40
|
+
)
|
|
41
|
+
elif self.language == "go":
|
|
42
|
+
self.port = 2345 # delve default headless port
|
|
43
|
+
self.process = subprocess.Popen(
|
|
44
|
+
[
|
|
45
|
+
"dlv",
|
|
46
|
+
"debug",
|
|
47
|
+
"--headless",
|
|
48
|
+
"--listen",
|
|
49
|
+
f":{self.port}",
|
|
50
|
+
"--api-version=2",
|
|
51
|
+
self.target,
|
|
52
|
+
],
|
|
53
|
+
cwd=self.repo_root,
|
|
54
|
+
stdout=subprocess.PIPE,
|
|
55
|
+
stderr=subprocess.PIPE,
|
|
56
|
+
)
|
|
57
|
+
elif self.language in ("node", "javascript", "typescript"):
|
|
58
|
+
self.port = 9229 # node --inspect default port
|
|
59
|
+
self.process = subprocess.Popen(
|
|
60
|
+
["node", f"--inspect-brk={self.port}", self.target],
|
|
61
|
+
cwd=self.repo_root,
|
|
62
|
+
stdout=subprocess.PIPE,
|
|
63
|
+
stderr=subprocess.PIPE,
|
|
64
|
+
)
|
|
65
|
+
else:
|
|
66
|
+
return {"error": f"Unsupported language for DAP: {self.language}"}
|
|
67
|
+
return {
|
|
68
|
+
"success": True,
|
|
69
|
+
"port": self.port,
|
|
70
|
+
"message": f"Debugger attached on port {self.port}",
|
|
71
|
+
}
|
|
72
|
+
except FileNotFoundError:
|
|
73
|
+
return {"error": f"Debugger for {self.language} not found (install debugpy/dlv/node)."}
|
|
74
|
+
except Exception as e:
|
|
75
|
+
return {"error": str(e)}
|
|
76
|
+
|
|
77
|
+
def stop(self) -> None:
|
|
78
|
+
if self.process:
|
|
79
|
+
try:
|
|
80
|
+
self.process.terminate()
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
83
|
+
self.process = None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# ── SINRuntimeTrace: High-level Orchestrator ───────────────────────────────
|
|
87
|
+
class SINRuntimeTrace:
|
|
88
|
+
"""High-level runtime tracing orchestrator."""
|
|
89
|
+
|
|
90
|
+
def __init__(self, repo_root: Optional[Path] = None):
|
|
91
|
+
self.repo_root = repo_root or Path.cwd()
|
|
92
|
+
self.sessions: dict[str, DAPSession] = {}
|
|
93
|
+
|
|
94
|
+
def trace_function(
|
|
95
|
+
self,
|
|
96
|
+
file_path: str,
|
|
97
|
+
function_name: str,
|
|
98
|
+
language: str = "python",
|
|
99
|
+
store_in_memory: bool = True,
|
|
100
|
+
) -> dict:
|
|
101
|
+
session_id = f"{language}_{function_name}"
|
|
102
|
+
session = DAPSession(language, file_path, self.repo_root)
|
|
103
|
+
result = session.start()
|
|
104
|
+
if not result.get("success"):
|
|
105
|
+
return result
|
|
106
|
+
self.sessions[session_id] = session
|
|
107
|
+
if store_in_memory:
|
|
108
|
+
try:
|
|
109
|
+
from sin_code_bundle import memory
|
|
110
|
+
|
|
111
|
+
memory.remember(
|
|
112
|
+
f"Runtime trace initiated for {function_name} in {file_path} on port {result['port']}",
|
|
113
|
+
kind="runtime",
|
|
114
|
+
scope="repo",
|
|
115
|
+
)
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
return {
|
|
119
|
+
"success": True,
|
|
120
|
+
"session_id": session_id,
|
|
121
|
+
"port": result["port"],
|
|
122
|
+
"message": f"Attach DAP client to localhost:{result['port']} to inspect {function_name}",
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
def get_session_status(self, session_id: str) -> dict:
|
|
126
|
+
if session_id in self.sessions:
|
|
127
|
+
return {"active": True, "port": self.sessions[session_id].port}
|
|
128
|
+
return {"active": False, "error": "Session not found"}
|
|
129
|
+
|
|
130
|
+
def stop_trace(self, session_id: str) -> dict:
|
|
131
|
+
if session_id in self.sessions:
|
|
132
|
+
self.sessions[session_id].stop()
|
|
133
|
+
del self.sessions[session_id]
|
|
134
|
+
return {"success": True, "message": "Session terminated"}
|
|
135
|
+
return {"error": "Session not found"}
|