prettyplay 0.0.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.
Files changed (75) hide show
  1. prettyplay/.usages/lifecycle.md +117 -0
  2. prettyplay/.usages/steps.md +169 -0
  3. prettyplay/__init__.py +18 -0
  4. prettyplay/cache/.usages/addressing.md +44 -0
  5. prettyplay/cache/.usages/budgets.md +51 -0
  6. prettyplay/cache/.usages/storage.md +32 -0
  7. prettyplay/cache/__init__.py +8 -0
  8. prettyplay/cache/budgets.py +93 -0
  9. prettyplay/cache/models.py +55 -0
  10. prettyplay/cache/store.py +276 -0
  11. prettyplay/cache/text.py +36 -0
  12. prettyplay/config/.usages/configuration.md +246 -0
  13. prettyplay/config/__init__.py +6 -0
  14. prettyplay/config/loader.py +336 -0
  15. prettyplay/config/models.py +288 -0
  16. prettyplay/driver/.usages/error_kinds.md +26 -0
  17. prettyplay/driver/.usages/plumbing.md +52 -0
  18. prettyplay/driver/__init__.py +11 -0
  19. prettyplay/driver/errors.py +76 -0
  20. prettyplay/driver/page.py +175 -0
  21. prettyplay/driver/session.py +330 -0
  22. prettyplay/engine/.usages/generation.md +157 -0
  23. prettyplay/engine/.usages/healing.md +61 -0
  24. prettyplay/engine/__init__.py +19 -0
  25. prettyplay/engine/attempts.py +80 -0
  26. prettyplay/engine/classification.py +70 -0
  27. prettyplay/engine/compliance.py +125 -0
  28. prettyplay/engine/execution.py +48 -0
  29. prettyplay/engine/generator.py +1110 -0
  30. prettyplay/engine/groups/.usages/recovery.md +71 -0
  31. prettyplay/engine/groups/__init__.py +7 -0
  32. prettyplay/engine/groups/diagnosis.py +128 -0
  33. prettyplay/engine/groups/outcome.py +55 -0
  34. prettyplay/engine/groups/recovery.py +491 -0
  35. prettyplay/engine/healer.py +149 -0
  36. prettyplay/engine/polling/.usages/settle.md +55 -0
  37. prettyplay/engine/polling/__init__.py +6 -0
  38. prettyplay/engine/polling/settle.py +98 -0
  39. prettyplay/engine/polling/window.py +72 -0
  40. prettyplay/engine/renderer/.usages/memory.md +24 -0
  41. prettyplay/engine/renderer/.usages/rendering.md +44 -0
  42. prettyplay/engine/renderer/.usages/validation.md +20 -0
  43. prettyplay/engine/renderer/__init__.py +7 -0
  44. prettyplay/engine/renderer/memory.py +37 -0
  45. prettyplay/engine/renderer/render.py +184 -0
  46. prettyplay/engine/renderer/validation.py +76 -0
  47. prettyplay/engine/steering/.usages/steering.md +95 -0
  48. prettyplay/engine/steering/__init__.py +5 -0
  49. prettyplay/engine/steering/steering.py +765 -0
  50. prettyplay/engine/text.py +41 -0
  51. prettyplay/executor.py +654 -0
  52. prettyplay/failures/.usages/taxonomy.md +131 -0
  53. prettyplay/failures/__init__.py +25 -0
  54. prettyplay/failures/errors.py +359 -0
  55. prettyplay/groups.py +276 -0
  56. prettyplay/llm/.usages/classification.md +31 -0
  57. prettyplay/llm/.usages/providers.md +127 -0
  58. prettyplay/llm/.usages/transport.md +81 -0
  59. prettyplay/llm/__init__.py +38 -0
  60. prettyplay/llm/_request.py +823 -0
  61. prettyplay/llm/anthropic_provider.py +468 -0
  62. prettyplay/llm/models.py +273 -0
  63. prettyplay/llm/openai_provider.py +444 -0
  64. prettyplay/llm/provider.py +279 -0
  65. prettyplay/reporting/.usages/hooks.md +61 -0
  66. prettyplay/reporting/__init__.py +6 -0
  67. prettyplay/reporting/hooks.py +47 -0
  68. prettyplay/reporting/reporter.py +70 -0
  69. prettyplay/runtime.py +81 -0
  70. prettyplay/scenario.py +469 -0
  71. prettyplay-0.0.2.dist-info/METADATA +515 -0
  72. prettyplay-0.0.2.dist-info/RECORD +75 -0
  73. prettyplay-0.0.2.dist-info/WHEEL +5 -0
  74. prettyplay-0.0.2.dist-info/licenses/LICENSE +28 -0
  75. prettyplay-0.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,71 @@
