sidegraph 0.1.0__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.
- sidegraph/__init__.py +37 -0
- sidegraph/anchoring.py +246 -0
- sidegraph/bootstrap/__init__.py +49 -0
- sidegraph/bootstrap/apply.py +603 -0
- sidegraph/bootstrap/catalog.py +92 -0
- sidegraph/bootstrap/cli.py +827 -0
- sidegraph/bootstrap/integrations.py +184 -0
- sidegraph/bootstrap/model.py +277 -0
- sidegraph/bootstrap/planner.py +400 -0
- sidegraph/bootstrap/proof.py +103 -0
- sidegraph/bootstrap/review.py +331 -0
- sidegraph/bootstrap/scan.py +289 -0
- sidegraph/capture.py +1794 -0
- sidegraph/cli.py +1902 -0
- sidegraph/config.py +148 -0
- sidegraph/doc_import.py +2099 -0
- sidegraph/doctor.py +1429 -0
- sidegraph/domains.py +902 -0
- sidegraph/engine/__init__.py +7 -0
- sidegraph/engine/reader.py +353 -0
- sidegraph/gitio.py +572 -0
- sidegraph/host/__init__.py +7 -0
- sidegraph/host/hooks.py +770 -0
- sidegraph/importer.py +239 -0
- sidegraph/okf.py +471 -0
- sidegraph/profiles.py +459 -0
- sidegraph/retrieval.py +1657 -0
- sidegraph/schema.py +386 -0
- sidegraph/server.py +2608 -0
- sidegraph/store.py +3363 -0
- sidegraph/sync.py +885 -0
- sidegraph/verify.py +1040 -0
- sidegraph/viz/__init__.py +4 -0
- sidegraph/viz/assets/vis-network.min.js +33 -0
- sidegraph/viz/model.py +248 -0
- sidegraph/viz/render.py +110 -0
- sidegraph/viz/template.html +131 -0
- sidegraph-0.1.0.dist-info/METADATA +392 -0
- sidegraph-0.1.0.dist-info/RECORD +42 -0
- sidegraph-0.1.0.dist-info/WHEEL +4 -0
- sidegraph-0.1.0.dist-info/entry_points.txt +18 -0
- sidegraph-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Pure, local inspection of Sidegraph host integration configuration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import shlex
|
|
7
|
+
import tomllib
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Literal
|
|
11
|
+
|
|
12
|
+
from .model import HostKind, IntegrationResult
|
|
13
|
+
|
|
14
|
+
Status = Literal["verified", "missing", "invalid", "unsupported"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_json(path: Path) -> tuple[Mapping[str, Any] | None, bool]:
|
|
18
|
+
if not path.is_file():
|
|
19
|
+
return None, False
|
|
20
|
+
try:
|
|
21
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
22
|
+
except (OSError, json.JSONDecodeError, UnicodeDecodeError):
|
|
23
|
+
return None, True
|
|
24
|
+
return (data, False) if isinstance(data, Mapping) else (None, True)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _load_toml(path: Path) -> tuple[Mapping[str, Any] | None, bool]:
|
|
28
|
+
if not path.is_file():
|
|
29
|
+
return None, False
|
|
30
|
+
try:
|
|
31
|
+
data = tomllib.loads(path.read_text(encoding="utf-8"))
|
|
32
|
+
except (OSError, tomllib.TOMLDecodeError, UnicodeDecodeError):
|
|
33
|
+
return None, True
|
|
34
|
+
return data, False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _command_strings(value: object) -> tuple[str, ...]:
|
|
38
|
+
# Iterative, not recursive: value is untrusted, user-editable JSON/TOML (.mcp.json,
|
|
39
|
+
# Codex hooks config), and a config author can nest it arbitrarily deep. A recursive
|
|
40
|
+
# walk hits CPython's call-stack recursion limit on a few thousand levels of nesting
|
|
41
|
+
# and raises RecursionError with no injection needed at all -- an explicit worklist
|
|
42
|
+
# is bounded only by heap memory, not the call stack, so no plausible config depth
|
|
43
|
+
# can trip it.
|
|
44
|
+
commands: list[str] = []
|
|
45
|
+
stack: list[object] = [value]
|
|
46
|
+
while stack:
|
|
47
|
+
current = stack.pop()
|
|
48
|
+
if isinstance(current, Mapping):
|
|
49
|
+
for key, child in current.items():
|
|
50
|
+
if key == "command" and isinstance(child, str):
|
|
51
|
+
commands.append(child)
|
|
52
|
+
stack.append(child)
|
|
53
|
+
elif isinstance(current, list):
|
|
54
|
+
stack.extend(current)
|
|
55
|
+
return tuple(commands)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _contains_entrypoint(commands: tuple[str, ...], entrypoint: str) -> bool:
|
|
59
|
+
for command in commands:
|
|
60
|
+
try:
|
|
61
|
+
if entrypoint in shlex.split(command):
|
|
62
|
+
return True
|
|
63
|
+
except ValueError:
|
|
64
|
+
continue
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _status(config: Mapping[str, Any] | None, invalid: bool, entrypoint: str) -> Status:
|
|
69
|
+
if invalid:
|
|
70
|
+
return "invalid"
|
|
71
|
+
if config is None:
|
|
72
|
+
return "missing"
|
|
73
|
+
return "verified" if _contains_entrypoint(_command_strings(config), entrypoint) else "missing"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _event_status(
|
|
77
|
+
config: Mapping[str, Any] | None,
|
|
78
|
+
invalid: bool,
|
|
79
|
+
event: str,
|
|
80
|
+
entrypoint: str,
|
|
81
|
+
) -> Status:
|
|
82
|
+
if invalid:
|
|
83
|
+
return "invalid"
|
|
84
|
+
if config is None:
|
|
85
|
+
return "missing"
|
|
86
|
+
hooks = config.get("hooks")
|
|
87
|
+
if not isinstance(hooks, Mapping):
|
|
88
|
+
return "missing"
|
|
89
|
+
event_config = hooks.get(event)
|
|
90
|
+
return (
|
|
91
|
+
"verified"
|
|
92
|
+
if _contains_entrypoint(_command_strings(event_config), entrypoint)
|
|
93
|
+
else "missing"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _repair_action(path: Path, root: Path) -> str:
|
|
98
|
+
try:
|
|
99
|
+
display_path = path.relative_to(root).as_posix()
|
|
100
|
+
except ValueError:
|
|
101
|
+
display_path = path.name
|
|
102
|
+
return f"Repair {display_path}, then rerun sidegraph-bootstrap"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _configure_action(entrypoint: str, path: Path, root: Path) -> str:
|
|
106
|
+
try:
|
|
107
|
+
display_path = path.relative_to(root).as_posix()
|
|
108
|
+
except ValueError:
|
|
109
|
+
display_path = path.name
|
|
110
|
+
return f"Configure {entrypoint} in {display_path}, then rerun sidegraph-bootstrap"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _next_action(checks: tuple[tuple[Status, str, Path, bool], ...], root: Path) -> str | None:
|
|
114
|
+
for status, entrypoint, path, invalid in checks:
|
|
115
|
+
if invalid:
|
|
116
|
+
return _repair_action(path, root)
|
|
117
|
+
if status != "verified":
|
|
118
|
+
return _configure_action(entrypoint, path, root)
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _verify_claude(root: Path) -> IntegrationResult:
|
|
123
|
+
mcp_path = root / ".mcp.json"
|
|
124
|
+
hooks_path = root / ".claude" / "settings.json"
|
|
125
|
+
mcp_config, mcp_invalid = _load_json(mcp_path)
|
|
126
|
+
hooks_config, hooks_invalid = _load_json(hooks_path)
|
|
127
|
+
mcp = _status(mcp_config, mcp_invalid, "sidegraph-mcp")
|
|
128
|
+
session_start = _event_status(
|
|
129
|
+
hooks_config, hooks_invalid, "SessionStart", "sidegraph-session-start"
|
|
130
|
+
)
|
|
131
|
+
stop = _event_status(hooks_config, hooks_invalid, "Stop", "sidegraph-stop")
|
|
132
|
+
pretool = _event_status(hooks_config, hooks_invalid, "PreToolUse", "sidegraph-pre-tool-use")
|
|
133
|
+
checks = (
|
|
134
|
+
(mcp, "sidegraph-mcp", mcp_path, mcp_invalid),
|
|
135
|
+
(session_start, "sidegraph-session-start", hooks_path, hooks_invalid),
|
|
136
|
+
(stop, "sidegraph-stop", hooks_path, hooks_invalid),
|
|
137
|
+
(pretool, "sidegraph-pre-tool-use", hooks_path, hooks_invalid),
|
|
138
|
+
)
|
|
139
|
+
fully_supported = all(status == "verified" for status, *_ in checks)
|
|
140
|
+
return IntegrationResult(
|
|
141
|
+
host=HostKind.CLAUDE_CODE,
|
|
142
|
+
mcp=mcp,
|
|
143
|
+
session_start=session_start,
|
|
144
|
+
stop=stop,
|
|
145
|
+
pretool_read_grep=pretool,
|
|
146
|
+
fully_supported=fully_supported,
|
|
147
|
+
next_action=None if fully_supported else _next_action(checks, root),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _verify_codex(root: Path, codex_config: Path | None) -> IntegrationResult:
|
|
152
|
+
mcp_path = codex_config or root / ".codex" / "config.toml"
|
|
153
|
+
hooks_path = root / ".codex" / "hooks" / "hooks.json"
|
|
154
|
+
mcp_config, mcp_invalid = _load_toml(mcp_path)
|
|
155
|
+
hooks_config, hooks_invalid = _load_json(hooks_path)
|
|
156
|
+
mcp_status = _status(mcp_config, mcp_invalid, "sidegraph-mcp")
|
|
157
|
+
session_status = _event_status(
|
|
158
|
+
hooks_config, hooks_invalid, "SessionStart", "sidegraph-session-start"
|
|
159
|
+
)
|
|
160
|
+
stop_status = _event_status(hooks_config, hooks_invalid, "Stop", "sidegraph-stop")
|
|
161
|
+
checks = (
|
|
162
|
+
(mcp_status, "sidegraph-mcp", mcp_path, mcp_invalid),
|
|
163
|
+
(session_status, "sidegraph-session-start", hooks_path, hooks_invalid),
|
|
164
|
+
(stop_status, "sidegraph-stop", hooks_path, hooks_invalid),
|
|
165
|
+
)
|
|
166
|
+
next_action = _next_action(checks, root)
|
|
167
|
+
return IntegrationResult(
|
|
168
|
+
host=HostKind.CODEX,
|
|
169
|
+
mcp=mcp_status,
|
|
170
|
+
session_start=session_status,
|
|
171
|
+
stop=stop_status,
|
|
172
|
+
pretool_read_grep="unsupported",
|
|
173
|
+
fully_supported=False,
|
|
174
|
+
next_action=next_action,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def verify_integration(
|
|
179
|
+
root: Path, host: HostKind, *, codex_config: Path | None = None
|
|
180
|
+
) -> IntegrationResult:
|
|
181
|
+
"""Inspect host configuration without executing a host or modifying its files."""
|
|
182
|
+
if host == HostKind.CLAUDE_CODE:
|
|
183
|
+
return _verify_claude(root)
|
|
184
|
+
return _verify_codex(root, codex_config)
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field, computed_field
|
|
9
|
+
|
|
10
|
+
from sidegraph.profiles import ProfileDetection as ProfileDetection
|
|
11
|
+
from sidegraph.schema import Decision, DecisionKind, DecisionStatus, Descriptor
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FrozenModel(BaseModel):
|
|
15
|
+
model_config = ConfigDict(frozen=True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class WarningCode(StrEnum):
|
|
19
|
+
MISSING_CHOICE = "missing-choice"
|
|
20
|
+
MISSING_REJECTED = "missing-rejected-alternatives"
|
|
21
|
+
UNRESOLVED_ANCHOR = "unresolved-anchor"
|
|
22
|
+
AMBIGUOUS_ANCHOR = "ambiguous-anchor"
|
|
23
|
+
CURRENT_STATE = "likely-current-state-summary"
|
|
24
|
+
DUPLICATE_PLAN = "duplicate-within-plan"
|
|
25
|
+
DUPLICATE_CANONICAL = "duplicate-canonical-memory"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# One consequence sentence per WarningCode (design §3.8, m3) — rendered beside the bare code
|
|
29
|
+
# in render_candidate so a reviewer sees what accepting will actually do, not just a label.
|
|
30
|
+
# A stable, documented public contract (predecessor spec §3.3): pinned by
|
|
31
|
+
# tests/test_bootstrap_review.py and mirrored in docs/getting-started/bootstrap.md.
|
|
32
|
+
#
|
|
33
|
+
# DUPLICATE_CANONICAL is the one load-bearing case: it fires on a REF match
|
|
34
|
+
# (planner.py's _enrich_candidate queries catalog.find_by_ref), never a content match, so
|
|
35
|
+
# its sentence must stay true across every content relationship that ref can carry —
|
|
36
|
+
# checked clause by clause, by construction, against all six write-path cells
|
|
37
|
+
# (existing accepted/proposed/rejected record x identical/changed accepted content; see
|
|
38
|
+
# design §3.8's table and the doc-import write seam in doc_import.py/planner.py):
|
|
39
|
+
# accepted + identical -> skipped-existing, record unchanged (bindings may still repair)
|
|
40
|
+
# accepted + changed -> superseding successor written, predecessor closed
|
|
41
|
+
# proposed + identical -> ratified in place (the accept grant from spec §3.1), any
|
|
42
|
+
# supporting facts cascade
|
|
43
|
+
# proposed + changed -> Store.drop marks the stale draft rejected, a fresh record is
|
|
44
|
+
# written with supersedes=None
|
|
45
|
+
# rejected + identical -> needs_status_change flips; a new accepted record revives it
|
|
46
|
+
# with supersedes=<rejected id>
|
|
47
|
+
# rejected + changed -> a fresh accepted record is written with supersedes=None (no
|
|
48
|
+
# open record exists to close, so the clause is vacuously true)
|
|
49
|
+
# Two earlier drafts of this sentence were false: one claimed accepting always "writes a
|
|
50
|
+
# successor that supersedes the stored record" (false for identical-vs-accepted); the next
|
|
51
|
+
# claimed identical content "is skipped without a write" (false for the ratify and revive
|
|
52
|
+
# cells, and over-claimed even for the one cell it fit, since the skipped-existing branch
|
|
53
|
+
# still runs _apply_doc_bindings and can write binding records).
|
|
54
|
+
WARNING_CONSEQUENCES: Mapping[WarningCode, str] = {
|
|
55
|
+
WarningCode.MISSING_CHOICE: (
|
|
56
|
+
"the document states no decision; the record would carry only context"
|
|
57
|
+
),
|
|
58
|
+
WarningCode.MISSING_REJECTED: (
|
|
59
|
+
"no rejected alternatives were found; the most valuable field stays empty"
|
|
60
|
+
),
|
|
61
|
+
WarningCode.UNRESOLVED_ANCHOR: (
|
|
62
|
+
"the anchor does not resolve; the record would not surface for that code"
|
|
63
|
+
),
|
|
64
|
+
WarningCode.AMBIGUOUS_ANCHOR: (
|
|
65
|
+
"several entities match; Sidegraph never guesses, so the anchor stays degraded"
|
|
66
|
+
),
|
|
67
|
+
WarningCode.CURRENT_STATE: (
|
|
68
|
+
"this reads as a current-state summary, not a decision with a fork"
|
|
69
|
+
),
|
|
70
|
+
WarningCode.DUPLICATE_PLAN: ("another candidate in this same plan carries identical content"),
|
|
71
|
+
WarningCode.DUPLICATE_CANONICAL: (
|
|
72
|
+
"a record already exists for this source; accepting identical content leaves an "
|
|
73
|
+
"accepted record unchanged, ratifies a pending proposal, or revives a rejected "
|
|
74
|
+
"record — accepting changed content writes a replacement (closing any open record "
|
|
75
|
+
"at this source), and edits made in an earlier review are not carried over"
|
|
76
|
+
),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class Exclusion(FrozenModel):
|
|
81
|
+
path: str
|
|
82
|
+
reason: str
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ScanResult(FrozenModel):
|
|
86
|
+
root: str
|
|
87
|
+
files: tuple[str, ...] = ()
|
|
88
|
+
exclusions: tuple[Exclusion, ...] = ()
|
|
89
|
+
max_bytes: int = 512_000
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class SourceFingerprint(FrozenModel):
|
|
93
|
+
path: str
|
|
94
|
+
sha256: str
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class AnchorPlan(FrozenModel):
|
|
98
|
+
descriptor: Descriptor
|
|
99
|
+
status: Literal["resolved", "ambiguous", "unresolved"]
|
|
100
|
+
candidates: tuple[str, ...] = ()
|
|
101
|
+
tier: int | None = None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class PlanIssue(FrozenModel):
|
|
105
|
+
file_path: str
|
|
106
|
+
ref: str | None = None
|
|
107
|
+
warning: WarningCode
|
|
108
|
+
detail: str
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class EditableCandidate(FrozenModel):
|
|
112
|
+
title: str
|
|
113
|
+
context: str
|
|
114
|
+
choice: str
|
|
115
|
+
rejected: str | None = None
|
|
116
|
+
consequences: str | None = None
|
|
117
|
+
kind: DecisionKind
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class BootstrapCandidate(FrozenModel):
|
|
121
|
+
key: str
|
|
122
|
+
file_path: str
|
|
123
|
+
ref: str
|
|
124
|
+
fragment: str | None = None
|
|
125
|
+
source_hash: str
|
|
126
|
+
title: str
|
|
127
|
+
context: str
|
|
128
|
+
choice: str
|
|
129
|
+
rejected: str | None = None
|
|
130
|
+
consequences: str | None = None
|
|
131
|
+
kind: DecisionKind
|
|
132
|
+
default_status: DecisionStatus
|
|
133
|
+
redacted_anchor_text: str = Field(exclude=True)
|
|
134
|
+
anchor_intents: tuple[Descriptor, ...] = ()
|
|
135
|
+
file_anchor_intent: Descriptor | None = None
|
|
136
|
+
anchors: tuple[AnchorPlan, ...] = ()
|
|
137
|
+
warnings: tuple[WarningCode, ...] = ()
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def from_fields(cls, **fields: Any) -> BootstrapCandidate:
|
|
141
|
+
material = "\0".join(
|
|
142
|
+
str(fields.get(name) or "")
|
|
143
|
+
for name in (
|
|
144
|
+
"ref",
|
|
145
|
+
"title",
|
|
146
|
+
"context",
|
|
147
|
+
"choice",
|
|
148
|
+
"rejected",
|
|
149
|
+
"consequences",
|
|
150
|
+
"kind",
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
return cls(key=hashlib.sha256(material.encode()).hexdigest()[:16], **fields)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class BootstrapPlan(FrozenModel):
|
|
157
|
+
root: str
|
|
158
|
+
profile: str
|
|
159
|
+
fingerprint: str
|
|
160
|
+
source_fingerprints: tuple[SourceFingerprint, ...] = ()
|
|
161
|
+
catalog_fingerprint: str
|
|
162
|
+
graph_version: str | None = None
|
|
163
|
+
candidates: tuple[BootstrapCandidate, ...] = ()
|
|
164
|
+
issues: tuple[PlanIssue, ...] = ()
|
|
165
|
+
files_read: tuple[str, ...] = ()
|
|
166
|
+
exclusions: tuple[Exclusion, ...] = ()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class ReviewAction(StrEnum):
|
|
170
|
+
ACCEPT = "accept"
|
|
171
|
+
KEEP_PROPOSED = "keep-proposed"
|
|
172
|
+
SKIP = "skip"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class ReviewedCandidate(FrozenModel):
|
|
176
|
+
candidate: BootstrapCandidate
|
|
177
|
+
action: ReviewAction
|
|
178
|
+
edited: bool = False
|
|
179
|
+
action_elapsed_seconds: float = Field(default=0.0, ge=0.0)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class ReviewResult(FrozenModel):
|
|
183
|
+
items: tuple[ReviewedCandidate, ...]
|
|
184
|
+
elapsed_seconds: float = Field(default=0.0, ge=0.0)
|
|
185
|
+
|
|
186
|
+
@computed_field # type: ignore[prop-decorator]
|
|
187
|
+
@property
|
|
188
|
+
def has_writes(self) -> bool:
|
|
189
|
+
return any(item.action != ReviewAction.SKIP for item in self.items)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class EditResult(FrozenModel):
|
|
193
|
+
candidate: BootstrapCandidate
|
|
194
|
+
diff: str
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class RunStatus(StrEnum):
|
|
198
|
+
COMPLETE = "complete"
|
|
199
|
+
PARTIAL_RECOVERABLE = "partial-recoverable"
|
|
200
|
+
INCOMPLETE = "incomplete"
|
|
201
|
+
DIAGNOSTIC = "diagnostic"
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class AcceptedRecord(FrozenModel):
|
|
205
|
+
record_id: str
|
|
206
|
+
ref: str
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class Reconciliation(FrozenModel):
|
|
210
|
+
durable: tuple[str, ...] = ()
|
|
211
|
+
pending: tuple[str, ...] = ()
|
|
212
|
+
canonical_files: tuple[str, ...] = ()
|
|
213
|
+
durable_accepted_records: tuple[AcceptedRecord, ...] = ()
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class BootstrapReport(FrozenModel):
|
|
217
|
+
status: RunStatus
|
|
218
|
+
documents_scanned: int
|
|
219
|
+
candidates: int
|
|
220
|
+
accepted: int
|
|
221
|
+
kept_proposed: int
|
|
222
|
+
skipped: int
|
|
223
|
+
reviewed_candidates: int = 0
|
|
224
|
+
accepted_without_edit: int = 0
|
|
225
|
+
edited_then_accepted: int = 0
|
|
226
|
+
kept_proposed_without_edit: int = 0
|
|
227
|
+
edited_then_kept_proposed: int = 0
|
|
228
|
+
candidate_precision_numerator: int = 0
|
|
229
|
+
review_elapsed_seconds: float = 0.0
|
|
230
|
+
accept_action_seconds: float = 0.0
|
|
231
|
+
edit_accept_action_seconds: float = 0.0
|
|
232
|
+
keep_proposed_action_seconds: float = 0.0
|
|
233
|
+
edit_keep_proposed_action_seconds: float = 0.0
|
|
234
|
+
skip_action_seconds: float = 0.0
|
|
235
|
+
live_anchors: int
|
|
236
|
+
review_debt_count: int
|
|
237
|
+
oldest_proposal_days: int | None = None
|
|
238
|
+
durable_candidate_keys: tuple[str, ...] = ()
|
|
239
|
+
pending_candidate_keys: tuple[str, ...] = ()
|
|
240
|
+
durable_accepted_records: tuple[AcceptedRecord, ...] = ()
|
|
241
|
+
canonical_files: tuple[str, ...] = ()
|
|
242
|
+
verification_failures: tuple[str, ...] = ()
|
|
243
|
+
failed_ref: str | None = None
|
|
244
|
+
error: str | None = None
|
|
245
|
+
next_command: str | None = None
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class HostKind(StrEnum):
|
|
249
|
+
CLAUDE_CODE = "claude-code"
|
|
250
|
+
CODEX = "codex"
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class IntegrationResult(FrozenModel):
|
|
254
|
+
host: HostKind
|
|
255
|
+
mcp: Literal["verified", "missing", "invalid", "unsupported"]
|
|
256
|
+
session_start: Literal["verified", "missing", "invalid", "unsupported"]
|
|
257
|
+
stop: Literal["verified", "missing", "invalid", "unsupported"]
|
|
258
|
+
pretool_read_grep: Literal["verified", "missing", "invalid", "unsupported"]
|
|
259
|
+
fully_supported: bool
|
|
260
|
+
next_action: str | None = None
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class ProofSelection(FrozenModel):
|
|
264
|
+
decision: Decision
|
|
265
|
+
file_path: str
|
|
266
|
+
rule: str
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class ProofResult(FrozenModel):
|
|
270
|
+
complete: bool
|
|
271
|
+
primary_line: str | None = None
|
|
272
|
+
source: str | None = None
|
|
273
|
+
file_path: str | None = None
|
|
274
|
+
selection_rule: str | None = None
|
|
275
|
+
full_context: str | None = None
|
|
276
|
+
copyable_prompt: str | None = None
|
|
277
|
+
reason: str | None = None
|