claude-sql 0.4.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.
- claude_sql/__init__.py +5 -0
- claude_sql/binding.py +740 -0
- claude_sql/blind_handover.py +155 -0
- claude_sql/checkpointer.py +202 -0
- claude_sql/cli.py +2344 -0
- claude_sql/cluster_worker.py +208 -0
- claude_sql/community_worker.py +306 -0
- claude_sql/config.py +380 -0
- claude_sql/embed_worker.py +482 -0
- claude_sql/freeze.py +189 -0
- claude_sql/friction_worker.py +561 -0
- claude_sql/install_source.py +77 -0
- claude_sql/judge_worker.py +459 -0
- claude_sql/judges.py +239 -0
- claude_sql/kappa_worker.py +257 -0
- claude_sql/llm_worker.py +1760 -0
- claude_sql/logging_setup.py +95 -0
- claude_sql/output.py +248 -0
- claude_sql/parquet_shards.py +172 -0
- claude_sql/retry_queue.py +180 -0
- claude_sql/review_sheet_render.py +167 -0
- claude_sql/review_sheet_worker.py +463 -0
- claude_sql/schemas.py +454 -0
- claude_sql/session_text.py +387 -0
- claude_sql/skills_catalog.py +354 -0
- claude_sql/sql_views.py +1751 -0
- claude_sql/terms_worker.py +145 -0
- claude_sql/ungrounded_worker.py +190 -0
- claude_sql-0.4.0.dist-info/METADATA +530 -0
- claude_sql-0.4.0.dist-info/RECORD +32 -0
- claude_sql-0.4.0.dist-info/WHEEL +4 -0
- claude_sql-0.4.0.dist-info/entry_points.txt +3 -0
claude_sql/schemas.py
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
"""Pydantic v2 classification schemas wired for Bedrock output_config.format.
|
|
2
|
+
|
|
3
|
+
Each model exposes a ``SCHEMA_DICT`` module constant -- the ``model_json_schema()``
|
|
4
|
+
output flattened (no ``$ref``/``$defs``) and with ``additionalProperties: false``
|
|
5
|
+
injected at every object level, so it passes Bedrock's JSON Schema Draft 2020-12
|
|
6
|
+
subset validator.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Literal
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _bedrock_schema(model: type[BaseModel]) -> dict:
|
|
17
|
+
"""Turn a pydantic v2 BaseModel into a Bedrock-compatible JSON Schema.
|
|
18
|
+
|
|
19
|
+
Bedrock's structured output only accepts JSON Schema Draft 2020-12 SUBSET.
|
|
20
|
+
Pydantic's default ``model_json_schema()`` emits ``$ref``/``$defs`` for
|
|
21
|
+
nested models; Bedrock 400s on those. We inline refs and inject
|
|
22
|
+
``additionalProperties: false`` at every object level.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
model : type[BaseModel]
|
|
27
|
+
The pydantic model class to convert.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
dict
|
|
32
|
+
Flattened JSON Schema dict safe to pass to Bedrock's
|
|
33
|
+
``output_config.format.schema`` field.
|
|
34
|
+
"""
|
|
35
|
+
raw = model.model_json_schema()
|
|
36
|
+
return _flatten(raw)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _flatten(schema: dict, defs: dict | None = None) -> dict:
|
|
40
|
+
"""Recursively inline $ref references and add additionalProperties: false.
|
|
41
|
+
|
|
42
|
+
Uses ``$defs`` from the root schema as the lookup table for inlining.
|
|
43
|
+
Walks every nested object/array and removes ``$defs``/``$ref`` keys.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
schema : dict
|
|
48
|
+
A (sub)schema dict produced by pydantic's ``model_json_schema``.
|
|
49
|
+
defs : dict | None
|
|
50
|
+
The ``$defs`` lookup table from the root schema. Populated from
|
|
51
|
+
``schema`` on the first call.
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
dict
|
|
56
|
+
The flattened schema with refs inlined and object-typed nodes
|
|
57
|
+
forced to ``additionalProperties: false``.
|
|
58
|
+
"""
|
|
59
|
+
if defs is None:
|
|
60
|
+
defs = schema.get("$defs") or schema.get("definitions") or {}
|
|
61
|
+
out: dict = {}
|
|
62
|
+
for k, v in schema.items():
|
|
63
|
+
if k in ("$defs", "definitions"):
|
|
64
|
+
continue
|
|
65
|
+
if k == "$ref":
|
|
66
|
+
# e.g. "#/$defs/Conflict"
|
|
67
|
+
name = v.rsplit("/", 1)[-1]
|
|
68
|
+
target = defs.get(name, {})
|
|
69
|
+
return _flatten(target, defs) # replace the whole object
|
|
70
|
+
if isinstance(v, dict):
|
|
71
|
+
out[k] = _flatten(v, defs)
|
|
72
|
+
elif isinstance(v, list):
|
|
73
|
+
out[k] = [_flatten(i, defs) if isinstance(i, dict) else i for i in v]
|
|
74
|
+
else:
|
|
75
|
+
out[k] = v
|
|
76
|
+
# Ensure object-typed schemas reject unknown fields.
|
|
77
|
+
if out.get("type") == "object" and "additionalProperties" not in out:
|
|
78
|
+
out["additionalProperties"] = False
|
|
79
|
+
# Bedrock's JSON Schema subset rejects numeric range constraints on
|
|
80
|
+
# number/integer types; strip them. Pydantic still enforces them at
|
|
81
|
+
# response-parse time, so we keep the ge/le annotations in the models.
|
|
82
|
+
if out.get("type") in ("number", "integer"):
|
|
83
|
+
for banned in ("minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"):
|
|
84
|
+
out.pop(banned, None)
|
|
85
|
+
# String length constraints are also rejected by Bedrock's subset.
|
|
86
|
+
if out.get("type") == "string":
|
|
87
|
+
for banned in ("minLength", "maxLength", "pattern", "format"):
|
|
88
|
+
out.pop(banned, None)
|
|
89
|
+
# Array item/length constraints.
|
|
90
|
+
if out.get("type") == "array":
|
|
91
|
+
for banned in ("minItems", "maxItems", "uniqueItems"):
|
|
92
|
+
out.pop(banned, None)
|
|
93
|
+
# Title keys just add noise; Bedrock accepts them but they clutter the
|
|
94
|
+
# schema for the model's pattern-matching. Keep descriptions, drop titles.
|
|
95
|
+
out.pop("title", None)
|
|
96
|
+
return out
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class SessionClassification(BaseModel):
|
|
100
|
+
"""Classify an entire Claude Code session.
|
|
101
|
+
|
|
102
|
+
One row per session, written to ``session_classifications.parquet``.
|
|
103
|
+
Used to build the ``session_classifications`` DuckDB view.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
model_config = ConfigDict(extra="forbid")
|
|
107
|
+
|
|
108
|
+
autonomy_tier: Literal["manual", "assisted", "autonomous"] = Field(
|
|
109
|
+
...,
|
|
110
|
+
description=(
|
|
111
|
+
"How much the agent drove the work. "
|
|
112
|
+
"'manual': the user typed every instruction and confirmed each step. "
|
|
113
|
+
"'assisted': the agent took initiative but the user course-corrected often. "
|
|
114
|
+
"'autonomous': the agent ran multi-step work end-to-end with minimal user "
|
|
115
|
+
"intervention -- tier-3 work."
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
work_category: Literal[
|
|
119
|
+
"sde",
|
|
120
|
+
"admin",
|
|
121
|
+
"strategy_business",
|
|
122
|
+
"events",
|
|
123
|
+
"thought_leadership",
|
|
124
|
+
"other",
|
|
125
|
+
] = Field(
|
|
126
|
+
...,
|
|
127
|
+
description=(
|
|
128
|
+
"Dominant activity. "
|
|
129
|
+
"'sde': software engineering, debugging, code review, tests, CI. "
|
|
130
|
+
"'admin': expense reports, scheduling, low-signal email triage, routine ops. "
|
|
131
|
+
"'strategy_business': business analysis, competitive research, strategic memos, "
|
|
132
|
+
"proposals. "
|
|
133
|
+
"'events': event planning/logistics, speaker prep, agenda building. "
|
|
134
|
+
"'thought_leadership': writing for external audiences (blog posts, conference "
|
|
135
|
+
"abstracts, LinkedIn). "
|
|
136
|
+
"'other': use only when nothing else fits."
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
success: Literal["success", "partial", "failure", "unknown"] = Field(
|
|
140
|
+
...,
|
|
141
|
+
description=(
|
|
142
|
+
"Did the session complete its stated goal? "
|
|
143
|
+
"'success': the user's goal was clearly met. "
|
|
144
|
+
"'partial': the goal was reached with caveats or leftover TODOs. "
|
|
145
|
+
"'failure': the session ended without achieving the goal. "
|
|
146
|
+
"'unknown': insufficient signal to judge."
|
|
147
|
+
),
|
|
148
|
+
)
|
|
149
|
+
goal: str = Field(
|
|
150
|
+
...,
|
|
151
|
+
min_length=1,
|
|
152
|
+
max_length=280,
|
|
153
|
+
description=(
|
|
154
|
+
"ONE sentence summarizing the user's goal, inferred from the opening user "
|
|
155
|
+
"messages and overall arc. Present tense, <= 280 chars. Example: "
|
|
156
|
+
'"Refactor the auth middleware to use the new token rotator."'
|
|
157
|
+
),
|
|
158
|
+
)
|
|
159
|
+
confidence: float = Field(
|
|
160
|
+
...,
|
|
161
|
+
ge=0.0,
|
|
162
|
+
le=1.0,
|
|
163
|
+
description=(
|
|
164
|
+
"Classifier self-assessed confidence 0.0-1.0. Use <0.5 for genuinely "
|
|
165
|
+
"ambiguous sessions."
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
SESSION_CLASSIFICATION_SCHEMA: dict = _bedrock_schema(SessionClassification)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class MessageTrajectory(BaseModel):
|
|
174
|
+
"""Per-message sentiment + transition-filler flag.
|
|
175
|
+
|
|
176
|
+
Applied only to messages that pass the cheap regex heuristic pre-filter.
|
|
177
|
+
Classification is on the message's standalone polarity — the model
|
|
178
|
+
sees just this one message, with no surrounding turns. The legacy
|
|
179
|
+
field name ``sentiment_delta`` is preserved for parquet compatibility,
|
|
180
|
+
but the semantics are absolute polarity, not "delta vs prior".
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
model_config = ConfigDict(extra="forbid")
|
|
184
|
+
|
|
185
|
+
sentiment_delta: Literal["positive", "neutral", "negative"] = Field(
|
|
186
|
+
...,
|
|
187
|
+
description=(
|
|
188
|
+
"Standalone emotional polarity of this single message — judged "
|
|
189
|
+
"from the message text alone, no surrounding context. "
|
|
190
|
+
"'positive': excitement, approval, momentum, explicit thanks. "
|
|
191
|
+
"'neutral': factual, procedural, no affect (this is the "
|
|
192
|
+
"majority class for a coding session — use it aggressively). "
|
|
193
|
+
"'negative': frustration, pushback, blocked, explicit "
|
|
194
|
+
"correction. "
|
|
195
|
+
"When the cue is mild or ambiguous, prefer 'neutral'."
|
|
196
|
+
),
|
|
197
|
+
)
|
|
198
|
+
is_transition: bool = Field(
|
|
199
|
+
...,
|
|
200
|
+
description=(
|
|
201
|
+
"True if this message is pure transition/filler (e.g. 'Ok now let me check that', "
|
|
202
|
+
"'Running the tests', 'Clean.'). Use True for acknowledgement-only messages that "
|
|
203
|
+
"don't carry substantive content."
|
|
204
|
+
),
|
|
205
|
+
)
|
|
206
|
+
confidence: float = Field(
|
|
207
|
+
...,
|
|
208
|
+
ge=0.0,
|
|
209
|
+
le=1.0,
|
|
210
|
+
description="Classifier self-confidence 0.0-1.0.",
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
MESSAGE_TRAJECTORY_SCHEMA: dict = _bedrock_schema(MessageTrajectory)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class Conflict(BaseModel):
|
|
218
|
+
"""A single stance conflict within a session."""
|
|
219
|
+
|
|
220
|
+
model_config = ConfigDict(extra="forbid")
|
|
221
|
+
|
|
222
|
+
stance_a: str = Field(
|
|
223
|
+
...,
|
|
224
|
+
min_length=1,
|
|
225
|
+
max_length=280,
|
|
226
|
+
description="One-sentence summary of the first stance.",
|
|
227
|
+
)
|
|
228
|
+
stance_b: str = Field(
|
|
229
|
+
...,
|
|
230
|
+
min_length=1,
|
|
231
|
+
max_length=280,
|
|
232
|
+
description="One-sentence summary of the conflicting stance.",
|
|
233
|
+
)
|
|
234
|
+
resolution: Literal["resolved", "unresolved", "abandoned"] = Field(
|
|
235
|
+
...,
|
|
236
|
+
description=(
|
|
237
|
+
"How the conflict ended. "
|
|
238
|
+
"'resolved': the session converged on one stance. "
|
|
239
|
+
"'unresolved': both stances were still live at end of session. "
|
|
240
|
+
"'abandoned': the topic was dropped without resolution."
|
|
241
|
+
),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class SessionConflicts(BaseModel):
|
|
246
|
+
"""All stance conflicts detected in a session. Empty list if none found."""
|
|
247
|
+
|
|
248
|
+
model_config = ConfigDict(extra="forbid")
|
|
249
|
+
|
|
250
|
+
conflicts: list[Conflict] = Field(
|
|
251
|
+
default_factory=list,
|
|
252
|
+
description=(
|
|
253
|
+
"Zero or more stance conflicts. A conflict is a pair of mutually-exclusive "
|
|
254
|
+
"positions on the same question that surface during the session. Report "
|
|
255
|
+
"only substantive technical or strategic conflicts -- skip trivial style "
|
|
256
|
+
"disagreements."
|
|
257
|
+
),
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
SESSION_CONFLICTS_SCHEMA: dict = _bedrock_schema(SessionConflicts)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class UserFrictionSignal(BaseModel):
|
|
265
|
+
"""Classify a single short user message for friction signals.
|
|
266
|
+
|
|
267
|
+
A "friction signal" is anything in the user's utterance that implies the
|
|
268
|
+
agent's last turn fell short of expectations: an impatient status ping,
|
|
269
|
+
a one-word question pointing at a missed artifact (``screenshot?``,
|
|
270
|
+
``tests?``), confusion about what happened, a hard interruption, a
|
|
271
|
+
correction, or open frustration.
|
|
272
|
+
|
|
273
|
+
Applied only to user-role messages below
|
|
274
|
+
:class:`Settings.friction_max_chars` (default 300). Long user messages
|
|
275
|
+
are almost always genuine task turns rather than interrupt/confusion
|
|
276
|
+
signals -- the filter keeps Bedrock cost linear in the interesting slice.
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
model_config = ConfigDict(extra="forbid")
|
|
280
|
+
|
|
281
|
+
label: Literal[
|
|
282
|
+
"status_ping",
|
|
283
|
+
"unmet_expectation",
|
|
284
|
+
"confusion",
|
|
285
|
+
"interruption",
|
|
286
|
+
"correction",
|
|
287
|
+
"frustration",
|
|
288
|
+
"none",
|
|
289
|
+
] = Field(
|
|
290
|
+
...,
|
|
291
|
+
description=(
|
|
292
|
+
"Dominant friction category for this single user message. "
|
|
293
|
+
"'status_ping': the user asks about progress/ETA ('how's it going?', "
|
|
294
|
+
"'any update?', 'status?', 'where are we?'). "
|
|
295
|
+
"'unmet_expectation': a one- or two-word question that points at "
|
|
296
|
+
"something the agent should have done proactively but didn't "
|
|
297
|
+
"('screenshot?', 'tests?', 'link?', 'diff?'). "
|
|
298
|
+
"'confusion': the user signals they don't understand the output or "
|
|
299
|
+
"state ('what does that mean?', 'why did you do X?', 'I don't get it'). "
|
|
300
|
+
"'interruption': the user cuts the agent off or redirects mid-task "
|
|
301
|
+
"('wait', 'stop', 'hold on', 'actually...', 'before you do that'). "
|
|
302
|
+
"'correction': the user tells the agent its last action/answer was "
|
|
303
|
+
"wrong ('no, not that', 'that's wrong', 'nope', 'try again'). "
|
|
304
|
+
"'frustration': terse annoyance or sarcasm ('ugh', 'seriously?', "
|
|
305
|
+
"'are you kidding', 'really?'). "
|
|
306
|
+
"'none': ordinary task turn with no friction signal -- a substantive "
|
|
307
|
+
"instruction, a plain question, an acknowledgement. USE THIS FOR "
|
|
308
|
+
"THE MAJORITY OF MESSAGES."
|
|
309
|
+
),
|
|
310
|
+
)
|
|
311
|
+
rationale: str = Field(
|
|
312
|
+
...,
|
|
313
|
+
min_length=1,
|
|
314
|
+
max_length=200,
|
|
315
|
+
description=(
|
|
316
|
+
"One short sentence (<=200 chars) naming the specific phrase or "
|
|
317
|
+
"structural cue that triggered the label. Use an empty-ish "
|
|
318
|
+
"placeholder like 'ordinary instruction' when label='none'."
|
|
319
|
+
),
|
|
320
|
+
)
|
|
321
|
+
confidence: float = Field(
|
|
322
|
+
...,
|
|
323
|
+
ge=0.0,
|
|
324
|
+
le=1.0,
|
|
325
|
+
description=(
|
|
326
|
+
"Classifier self-confidence 0.0-1.0. Use <0.5 when the message is "
|
|
327
|
+
"genuinely ambiguous between 'none' and a friction label."
|
|
328
|
+
),
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
USER_FRICTION_SCHEMA: dict = _bedrock_schema(UserFrictionSignal)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class Correction(BaseModel):
|
|
336
|
+
"""One spot in the bound transcript where the human redirected the agent.
|
|
337
|
+
|
|
338
|
+
A correction is a substantive course change: the agent did one thing,
|
|
339
|
+
the human said "no, do this instead" (or equivalent), and the agent
|
|
340
|
+
pivoted. Style nits and acknowledgements don't qualify -- the
|
|
341
|
+
downstream PR review sheet uses these to surface where the agent
|
|
342
|
+
went off-rails, not where the human polished a phrase.
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
model_config = ConfigDict(extra="forbid")
|
|
346
|
+
|
|
347
|
+
what_agent_did: str = Field(
|
|
348
|
+
...,
|
|
349
|
+
min_length=5,
|
|
350
|
+
max_length=300,
|
|
351
|
+
description=(
|
|
352
|
+
"The agent's action that was corrected, in one short clause. "
|
|
353
|
+
'Example: "Started rewriting the auth middleware from scratch."'
|
|
354
|
+
),
|
|
355
|
+
)
|
|
356
|
+
correction: str = Field(
|
|
357
|
+
...,
|
|
358
|
+
min_length=5,
|
|
359
|
+
max_length=300,
|
|
360
|
+
description=(
|
|
361
|
+
"What the human said to redirect, paraphrased to one short clause. "
|
|
362
|
+
'Example: "Asked to keep the existing middleware and only update the token rotator."'
|
|
363
|
+
),
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class PRReviewSheet(BaseModel):
|
|
368
|
+
"""Compressed PR review derived from the bound transcript.
|
|
369
|
+
|
|
370
|
+
Six-field schema designed to fit a 1K-token review-sheet budget when
|
|
371
|
+
rendered as Markdown. Field descriptions carry the synthesis rules so
|
|
372
|
+
downstream prompts only need to frame the task; the model's job is to
|
|
373
|
+
populate the schema from the bound JSONL transcript.
|
|
374
|
+
"""
|
|
375
|
+
|
|
376
|
+
model_config = ConfigDict(extra="forbid")
|
|
377
|
+
|
|
378
|
+
human_intent: str = Field(
|
|
379
|
+
...,
|
|
380
|
+
min_length=10,
|
|
381
|
+
max_length=600,
|
|
382
|
+
description=(
|
|
383
|
+
"What the human asked for in 1-3 sentences. Synthesized from the "
|
|
384
|
+
"initial user-role messages and any explicit goal restatements. "
|
|
385
|
+
"Present tense, paraphrased, not a literal quote."
|
|
386
|
+
),
|
|
387
|
+
)
|
|
388
|
+
agent_exploration: list[str] = Field(
|
|
389
|
+
...,
|
|
390
|
+
min_length=1,
|
|
391
|
+
max_length=8,
|
|
392
|
+
description=(
|
|
393
|
+
"Concrete subjects the agent explored, as 3-8 short bullets. "
|
|
394
|
+
"Each bullet is a noun phrase or short clause naming what was "
|
|
395
|
+
"investigated. Drawn from tool calls, search queries, and file "
|
|
396
|
+
"reads -- not from the agent's own narration."
|
|
397
|
+
),
|
|
398
|
+
)
|
|
399
|
+
corrections: list[Correction] = Field(
|
|
400
|
+
...,
|
|
401
|
+
max_length=5,
|
|
402
|
+
description=(
|
|
403
|
+
"Where the agent got corrected by the human, redirected, or ran "
|
|
404
|
+
"into stance conflicts. Up to 5 entries; empty list if none. "
|
|
405
|
+
"Skip surface-level acknowledgements; only substantive redirects."
|
|
406
|
+
),
|
|
407
|
+
)
|
|
408
|
+
tools_used: list[str] = Field(
|
|
409
|
+
...,
|
|
410
|
+
max_length=20,
|
|
411
|
+
description=(
|
|
412
|
+
"Tool names the agent successfully invoked (canonical names: "
|
|
413
|
+
"Read, Edit, Write, Bash, Grep, Glob, etc.). Deduplicated, "
|
|
414
|
+
"ordered by first use."
|
|
415
|
+
),
|
|
416
|
+
)
|
|
417
|
+
tools_refused: list[str] = Field(
|
|
418
|
+
...,
|
|
419
|
+
max_length=10,
|
|
420
|
+
description=(
|
|
421
|
+
"Tool calls the agent declined or that were blocked by hooks / "
|
|
422
|
+
"permissions. Empty list if none. Format each entry as "
|
|
423
|
+
"'ToolName: brief reason' (e.g. 'Bash: blocked by allowlist')."
|
|
424
|
+
),
|
|
425
|
+
)
|
|
426
|
+
diff_rationale: str = Field(
|
|
427
|
+
...,
|
|
428
|
+
min_length=20,
|
|
429
|
+
max_length=800,
|
|
430
|
+
description=(
|
|
431
|
+
"The 'why' behind the merged diff in 2-4 sentences. Names the "
|
|
432
|
+
"specific files or modules touched and the user-facing change. "
|
|
433
|
+
"Avoids restating the diff line-by-line; focus on the rationale."
|
|
434
|
+
),
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
PR_REVIEW_SHEET_SCHEMA: dict = _bedrock_schema(PRReviewSheet)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
__all__ = [
|
|
442
|
+
"MESSAGE_TRAJECTORY_SCHEMA",
|
|
443
|
+
"PR_REVIEW_SHEET_SCHEMA",
|
|
444
|
+
"SESSION_CLASSIFICATION_SCHEMA",
|
|
445
|
+
"SESSION_CONFLICTS_SCHEMA",
|
|
446
|
+
"USER_FRICTION_SCHEMA",
|
|
447
|
+
"Conflict",
|
|
448
|
+
"Correction",
|
|
449
|
+
"MessageTrajectory",
|
|
450
|
+
"PRReviewSheet",
|
|
451
|
+
"SessionClassification",
|
|
452
|
+
"SessionConflicts",
|
|
453
|
+
"UserFrictionSignal",
|
|
454
|
+
]
|