1
+ # Group recovery
2
+
3
+ Domain: the diagnosis-driven recovery of step groups. Audience: library internals and engineers
4
+ reasoning about recovered group runs.
5
+
6
+ ## When recovery runs
7
+
8
+ Every classification point of a group step on a non-strict run routes here instead of the per-step
9
+ classification-and-heal: a failed check of a generation candidate, a failed cached replay and the
10
+ generation-budget exhaustion. Inside a group there is no isolated per-step healing. Strict mode
11
+ never runs recovery — the classification-only path stays.
12
+
13
+ ## The cycle
14
+
15
+ ```python
16
+ from prettyplay.engine.renderer import PreparedStep
17
+
18
+ healed = recovery.recover(
19
+ group_prompt="accept cookies, fill and submit the order form",
20
+ traces=group_traces, # GroupStepOutcome per group step, execution order
21
+ prepared=PreparedStep(instruction="the status shows order confirmed"), # the render product of the failed step
22
+ step_type="assertion",
23
+ previous_steps=scenario, # the typed scenario records of the test, execution order
24
+ identity=identity,
25
+ attempt_history=history,
26
+ page=page,
27
+ window=window,
28
+ memory=memory,
29
+ )
30
+ ```
31
+
32
+ 1. One group-level diagnosis request — the classification model and instructions, the group
33
+ prompt, the step traces with outcomes and URL transitions, the current page state, the failed
34
+ step's attempt history. Labels: `recoverable | product_defect | incurable`; a garbage answer
35
+ degrades to incurable with the raw answer logged.
36
+ 2. `product_defect` fails loudly; `incurable` fails terminally; a root named outside the group
37
+ ends in the honest terminal failure naming that step.
38
+ 3. `recoverable`: the row runs from the earliest affected group step through the failed step —
39
+ each step is
40
+ re-rendered against the current context (its template sentence, its recorded inputs, the current memory),
41
+ regenerates as its own unit (the diagnosis recommendation + the group framing ride the request), re-executes
42
+ immediately on the current page, validates its returned result, passes the two-dimension compliance gate,
43
+ publishes its captures to the test memory and writes back to the cache per step.
44
+ 4. A repeat failure re-enters a fresh diagnosis and a new cycle while cycles remain; every new
45
+ cycle grants each row step a fresh full healing counter; the number of cycles per group is
46
+ capped by `healing_attempts` — never infinite.
47
+
48
+ ## Reporting
49
+
50
+ Diagnosis and regeneration requests use the provider's bounded transport retries. Resending
51
+ an identical request consumes no additional group cycle or per-step healing attempt. A final
52
+ `LLMUnavailableError` propagates out of recovery; it never opens another recovery cycle.
53
+
54
+ The block itself reports through the four group lifecycle hook events — `on_group_started`,
55
+ then `on_group_passed` (a recovered group reports passed) or `on_group_failed`, closed by
56
+ `on_group_finished`. `on_healing_started` / `on_healed` fire per recovered step; the diagnosis
57
+ itself and the row composition stay log-only records.
58
+
59
+ ## Memory and re-render
60
+
61
+ - Row steps re-render through `render_step` — recovery never reuses a stale instruction: the current memory and
62
+ the step's recorded inputs produce the fresh one
63
+ - Recovery keeps local copies of previous scenario records and group traces. After each accepted row step, its
64
+ newly prepared instruction replaces that occurrence in the local views used by later row requests and diagnoses;
65
+ the test's stored records and appended traces remain immutable
66
+ - A failed row regeneration becomes the active failure for the next diagnosis: STEP and HISTORY describe that row
67
+ step and its latest attempt; a complete row still returns the healed code of the originally failed step
68
+ - Every accepted row step publishes its captures immediately; a later failure in the row never rolls back accepted
69
+ values — steps left behind keep their last accepted observations
70
+ - Traces carry the instruction (what the model saw and what the row matcher quotes) and the original `vars` of
71
+ each step's call
@@ -0,0 +1,7 @@
1
+ """Facade of the prettyplay.engine.groups cell: the diagnosis-driven recovery of step groups."""
2
+
3
+ from .diagnosis import classify_group_failure
4
+ from .outcome import GroupStepOutcome
5
+ from .recovery import GroupRecovery
6
+
7
+ __all__ = ["GroupRecovery", "GroupStepOutcome", "classify_group_failure"]
@@ -0,0 +1,128 @@
1
+ """The single group diagnosis request: the whole interaction in view, one verdict."""
2
+
3
+ import logging
4
+
5
+ from ...config import Config
6
+ from ...driver import PageFacade
7
+ from ...llm import GroupFailureClassification, LLMProvider, ScenarioStep
8
+ from ..attempts import StepAttempt
9
+ from .outcome import GroupStepOutcome
10
+
11
+ logger = logging.getLogger("prettyplay")
12
+
13
+ #: System prompt of every group diagnosis request; applied verbatim by the provider.
14
+ #: Frozen mirror of ``.goga/usages/prompts/group_diagnosis.md`` (the section after the ``---``
15
+ #: separator) — the single source of the prompt; the constant changes only together with the file.
16
+ GROUP_DIAGNOSIS_PROMPT = """\
17
+ You diagnose a failure of one step inside a group of steps that form one coherent interaction with
18
+ a shared goal.
19
+
20
+ Input you receive:
21
+ - GROUP PROMPT: the shared goal of the group, verbatim
22
+ - PREVIOUS STEPS: the prepared instructions of the prior scenario of the test, in execution order —
23
+ the group entries marked
24
+ - GROUP STEPS: every step of the group in execution order — each with its recorded prepared
25
+ instruction, its outcome (passed or failed) and its URL before -> after transition
26
+ - STEP: the failed step's prepared instruction
27
+ - HISTORY: the verbatim record of every attempt of the failed step so far
28
+ - PAGE SNAPSHOT: the accessibility snapshot of the current page
29
+ - SCREENSHOT: an image of the page, when attached
30
+ - USER INSTRUCTIONS: the project's binding classification guidance, when configured — follow it;
31
+ it never overrides the fixed answer format below
32
+
33
+ Answer with exactly one JSON object of the form:
34
+ {"category": "recoverable | product_defect | incurable",
35
+ "root_cause": "<one short sentence>",
36
+ "earliest_step": "<the verbatim sentence of the earliest affected step>",
37
+ "recommendation": "<one short sentence what should be regenerated or done>"}
38
+
39
+ Category calibration:
40
+ - recoverable — the failure stems from one or more earlier steps of the group (a step that did not
41
+ land what its sentence says) and regenerating the affected steps of the group can fix the run
42
+ - product_defect — the application is genuinely broken; regenerating steps cannot and must not
43
+ turn this green
44
+ - incurable — the root cannot be reached from inside the group (it lives in an earlier step of the
45
+ test outside the group), or regeneration cannot help
46
+
47
+ Rules:
48
+ - earliest_step must quote a visible instruction verbatim: a group step's instruction when the
49
+ category is recoverable; when the root lives outside the group, quote that outside step's
50
+ instruction from PREVIOUS STEPS verbatim and answer incurable; raw template sentences are not
51
+ request content
52
+ - Output only the JSON object, no other text"""
53
+
54
+
55
+ def classify_group_failure( # noqa: PLR0913, PLR0917 — the parameter list is fixed by the recovery contract
56
+ config: Config,
57
+ provider: LLMProvider,
58
+ group_prompt: str,
59
+ traces: list[GroupStepOutcome],
60
+ previous_steps: list[ScenarioStep],
61
+ step_text: str,
62
+ step_type: str, # noqa: ARG001 — the contract parameter; the kind rides the traces, not the port call
63
+ attempt_history: list[StepAttempt],
64
+ page: PageFacade,
65
+ ) -> GroupFailureClassification:
66
+ """Diagnose a failed group step with the whole interaction in view.
67
+
68
+ Args:
69
+ config: project settings; ``send_screenshots`` attaches the page
70
+ image and ``classification_prompt`` supplies the user
71
+ instructions of the request.
72
+ provider: the LLM port implementation diagnosing the failure.
73
+ group_prompt: the group prompt of the diagnosed group, verbatim.
74
+ traces: the group's step traces in execution order — every record
75
+ renders its recorded prepared instruction into the GROUP STEPS
76
+ block; the raw sentences stay local.
77
+ previous_steps: the typed scenario records of the test, in
78
+ execution order — the provider renders their prepared
79
+ instructions as the PREVIOUS STEPS block, the group entries
80
+ marked; the basis of the outside-group root diagnosis.
81
+ step_text: the prepared instruction of the failed step, verbatim —
82
+ never the raw template sentence.
83
+ step_type: the step kind: action or assertion.
84
+ attempt_history: the per-step attempt history of the failed step,
85
+ grown to the failure — every record rendered verbatim into the
86
+ HISTORY block.
87
+ page: the page facade of the current test.
88
+
89
+ Returns:
90
+ The diagnosis verdict — a garbage answer arrives as the
91
+ conservative incurable, never as a granted regeneration.
92
+
93
+ Raises:
94
+ LLMUnavailableError: the provider service failed after the bounded
95
+ transport retries; an explicit infrastructure failure — the
96
+ calling recovery decides.
97
+ """
98
+ snapshot = page.aria_snapshot()
99
+ screenshot = page.screenshot() if config.send_screenshots else None
100
+
101
+ verdict = provider.classify_group_failure(
102
+ prompt=GROUP_DIAGNOSIS_PROMPT,
103
+ user_instructions=config.classification_prompt,
104
+ group_prompt=group_prompt,
105
+ group_steps=[trace.render() for trace in traces],
106
+ previous_steps=previous_steps,
107
+ step_text=step_text,
108
+ attempt_history=[record.render() for record in attempt_history],
109
+ snapshot=snapshot,
110
+ screenshot=screenshot,
111
+ )
112
+
113
+ if verdict.degraded:
114
+ logger.warning(
115
+ "group_diagnosis_degraded",
116
+ extra={"group": group_prompt, "answer": verdict.root_cause},
117
+ )
118
+
119
+ logger.info(
120
+ "group_diagnosed",
121
+ extra={
122
+ "group": group_prompt,
123
+ "category": verdict.category,
124
+ "earliest": verdict.earliest_step,
125
+ },
126
+ )
127
+
128
+ return verdict
@@ -0,0 +1,55 @@
1
+ """The verbatim trace record of a group step: the unit the GROUP STEPS block renders."""
2
+
3
+ from pydantic import BaseModel, ConfigDict
4
+
5
+ from ...cache import StepIdentity
6
+
7
+
8
+ class GroupStepOutcome(BaseModel):
9
+ """One verbatim trace record of a group step's execution — never rewritten or truncated.
10
+
11
+ Attributes:
12
+ sentence: the raw sentence of the group step (the template source),
13
+ verbatim — the re-render source of the row mechanics, never
14
+ rendered into requests.
15
+ instruction: the prepared instruction recorded at the step's
16
+ execution — what the GROUP STEPS block renders and the matcher
17
+ matches against; equals ``sentence`` for a non-template step.
18
+ step_type: the step kind: action or assertion.
19
+ tries: the declared retry count of the step; None — the step is
20
+ governed by the global polling settings.
21
+ delay: the declared start pause of the step in seconds; None — no
22
+ pause.
23
+ vars: the call-local input bindings of the step's original call —
24
+ the re-render input of the row mechanics.
25
+ outcome: the execution outcome: passed or failed.
26
+ url_before: the page URL read immediately before the step's
27
+ execution.
28
+ url_after: the page URL read immediately after the step's execution.
29
+ identity: the cache address of the step — recorded by the executor
30
+ when the trace is appended; the addressing metadata of the row
31
+ mechanics, never rendered into the GROUP STEPS block.
32
+ """
33
+
34
+ model_config = ConfigDict(kw_only=True, frozen=True)
35
+
36
+ sentence: str = ""
37
+ instruction: str = ""
38
+ step_type: str = ""
39
+ tries: int | None = None
40
+ delay: float | None = None
41
+ vars: dict[str, str] = {}
42
+ outcome: str = ""
43
+ url_before: str = ""
44
+ url_after: str = ""
45
+ identity: StepIdentity
46
+
47
+ def render(self) -> str:
48
+ """Render the complete verbatim trace — the unit the GROUP STEPS block renders.
49
+
50
+ Returns:
51
+ The multi-line trace text — the pinned three-line format:
52
+ instruction, outcome label, URL pair; the sentence and the
53
+ identity never render.
54
+ """
55
+ return f"{self.instruction}\noutcome: {self.outcome}\nurl: {self.url_before} -> {self.url_after}"