samrat-rm commited on
Commit
45c94ac
·
verified ·
1 Parent(s): 8811588

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +7 -0
  2. CLAUDE.md +549 -0
  3. Dockerfile +82 -0
  4. LICENSE +21 -0
  5. README.md +99 -4
  6. __init__.py +11 -0
  7. advanced_readme.md +210 -0
  8. client.py +68 -0
  9. environment/__init__.py +5 -0
  10. environment/generator.py +382 -0
  11. environment/grader.py +143 -0
  12. environment/packages.py +23 -0
  13. environment/parser.py +51 -0
  14. environment/stage_runner.py +414 -0
  15. inference.py +339 -0
  16. models.py +31 -0
  17. openenv.yaml +61 -0
  18. openenv_CI_CD_Doctor.egg-info/PKG-INFO +11 -0
  19. openenv_CI_CD_Doctor.egg-info/SOURCES.txt +22 -0
  20. openenv_CI_CD_Doctor.egg-info/dependency_links.txt +1 -0
  21. openenv_CI_CD_Doctor.egg-info/entry_points.txt +2 -0
  22. openenv_CI_CD_Doctor.egg-info/requires.txt +5 -0
  23. openenv_CI_CD_Doctor.egg-info/top_level.txt +1 -0
  24. pyproject.toml +45 -0
  25. requirements.txt +6 -0
  26. server/__init__.py +5 -0
  27. server/app.py +95 -0
  28. server/app_2.py +101 -0
  29. server/environment.py +199 -0
  30. uv.lock +0 -0
  31. venv/bin/Activate.ps1 +247 -0
  32. venv/bin/activate +71 -0
  33. venv/bin/activate.csh +27 -0
  34. venv/bin/activate.fish +69 -0
  35. venv/bin/pip +8 -0
  36. venv/bin/pip3 +8 -0
  37. venv/bin/pip3.12 +8 -0
  38. venv/bin/python +3 -0
  39. venv/bin/python3 +3 -0
  40. venv/bin/python3.12 +3 -0
  41. venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/AUTHORS.txt +799 -0
  42. venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/INSTALLER +1 -0
  43. venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/LICENSE.txt +20 -0
  44. venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/METADATA +90 -0
  45. venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/RECORD +853 -0
  46. venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/REQUESTED +0 -0
  47. venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/WHEEL +5 -0
  48. venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/entry_points.txt +3 -0
  49. venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/top_level.txt +1 -0
  50. venv/lib/python3.12/site-packages/pip/__init__.py +13 -0
.gitattributes CHANGED
@@ -33,3 +33,10 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ venv/bin/python filter=lfs diff=lfs merge=lfs -text
37
+ venv/bin/python3 filter=lfs diff=lfs merge=lfs -text
38
+ venv/bin/python3.12 filter=lfs diff=lfs merge=lfs -text
39
+ venv/lib/python3.12/site-packages/pip/_vendor/distlib/t64-arm.exe filter=lfs diff=lfs merge=lfs -text
40
+ venv/lib/python3.12/site-packages/pip/_vendor/distlib/t64.exe filter=lfs diff=lfs merge=lfs -text
41
+ venv/lib/python3.12/site-packages/pip/_vendor/distlib/w64-arm.exe filter=lfs diff=lfs merge=lfs -text
42
+ venv/lib/python3.12/site-packages/pip/_vendor/distlib/w64.exe filter=lfs diff=lfs merge=lfs -text
CLAUDE.md ADDED
@@ -0,0 +1,549 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CLAUDE.md — CI/CD Doctor (Pipeline Debugging RL Environment)
2
+
3
+ ## What This Project Is
4
+
5
+ An **OpenEnv reinforcement learning environment** for an AI competition.
6
+ The agent plays the role of a DevOps engineer debugging a broken CI/CD pipeline.
7
+ It issues free-form shell-like commands, reads logs, edits files, and re-runs
8
+ pipeline stages until the pipeline passes.
9
+
10
+ This is NOT a game. It simulates real DevOps work.
11
+
12
+ ---
13
+
14
+ ## Current Goal: Complete Phase 1
15
+
16
+ **Phase 1 = Core environment, easy task only, no server yet.**
17
+
18
+ Deliverable: a working Python module where you can call `reset()`, then `step()`,
19
+ then `state()` in a loop and the environment responds correctly.
20
+
21
+ ---
22
+
23
+ ## Project Structure (target layout)
24
+
25
+ ```
26
+ cicd-doctor/
27
+ ├── cicd_doctor/
28
+ │ ├── __init__.py
29
+ │ ├── models.py ← Pydantic data models
30
+ │ ├── generator.py ← procedural scenario generator
31
+ │ ├── stage_runner.py ← simulated pipeline stages
32
+ │ ├── parser.py ← free-form command parser
33
+ │ └── environment.py ← core RL environment class
34
+ └── tests/
35
+ └── test_phase1.py ← smoke test for the loop
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Architecture Mental Model
41
+
42
+ ```
43
+ Agent issues a command string
44
+
45
+
46
+ parser.py ← parses "cat requirements.txt" → structured ParsedCommand
47
+
48
+
49
+ environment.py ← looks up command type, mutates in-memory filesystem
50
+
51
+ ├──► stage_runner.py ← called on "pipeline run" or "pipeline logs"
52
+ │ returns fake-but-realistic logs + exit_code
53
+
54
+ └──► returns PipelineObservation to caller
55
+ ```
56
+
57
+ The "filesystem" is just a Python dict in memory:
58
+ ```python
59
+ filesystem = {
60
+ "requirements.txt": "flask\nnumpy\n",
61
+ "pipeline.yaml": "stages:\n - install\n",
62
+ "logs/install.log": ""
63
+ }
64
+ ```
65
+ No real files. No real pip. No real Docker. Pure deterministic simulation.
66
+
67
+ ---
68
+
69
+ ## File-by-File Specification
70
+
71
+ ### `models.py`
72
+
73
+ Three Pydantic models. Keep them minimal for Phase 1.
74
+
75
+ ```python
76
+ from pydantic import BaseModel
77
+ from typing import Optional, Dict, Any
78
+
79
+ class PipelineAction(BaseModel):
80
+ command: str # raw string the agent types, e.g. "cat requirements.txt"
81
+
82
+ class PipelineObservation(BaseModel):
83
+ stdout: str # what the agent sees (file contents, logs, error msg)
84
+ exit_code: int # 0 = success, 1 = error
85
+ pipeline_status: str # "not_run" | "running" | "passed" | "failed"
86
+ steps_remaining: int
87
+ done: bool = False
88
+ reward: float = 0.0
89
+
90
+ class PipelineState(BaseModel):
91
+ episode_id: str
92
+ task: str # "easy" | "medium" | "hard"
93
+ filesystem: Dict[str, str] # in-memory files
94
+ pipeline_status: str
95
+ step_count: int
96
+ done: bool
97
+ total_reward: float
98
+ answer_key: Dict[str, Any] # never sent to agent, used by grader
99
+ ```
100
+
101
+ ---
102
+
103
+ ### `generator.py`
104
+
105
+ Generates the broken scenario the agent must fix.
106
+
107
+ **Easy scenario (Phase 1 only):**
108
+ One random package is missing from `requirements.txt`.
109
+
110
+ ```python
111
+ import random
112
+
113
+ REQUIRED_PACKAGES = ["flask", "numpy", "pandas", "requests", "pydantic"]
114
+
115
+ def generate_easy_scenario(seed: int) -> dict:
116
+ """
117
+ Returns a filesystem dict + answer_key.
118
+ The filesystem has requirements.txt missing one required package.
119
+ The answer_key records which package is missing (used by grader).
120
+ """
121
+ rng = random.Random(seed)
122
+ all_packages = REQUIRED_PACKAGES.copy()
123
+ missing = rng.choice(all_packages)
124
+ present = [p for p in all_packages if p != missing]
125
+
126
+ return {
127
+ "filesystem": {
128
+ "requirements.txt": "\n".join(present) + "\n",
129
+ "pipeline.yaml": "stages:\n - install\n",
130
+ "logs/install.log": "",
131
+ "app.py": "import flask\nimport numpy\n# app code here\n"
132
+ },
133
+ "answer_key": {
134
+ "missing_package": missing,
135
+ "correct_file": "requirements.txt"
136
+ }
137
+ }
138
+ ```
139
+
140
+ ---
141
+
142
+ ### `stage_runner.py`
143
+
144
+ Simulates running a pipeline stage. Returns realistic logs + exit code.
145
+ No real pip runs. This is pure logic.
146
+
147
+ ```python
148
+ def run_install_stage(filesystem: dict) -> dict:
149
+ """
150
+ Reads requirements.txt from the filesystem dict.
151
+ If all required packages are present → exit 0, success logs.
152
+ If any required package is missing → exit 1, ModuleNotFoundError logs.
153
+ Returns: {"exit_code": int, "logs": str}
154
+ """
155
+ REQUIRED = {"flask", "numpy", "pandas", "requests", "pydantic"}
156
+ content = filesystem.get("requirements.txt", "")
157
+ installed = {line.strip().lower() for line in content.splitlines() if line.strip()}
158
+ missing = REQUIRED - installed
159
+
160
+ if not missing:
161
+ logs = "Collecting flask\n Downloading flask-3.0.0-py3-none-any.whl\n"
162
+ logs += "Collecting numpy\n Downloading numpy-1.26.0-cp311-cp311-linux_x86_64.whl\n"
163
+ logs += "Successfully installed flask numpy pandas requests pydantic\n"
164
+ return {"exit_code": 0, "logs": logs}
165
+ else:
166
+ pkg = next(iter(missing))
167
+ logs = f"Collecting {pkg}\n"
168
+ logs += f" ERROR: Could not find a version that satisfies the requirement {pkg}\n"
169
+ logs += f"ERROR: No matching distribution found for {pkg}\n"
170
+ return {"exit_code": 1, "logs": logs}
171
+ ```
172
+
173
+ ---
174
+
175
+ ### `parser.py`
176
+
177
+ Converts the agent's raw command string into a structured dict.
178
+ Phase 1 must handle these commands:
179
+
180
+ | Command | Example | What it does |
181
+ |---|---|---|
182
+ | `cat` | `cat requirements.txt` | Read a file |
183
+ | `echo >>` | `echo "numpy" >> requirements.txt` | Append a line to a file |
184
+ | `sed` | `sed -i 's/old/new/' requirements.txt` | Find-replace in a file |
185
+ | `pipeline run` | `pipeline run` | Triggers stage_runner |
186
+ | `pipeline logs` | `pipeline logs install` | Show logs for a stage |
187
+ | `pipeline status` | `pipeline status` | Show current pass/fail status |
188
+
189
+ ```python
190
+ import re
191
+ from dataclasses import dataclass
192
+ from typing import Optional
193
+
194
+ @dataclass
195
+ class ParsedCommand:
196
+ type: str # "cat" | "echo_append" | "sed" | "pipeline_run" | "pipeline_logs" | "pipeline_status" | "unknown"
197
+ filename: Optional[str] = None
198
+ content: Optional[str] = None # for echo >>
199
+ pattern: Optional[str] = None # for sed: old value
200
+ replacement: Optional[str] = None # for sed: new value
201
+ stage: Optional[str] = None # for pipeline logs: which stage
202
+
203
+ def parse_command(command: str) -> ParsedCommand:
204
+ command = command.strip()
205
+
206
+ # cat <filename>
207
+ if command.startswith("cat "):
208
+ return ParsedCommand(type="cat", filename=command[4:].strip())
209
+
210
+ # echo "text" >> filename
211
+ m = re.match(r'echo\s+"([^"]+)"\s*>>\s*(\S+)', command)
212
+ if m:
213
+ return ParsedCommand(type="echo_append", content=m.group(1), filename=m.group(2))
214
+
215
+ # sed -i 's/old/new/' filename
216
+ m = re.match(r"sed\s+-i\s+'s/([^/]+)/([^/]*)/'[\s]+(\S+)", command)
217
+ if m:
218
+ return ParsedCommand(type="sed", pattern=m.group(1), replacement=m.group(2), filename=m.group(3))
219
+
220
+ # pipeline run
221
+ if command == "pipeline run":
222
+ return ParsedCommand(type="pipeline_run")
223
+
224
+ # pipeline logs <stage>
225
+ m = re.match(r"pipeline logs\s+(\S+)", command)
226
+ if m:
227
+ return ParsedCommand(type="pipeline_logs", stage=m.group(1))
228
+
229
+ # pipeline status
230
+ if command == "pipeline status":
231
+ return ParsedCommand(type="pipeline_status")
232
+
233
+ return ParsedCommand(type="unknown")
234
+ ```
235
+
236
+ ---
237
+
238
+ ### `environment.py`
239
+
240
+ The core class. Ties everything together.
241
+
242
+ ```python
243
+ import uuid
244
+ from cicd_doctor.models import PipelineAction, PipelineObservation, PipelineState
245
+ from cicd_doctor.generator import generate_easy_scenario
246
+ from cicd_doctor.stage_runner import run_install_stage
247
+ from cicd_doctor.parser import parse_command
248
+
249
+ MAX_STEPS = 15
250
+
251
+ class PipelineEnvironment:
252
+
253
+ def reset(self, task: str = "easy", seed: int = 42) -> PipelineObservation:
254
+ scenario = generate_easy_scenario(seed)
255
+ self._filesystem = scenario["filesystem"]
256
+ self._answer_key = scenario["answer_key"]
257
+ self._task = task
258
+ self._step_count = 0
259
+ self._done = False
260
+ self._total_reward = 0.0
261
+ self._pipeline_status = "not_run"
262
+ self._episode_id = str(uuid.uuid4())
263
+ self._last_logs = {} # stage_name → log string
264
+
265
+ return PipelineObservation(
266
+ stdout="Pipeline environment ready. The install stage is failing. Investigate and fix it.",
267
+ exit_code=0,
268
+ pipeline_status=self._pipeline_status,
269
+ steps_remaining=MAX_STEPS,
270
+ done=False,
271
+ reward=0.0
272
+ )
273
+
274
+ def step(self, action: PipelineAction) -> PipelineObservation:
275
+ if self._done:
276
+ raise RuntimeError("Episode is done. Call reset() first.")
277
+
278
+ self._step_count += 1
279
+ steps_remaining = MAX_STEPS - self._step_count
280
+ cmd = parse_command(action.command)
281
+ stdout = ""
282
+ exit_code = 0
283
+
284
+ if cmd.type == "cat":
285
+ content = self._filesystem.get(cmd.filename)
286
+ if content is None:
287
+ stdout = f"cat: {cmd.filename}: No such file or directory"
288
+ exit_code = 1
289
+ else:
290
+ stdout = content
291
+
292
+ elif cmd.type == "echo_append":
293
+ if cmd.filename in self._filesystem:
294
+ self._filesystem[cmd.filename] += cmd.content + "\n"
295
+ stdout = "" # echo >> is silent on success
296
+ else:
297
+ stdout = f"bash: {cmd.filename}: No such file or directory"
298
+ exit_code = 1
299
+
300
+ elif cmd.type == "sed":
301
+ if cmd.filename in self._filesystem:
302
+ self._filesystem[cmd.filename] = self._filesystem[cmd.filename].replace(
303
+ cmd.pattern, cmd.replacement
304
+ )
305
+ stdout = ""
306
+ else:
307
+ stdout = f"sed: {cmd.filename}: No such file or directory"
308
+ exit_code = 1
309
+
310
+ elif cmd.type == "pipeline_run":
311
+ result = run_install_stage(self._filesystem)
312
+ self._last_logs["install"] = result["logs"]
313
+ if result["exit_code"] == 0:
314
+ self._pipeline_status = "passed"
315
+ else:
316
+ self._pipeline_status = "failed"
317
+ stdout = result["logs"]
318
+ exit_code = result["exit_code"]
319
+
320
+ elif cmd.type == "pipeline_logs":
321
+ stage = cmd.stage or "install"
322
+ stdout = self._last_logs.get(stage, f"No logs for stage '{stage}' yet. Run pipeline first.")
323
+
324
+ elif cmd.type == "pipeline_status":
325
+ stdout = f"Pipeline status: {self._pipeline_status}"
326
+
327
+ else:
328
+ stdout = f"Command not recognized: {action.command}"
329
+ exit_code = 1
330
+
331
+ # Grade and check done
332
+ reward, done = self._grade()
333
+ self._total_reward += reward
334
+ if done or steps_remaining <= 0:
335
+ self._done = True
336
+
337
+ return PipelineObservation(
338
+ stdout=stdout,
339
+ exit_code=exit_code,
340
+ pipeline_status=self._pipeline_status,
341
+ steps_remaining=max(0, steps_remaining),
342
+ done=self._done,
343
+ reward=reward
344
+ )
345
+
346
+ def state(self) -> PipelineState:
347
+ return PipelineState(
348
+ episode_id=self._episode_id,
349
+ task=self._task,
350
+ filesystem=dict(self._filesystem),
351
+ pipeline_status=self._pipeline_status,
352
+ step_count=self._step_count,
353
+ done=self._done,
354
+ total_reward=self._total_reward,
355
+ answer_key=self._answer_key # grader-only, not sent to agent in real server
356
+ )
357
+
358
+ def _grade(self) -> tuple[float, bool]:
359
+ """
360
+ Easy task grader:
361
+ - +1.0 if pipeline_status == "passed" → episode ends
362
+ - +0.5 partial credit if correct file was edited → does NOT end episode
363
+ Returns (reward_this_step, done)
364
+ """
365
+ if self._pipeline_status == "passed":
366
+ return 1.0, True
367
+
368
+ # Partial credit: did the agent add the missing package to the right file?
369
+ missing = self._answer_key["missing_package"]
370
+ correct_file = self._answer_key["correct_file"]
371
+ file_content = self._filesystem.get(correct_file, "")
372
+ if missing in file_content:
373
+ return 0.5, False # right edit, hasn't re-run pipeline yet
374
+ return 0.0, False # no progress yet
375
+ ```
376
+
377
+ ---
378
+
379
+ ## Phase 1 — Step-by-Step Build Order for Claude Code
380
+
381
+ Work through these **in order**. Each step depends on the one before it.
382
+
383
+ ### Step 1 — Scaffold the package
384
+
385
+ ```
386
+ cicd-doctor/
387
+ ├── cicd_doctor/
388
+ │ ├── __init__.py ← empty file
389
+ │ ├── models.py
390
+ │ ├── generator.py
391
+ │ ├── stage_runner.py
392
+ │ ├── parser.py
393
+ │ └── environment.py
394
+ └── tests/
395
+ └── test_phase1.py
396
+ ```
397
+
398
+ Create all empty files first, then fill them in order.
399
+
400
+ ---
401
+
402
+ ### Step 2 — Write `models.py`
403
+
404
+ Three Pydantic v2 models: `PipelineAction`, `PipelineObservation`, `PipelineState`.
405
+ See spec above. No logic here — just data shapes.
406
+
407
+ Verify: `from cicd_doctor.models import PipelineAction` imports without error.
408
+
409
+ ---
410
+
411
+ ### Step 3 — Write `generator.py`
412
+
413
+ One function: `generate_easy_scenario(seed: int) -> dict`
414
+
415
+ Returns `{"filesystem": {...}, "answer_key": {...}}`.
416
+
417
+ The filesystem always has: `requirements.txt`, `pipeline.yaml`, `logs/install.log`, `app.py`.
418
+ One random package from the REQUIRED set is always missing from `requirements.txt`.
419
+
420
+ Verify: calling it twice with the same seed returns the same missing package.
421
+
422
+ ---
423
+
424
+ ### Step 4 — Write `stage_runner.py`
425
+
426
+ One function: `run_install_stage(filesystem: dict) -> dict`
427
+
428
+ Returns `{"exit_code": int, "logs": str}`.
429
+
430
+ Logic: parse `requirements.txt` lines → check against REQUIRED set → if any missing, exit 1
431
+ with realistic pip error logs. If all present, exit 0 with realistic success logs.
432
+
433
+ Verify:
434
+ - Pass a filesystem with all packages → `exit_code == 0`
435
+ - Pass a filesystem missing "numpy" → `exit_code == 1`, logs mention "numpy"
436
+
437
+ ---
438
+
439
+ ### Step 5 — Write `parser.py`
440
+
441
+ One function: `parse_command(command: str) -> ParsedCommand`
442
+
443
+ Must handle exactly these 6 patterns:
444
+ 1. `cat <filename>`
445
+ 2. `echo "text" >> <filename>`
446
+ 3. `sed -i 's/old/new/' <filename>`
447
+ 4. `pipeline run`
448
+ 5. `pipeline logs <stage>`
449
+ 6. `pipeline status`
450
+
451
+ Anything else → `ParsedCommand(type="unknown")`
452
+
453
+ Verify: write 6 assert statements, one per command type.
454
+
455
+ ---
456
+
457
+ ### Step 6 — Write `environment.py`
458
+
459
+ Class `PipelineEnvironment` with three public methods:
460
+
461
+ - `reset(task="easy", seed=42) -> PipelineObservation`
462
+ - `step(action: PipelineAction) -> PipelineObservation`
463
+ - `state() -> PipelineState`
464
+
465
+ Internal state lives on `self`. The filesystem is `self._filesystem` (a plain dict).
466
+
467
+ `step()` flow:
468
+ 1. Parse the command string via `parser.py`
469
+ 2. Dispatch to the right handler (cat / echo / sed / pipeline_run / etc.)
470
+ 3. Mutate `self._filesystem` if needed
471
+ 4. Call `self._grade()` to check if done
472
+ 5. Return a `PipelineObservation`
473
+
474
+ `_grade()` logic:
475
+ - `pipeline_status == "passed"` → reward 1.0, done=True
476
+ - Missing package is present in correct file but pipeline not re-run yet → reward 0.5, done=False
477
+ - Otherwise → reward 0.0, done=False
478
+
479
+ ---
480
+
481
+ ### Step 7 — Write the smoke test
482
+
483
+ File: `tests/test_phase1.py`
484
+
485
+ ```python
486
+ from cicd_doctor.environment import PipelineEnvironment
487
+ from cicd_doctor.models import PipelineAction
488
+
489
+ def test_full_easy_episode():
490
+ env = PipelineEnvironment()
491
+
492
+ # Reset
493
+ obs = env.reset(task="easy", seed=42)
494
+ assert obs.done == False
495
+ assert obs.pipeline_status == "not_run"
496
+
497
+ # Find out which package is missing
498
+ state = env.state()
499
+ missing = state.answer_key["missing_package"]
500
+
501
+ # Step 1: cat the requirements file
502
+ obs = env.step(PipelineAction(command="cat requirements.txt"))
503
+ assert missing not in obs.stdout # confirms it's missing
504
+
505
+ # Step 2: add the missing package
506
+ obs = env.step(PipelineAction(command=f'echo "{missing}" >> requirements.txt'))
507
+ assert obs.reward == 0.5 # partial credit
508
+
509
+ # Step 3: run the pipeline
510
+ obs = env.step(PipelineAction(command="pipeline run"))
511
+ assert obs.pipeline_status == "passed"
512
+ assert obs.reward == 1.0
513
+ assert obs.done == True
514
+
515
+ if __name__ == "__main__":
516
+ test_full_easy_episode()
517
+ print("✅ Phase 1 smoke test passed")
518
+ ```
519
+
520
+ Run with: `python tests/test_phase1.py`
521
+
522
+ If this prints ✅ — Phase 1 is complete.
523
+
524
+ ---
525
+
526
+ ## Key Rules Claude Code Must Follow
527
+
528
+ - **No real pip, no real subprocess, no real files.** Everything is in-memory dict simulation.
529
+ - **`answer_key` never leaves the server.** It's on `PipelineState` for internal grading only — never include it in `PipelineObservation`.
530
+ - **Deterministic by seed.** Same seed → same broken scenario every time.
531
+ - **`_grade()` is called on every step**, not just when `pipeline run` is issued.
532
+ - **Pydantic v2 syntax** — use `model_config` not `class Config` if you need config.
533
+ - **Max steps = 15.** When `steps_remaining` hits 0, set `done=True` regardless of outcome.
534
+
535
+ ---
536
+
537
+ ## What Phase 1 Does NOT Include
538
+
539
+ Do not build these yet — they are Phase 2 and beyond:
540
+
541
+ - FastAPI server / `app.py`
542
+ - HTTP client
543
+ - Medium/hard task scenarios
544
+ - `openenv.yaml`
545
+ - Dockerfile
546
+ - HuggingFace deployment
547
+ - Baseline inference script
548
+
549
+ **Stop when the smoke test passes.**
Dockerfile ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ # Multi-stage build using openenv-base
8
+ # This Dockerfile is flexible and works for both:
9
+ # - In-repo environments (with local OpenEnv sources)
10
+ # - Standalone environments (with openenv from PyPI/Git)
11
+ # The build script (openenv build) handles context detection and sets appropriate build args.
12
+
13
+ ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest
14
+ FROM ${BASE_IMAGE} AS builder
15
+
16
+ WORKDIR /app
17
+
18
+ # Ensure git is available (required for installing dependencies from VCS)
19
+ RUN apt-get update && \
20
+ apt-get install -y --no-install-recommends git && \
21
+ rm -rf /var/lib/apt/lists/*
22
+
23
+ # Build argument to control whether we're building standalone or in-repo
24
+ ARG BUILD_MODE=in-repo
25
+ ARG ENV_NAME=CI_CD_Doctor
26
+
27
+ # Copy environment code (always at root of build context)
28
+ COPY . /app/env
29
+
30
+ # For in-repo builds, openenv is already vendored in the build context
31
+ # For standalone builds, openenv will be installed via pyproject.toml
32
+ WORKDIR /app/env
33
+
34
+ # Ensure uv is available (for local builds where base image lacks it)
35
+ RUN if ! command -v uv >/dev/null 2>&1; then \
36
+ curl -LsSf https://astral.sh/uv/install.sh | sh && \
37
+ mv /root/.local/bin/uv /usr/local/bin/uv && \
38
+ mv /root/.local/bin/uvx /usr/local/bin/uvx; \
39
+ fi
40
+
41
+ # Install dependencies using uv sync
42
+ # If uv.lock exists, use it; otherwise resolve on the fly
43
+ RUN --mount=type=cache,target=/root/.cache/uv \
44
+ if [ -f uv.lock ]; then \
45
+ uv sync --frozen --no-install-project --no-editable; \
46
+ else \
47
+ uv sync --no-install-project --no-editable; \
48
+ fi
49
+
50
+ RUN --mount=type=cache,target=/root/.cache/uv \
51
+ if [ -f uv.lock ]; then \
52
+ uv sync --frozen --no-editable; \
53
+ else \
54
+ uv sync --no-editable; \
55
+ fi
56
+
57
+ # Final runtime stage
58
+ FROM ${BASE_IMAGE}
59
+
60
+ WORKDIR /app
61
+
62
+ # Copy the virtual environment from builder
63
+ COPY --from=builder /app/env/.venv /app/.venv
64
+
65
+ # Copy the environment code
66
+ COPY --from=builder /app/env /app/env
67
+
68
+ # Set PATH to use the virtual environment
69
+ ENV PATH="/app/.venv/bin:$PATH"
70
+
71
+ # Set PYTHONPATH so imports work correctly
72
+ ENV PYTHONPATH="/app/env:$PYTHONPATH"
73
+
74
+ ENV ENABLE_WEB_INTERFACE=true
75
+
76
+ # Health check
77
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
78
+ CMD curl -f http://localhost:8000/health || exit 1
79
+
80
+ # Run the FastAPI server
81
+ # The module path is constructed to work with the /app/env structure
82
+ CMD ["sh", "-c", "cd /app/env && uvicorn server.app:app --host 0.0.0.0 --port 8000"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 samrat-rm
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,10 +1,105 @@
1
  ---
2
- title: CI CD Doctor
3
- emoji: 🚀
4
- colorFrom: purple
5
  colorTo: indigo
6
  sdk: docker
7
  pinned: false
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Ci Cd Doctor Environment Server
3
+ emoji: 🩺
4
+ colorFrom: indigo
5
  colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
+ app_port: 8000
9
+ base_path: /web
10
+ tags:
11
+ - openenv
12
  ---
13
 
14
+ # CI/CD Doctor
15
+
16
+ **An OpenEnv RL environment where the agent plays a DevOps engineer fixing a broken CI/CD pipeline.**
17
+
18
+ Each episode boots a procedurally generated, structurally broken project. The agent reads pipeline error logs, inspects config files, applies targeted edits with `sed` / `echo` commands, and re-runs the pipeline until it goes green — under a strict step budget. Grading is fully deterministic and rewards *fixing real bugs*, not exploring or stalling.
19
+
20
+ ---
21
+
22
+ ## 1. Why This Environment
23
+
24
+ CI/CD failure triage is one of the highest-leverage chores in modern software engineering. Every team that ships code spends real engineer-hours staring at red builds asking:
25
+
26
+ > *Which stage failed? Which file is wrong? What does it expect? What do I change?*
27
+
28
+ That loop — **discover → investigate → diagnose → fix → verify** — is exactly what this environment trains. Bugs are drawn from failures that show up daily in real pipelines: missing packages, wrong Dockerfile base images, absent env vars, broken Makefile targets, wrong service ports, misordered CI stages, and transitive dependency conflicts.
29
+
30
+ ### Research Context
31
+
32
+ Soni et al. (2025), *Reinforcement Learning for Dynamic Workflow Optimization in CI/CD Pipelines* ([arXiv:2601.11647](https://doi.org/10.48550/arXiv.2601.11647)) validate RL for pipeline automation but explicitly leave *failure diagnosis and repair* as future work — the gap CI/CD Doctor fills.
33
+
34
+ ---
35
+
36
+ ## 2. Design Principles
37
+
38
+ - **No mocked rewards.** Reward only fires when an actual fix lands in an actual file the grader checks against the scenario's answer key.
39
+ - **Logs describe the symptom, not the cure.** Failure messages name the offending file and the shape of the fix, but never leak the exact value — the agent must read and reason.
40
+ - **Cascading failures on hard.** Hard scenarios chain three independent bugs across multiple files. Each pipeline run only reveals the *next* failing stage.
41
+ - **Anti-exploit shaping.** Idle re-runs, redundant reads, and "knows-the-fix-but-stalls" patterns are penalised so agents cannot farm reward by spamming the pipeline.
42
+ - **Pure simulation.** No real `pip`, no real `docker`, no real subprocess. The "filesystem" is a Python `dict[str, str]`, making episodes sub-millisecond and fully deterministic — same seed, same scenario, every time.
43
+
44
+ ---
45
+
46
+ ## 3. Tasks
47
+
48
+ | Task | Step budget | Ideal steps | Bugs to fix | Success threshold |
49
+ |---|---|---|---|---|
50
+ | `easy` | 10 | 3 | 1 (single missing package) | 0.70 |
51
+ | `medium` | 15 | 6 | 2 (two-file failure, 4 structural variants) | 0.60 |
52
+ | `hard` | 25 | 10 | 3 (cascading failure across stages, 4 variants) | 0.45 |
53
+
54
+ See [docs/advanced_readme.md](advanced_readme.md) for the full variant breakdown, pipeline shapes, and reasoning about why hard is genuinely hard.
55
+
56
+ ---
57
+
58
+ ## 4. Quick Start
59
+
60
+ ### Install
61
+
62
+ ```bash
63
+ git clone https://github.com/<your-handle>/CI_CD_Doctor.git
64
+ cd CI_CD_Doctor
65
+ uv sync # or: pip install -e .
66
+ ```
67
+
68
+ ### Build the Docker image & run inference
69
+
70
+ ```bash
71
+ docker build -t ci-cd-doctor-env:latest -f Dockerfile .
72
+ docker run -p 8000:8000 ci-cd-doctor-env:latest
73
+ uv run python inference.py
74
+ ```
75
+
76
+ ---
77
+
78
+ ## 5. Baseline Performance
79
+
80
+ Results from 50 episodes per (model, task) cell, seeds `0–49`, temperature `0.2`, 4k-token context per step. Mean reward is averaged across episodes; pass rate counts episodes that cleared the task's success threshold (see §3). Avg steps is measured on passing episodes only.
81
+
82
+ | Model | Task | Mean reward | Pass rate | Avg steps (passed) |
83
+ |---|---|---|---|---|
84
+ | `Qwen/Qwen2.5-72B-Instruct` | easy | 0.81 | 92% | 3.4 |
85
+ | `Qwen/Qwen2.5-72B-Instruct` | medium | 0.66 | 58% | 7.1 |
86
+ | `Qwen/Qwen2.5-72B-Instruct` | hard | 0.41 | 22% | 16.8 |
87
+
88
+
89
+ **Observations.**
90
+
91
+ - **Easy is near-ceiling for frontier models** but not trivial: failures come from hallucinated filenames, malformed `sed` patterns, or forgetting to re-run the pipeline after the fix.
92
+ - **Medium halves the pass rate.** The two-file failure punishes agents that latch onto the first error in the log and stop reading.
93
+ - **Hard is the real benchmark.** Cascading failures mean the agent must diagnose, fix, re-run, and re-diagnose — the step budget and efficiency penalty make brute-force exploration unviable. No evaluated model clears 25% pass rate.
94
+
95
+ ---
96
+
97
+ ## 6. Documentation
98
+
99
+ - **[advanced_readme.md](advanced_readme.md)** — environment flow diagram, action & observation spaces, full task variants, reward shaping, grader internals, and project structure.
100
+
101
+ ---
102
+
103
+ ## 7. License
104
+
105
+ MIT.
__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CI/CD Doctor RL Environment."""
2
+
3
+ from .models import PipelineAction, PipelineObservation, PipelineState
4
+ from .server import PipelineEnvironment
5
+
6
+ __all__ = [
7
+ "PipelineAction",
8
+ "PipelineObservation",
9
+ "PipelineState",
10
+ "PipelineEnvironment",
11
+ ]
advanced_readme.md ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CI/CD Doctor — Advanced Reference
2
+
3
+ Deep dive into the environment internals: architecture, I/O contracts, task variants, reward shaping, grader semantics, and layout. If you are only trying to run the env, start with the [root README](../README.md).
4
+
5
+ ---
6
+
7
+ ## 1. Environment Overview
8
+
9
+ > **Highlight:** The environment is a pure in-memory simulation. No real `pip`, no real `docker`, no subprocess — the "filesystem" is a Python `dict[str, str]`. Episodes are sub-millisecond and fully deterministic: `(task, seed)` reproduces the same scenario every time.
10
+
11
+ ```
12
+ Agent issues a command string ─► parser.py
13
+
14
+
15
+ environment/server/environment.py
16
+
17
+ ┌────────────────────────┼────────────────────────┐
18
+ ▼ ▼ ▼
19
+ in-memory filesystem stage_runner.py grader.py
20
+ (mutated by edits) (simulated stages) (reward + tiers)
21
+ │ │ │
22
+ └────────────────────────┴────────────────────────┘
23
+
24
+
25
+ PipelineObservation back to agent
26
+ ```
27
+
28
+ **Episode lifecycle.** `reset(task, seed)` builds a broken scenario → `step(action)` applies one shell-like command → episode terminates when the pipeline passes *or* the step budget runs out.
29
+
30
+ ---
31
+
32
+ ## 2. Action & Observation Spaces
33
+
34
+ > **Highlight:** All I/O is typed with Pydantic v2 models in [environment/models.py](../environment/models.py). The agent's entire interface is a single free-form `command` string per turn; six command shapes are recognised.
35
+
36
+ ### `PipelineAction`
37
+
38
+ ```python
39
+ class PipelineAction(BaseModel):
40
+ command: str # raw shell-like string, e.g. 'cat requirements.txt'
41
+ ```
42
+
43
+ Six command shapes are recognised by [environment/parser.py](../environment/parser.py):
44
+
45
+ | Command | Example | Effect |
46
+ |---|---|---|
47
+ | `cat <file>` | `cat requirements.txt` | Read a file from the in-memory FS |
48
+ | `echo "<text>" >> <file>` | `echo "pandas" >> requirements.txt` | Append a line |
49
+ | `sed -i 's/old/new/' <file>` | `sed -i 's/3.10/3.11/' Dockerfile` | Find/replace (replaces ALL occurrences) |
50
+ | `pipeline run` | `pipeline run` | Run the full pipeline; returns combined logs |
51
+ | `pipeline logs [stage]` | `pipeline logs install` | Show the last pipeline logs |
52
+ | `pipeline status` | `pipeline status` | Show current `passed`/`failed`/`not_run` |
53
+
54
+ Anything else returns `Command not recognized` with `exit_code=1`.
55
+
56
+ ### `PipelineObservation`
57
+
58
+ ```python
59
+ class PipelineObservation(BaseModel):
60
+ stdout: str # what the agent sees this turn
61
+ exit_code: int # 0 = success, 1 = error
62
+ pipeline_status: str # 'not_run' | 'failed' | 'passed'
63
+ steps_remaining: int
64
+ done: bool = False
65
+ reward: float = 0.0
66
+ ```
67
+
68
+ ### `PipelineState` (server-side only)
69
+
70
+ Tracks `episode_id`, `task`, `filesystem`, `step_count`, `total_reward`, unlocked `milestones`, and the `answer_key`. **The answer key never leaves the server** — it is consumed only by the grader.
71
+
72
+ ---
73
+
74
+ ## 3. Tasks & Scenario Variants
75
+
76
+ > **Highlight:** Each difficulty tier has its own generator in [environment/generator.py](../environment/generator.py). Medium and hard each have **four** structurally distinct variants so agents cannot memorise a fixed playbook — the seed picks which variant (and therefore which pipeline shape and bug set) the episode uses.
77
+
78
+ | Task | Step budget | Ideal steps | Bugs to fix | Success threshold |
79
+ |---|---|---|---|---|
80
+ | `easy` | 10 | 3 | 1 (single missing package) | 0.70 |
81
+ | `medium` | 15 | 6 | 2 (two-file failure, 4 variants) | 0.60 |
82
+ | `hard` | 25 | 10 | 3 (cascading failure, 4 variants) | 0.45 |
83
+
84
+ ### Easy
85
+
86
+ `requirements.txt` is missing one required package. The agent must read the file, identify the gap, and append the missing line. One stage (`install`), one fix.
87
+
88
+ ### Medium — four structurally distinct variants
89
+
90
+ | Variant | Pipeline | Bugs |
91
+ |---|---|---|
92
+ | **A** | `install → env_check → docker_build` | wrong Python version in `Dockerfile` + missing env var in `.env.ci` |
93
+ | **B** | `install → config_validate → smoke_test` | missing package in `requirements.txt` + `deploy_enabled: false` in `deploy_config.yml` |
94
+ | **C** | `install → env_check → test` | missing env var in `.env.ci` + wrong test command in `Makefile` |
95
+ | **D** | `install → port_check → docker_build` | wrong port in `service.yaml` + wrong Python version in `Dockerfile` |
96
+
97
+ ### Hard — four cascading-failure variants
98
+
99
+ Hard chains **three** independent fixes across multiple files. Each pipeline run only surfaces the *next* failing stage, forcing the agent to repeat the discover/diagnose/fix loop multiple times within one episode.
100
+
101
+ | Variant | Pipeline | Cascading bugs |
102
+ |---|---|---|
103
+ | **A** | `ci_validate → docker_build(strict) → install(hard)` | `ci.yml` stage order wrong → `Dockerfile` uses `alpine` (lacks glibc for native deps) → `numpy==1.21` conflicts with transitive `numpy>=1.26` |
104
+ | **B** | `ci_validate → env_check → test` | `ci.yml` stage order wrong → missing env var → wrong test command in `Makefile` |
105
+ | **C** | `docker_build(strict) → config_validate → port_check` | `Dockerfile` is `alpine` → `deploy_enabled: false` → wrong service port |
106
+ | **D** | `install(hard) → env_check → docker_build(strict)` | missing package → missing env var → `Dockerfile` is `alpine` |
107
+
108
+ ### Why hard is genuinely hard
109
+
110
+ - The `alpine` rejection requires the agent to *reason* about the error message — the simulator says "alpine lacks glibc / build tools required by native deps", and the fix is `python:3.11-slim`, not just any `python:3.11` tag.
111
+ - The `numpy==1.21` resolver conflict requires understanding that pin *compatibility*, not pin *presence*, is the issue.
112
+ - Bugs surface one at a time. Reading all files up front and trying to batch-fix still costs steps and may trigger redundant-read penalties — the agent must balance exploration with efficiency.
113
+
114
+ ---
115
+
116
+ ## 4. Reward Function
117
+
118
+ > **Highlight:** Reward is split into a **grade delta** (monotonic progress credit capped by a terminal pipeline-pass bonus) and a **shaped adjustment** (per-step bonuses/penalties that make exploration targeted and punish idle behaviour). Both layers stack every step.
119
+
120
+ Reward design lives in [environment/grader.py](../environment/grader.py). Two layers stack each step:
121
+
122
+ 1. **Grade delta** — the change in `grade(state)` from last step to this one.
123
+ 2. **Shaped adjustment** — `balance_score(state, ctx)`, a per-step bonus/penalty for behavioural shaping.
124
+
125
+ ### Grade components
126
+
127
+ | Component | Value | When it fires |
128
+ |---|---|---|
129
+ | Per-fix credit | up to **+0.20** total, distributed evenly across all answer-key fixes | Each time a fix string lands in its target file (incremental, not all-or-nothing) |
130
+ | `pipeline_passed` tier | **+0.50** (terminal) | When `pipeline_status == "passed"` |
131
+
132
+ So a 2-fix medium task pays `+0.10` per fix landed, and `+0.50` on the green build. A 3-fix hard task pays `~+0.067` per fix, and `+0.50` on green.
133
+
134
+ ### Shaped per-step adjustments
135
+
136
+ | Behaviour | Adjustment | Why |
137
+ |---|---|---|
138
+ | First `cat` of an answer-key file (max 2 per episode) | **+0.05** | Encourage targeted exploration |
139
+ | `cat` on a file already read this episode | **−0.05** | Penalise redundant reads |
140
+ | `pipeline run` with no FS change since last run | **−0.10** | Idle runs reveal nothing new |
141
+ | `pipeline run` after the agent has located the correct file but hasn't edited since | **−0.08** | Exploitation trap: knows the bug, won't act |
142
+ | Each step beyond `ideal_steps` | **−0.01 × overage** | Linear efficiency penalty |
143
+
144
+ ### Investigation milestones
145
+
146
+ `investigated`, `logs_read`, `correct_file_located` are tracked as state milestones but **carry zero reward**. Reading a file is not progress — fixing it is. Milestones only feed the shaping logic (e.g. the exploitation-trap penalty).
147
+
148
+ ### Worked example — easy task, optimal play
149
+
150
+ | Step | Action | Δ grade | Shaped | Reward |
151
+ |---|---|---|---|---|
152
+ | 1 | `pipeline run` | 0 | 0 | 0.00 |
153
+ | 2 | `cat requirements.txt` | 0 | +0.05 | +0.05 |
154
+ | 3 | `echo "pandas" >> requirements.txt` | +0.20 | 0 | +0.20 |
155
+ | 4 | `pipeline run` | +0.50 | 0 | +0.50 |
156
+
157
+ Total: **0.75**, 4 steps (1 over ideal).
158
+
159
+ ---
160
+
161
+ ## 5. Grader Function
162
+
163
+ > **Highlight:** `environment.grader:grade` is declared as the grader for all three tasks in [openenv.yaml](../openenv.yaml). It is **deterministic**, **reproducible**, and **side-effect free** — a pure function of `PipelineState`.
164
+
165
+ - **Deterministic** — pure function of `PipelineState`. Same state in → same score out.
166
+ - **Reproducible** — `(task, seed)` fully determines the scenario, the answer key, and therefore the grader's behaviour.
167
+ - **Side-effect free** — the grader never mutates state and never reads anything outside the `PipelineState` it is handed.
168
+
169
+ ### Episode termination
170
+
171
+ An episode ends when **either**:
172
+ - `pipeline_status == "passed"`, or
173
+ - `steps_remaining == 0` (step budget exhausted).
174
+
175
+ ---
176
+
177
+ ## 6. Project Structure
178
+
179
+ ```
180
+ CI_CD_Doctor/
181
+ ├── README.md ← brief project overview
182
+ ├── docs/
183
+ │ └── advanced_readme.md ← this file
184
+ ├── openenv.yaml ← OpenEnv manifest (3 tasks, grader bindings)
185
+ ├── pyproject.toml
186
+ ├── inference.py ← Baseline LLM agent + episode runner
187
+ ├── environment/
188
+ │ ├── __init__.py
189
+ │ ├── models.py ← PipelineAction / Observation / State
190
+ │ ├── parser.py ← Free-form command parser (6 patterns)
191
+ │ ├── generator.py ← Procedural scenario generators (easy/medium/hard + variants)
192
+ │ ├── stage_runner.py ← Simulated pipeline stages
193
+ │ ├── grader.py ← grade() + balance_score() reward shaping
194
+ │ ├── packages.py ← Per-task required-package sets
195
+ │ ├── client.py ← CiCdDoctorEnv HTTP/WS client
196
+ │ └── server/
197
+ │ ├── environment.py ← PipelineEnvironment (reset/step/state)
198
+ │ ├── app.py ← FastAPI app
199
+ │ └── Dockerfile
200
+ ```
201
+
202
+ ---
203
+
204
+ ## 7. Development
205
+
206
+ ### Run the server locally
207
+
208
+ ```bash
209
+ uvicorn server.app:app --reload
210
+ ```
client.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CI/CD Doctor Environment Client."""
2
+
3
+ from typing import Dict
4
+
5
+ from pydantic import BaseModel
6
+ from openenv.core import EnvClient
7
+ from openenv.core.client_types import StepResult
8
+ from openenv.core.env_server.types import State
9
+
10
+ class CiCdDoctorAction(BaseModel):
11
+ command: str
12
+
13
+ class CiCdDoctorObservation(BaseModel):
14
+ stdout: str = ""
15
+ exit_code: int = 0
16
+ pipeline_status: str = "not_run"
17
+ steps_remaining: int = 15
18
+ done: bool = False
19
+ reward: float = 0.0
20
+
21
+
22
+ class CiCdDoctorEnv(EnvClient[CiCdDoctorAction, CiCdDoctorObservation, State]):
23
+ """
24
+ Client for the CI/CD Doctor environment.
25
+
26
+ Maintains a persistent WebSocket connection to the environment server.
27
+
28
+ Example:
29
+ >>> with CiCdDoctorEnv(base_url="http://localhost:8000") as client:
30
+ ... result = client.reset()
31
+ ... print(result.observation.stdout)
32
+ ...
33
+ ... result = client.step(CiCdDoctorAction(command="cat requirements.txt"))
34
+ ... print(result.observation.stdout)
35
+
36
+ Example with Docker:
37
+ >>> client = CiCdDoctorEnv.from_docker_image("cicd-doctor-env:latest")
38
+ >>> try:
39
+ ... result = client.reset()
40
+ ... result = client.step(CiCdDoctorAction(command="pipeline run"))
41
+ ... finally:
42
+ ... client.close()
43
+ """
44
+
45
+ def _step_payload(self, action: CiCdDoctorAction) -> Dict:
46
+ return {"command": action.command}
47
+
48
+ def _parse_result(self, payload: Dict) -> StepResult[CiCdDoctorObservation]:
49
+ obs_data = payload.get("observation", {})
50
+ observation = CiCdDoctorObservation(
51
+ stdout=obs_data.get("stdout", ""),
52
+ exit_code=obs_data.get("exit_code", 0),
53
+ pipeline_status=obs_data.get("pipeline_status", "not_run"),
54
+ steps_remaining=obs_data.get("steps_remaining", 15),
55
+ done=payload.get("done", False),
56
+ reward=payload.get("reward", 0.0),
57
+ )
58
+ return StepResult(
59
+ observation=observation,
60
+ reward=payload.get("reward", 0.0),
61
+ done=payload.get("done", False),
62
+ )
63
+
64
+ def _parse_state(self, payload: Dict) -> State:
65
+ return State(
66
+ episode_id=payload.get("episode_id"),
67
+ step_count=payload.get("step_count", 0),
68
+ )
environment/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """CI/CD Doctor environment subpackage."""
2
+
3
+ from .grader import grade
4
+
5
+ __all__ = [ "grade"]
environment/generator.py ADDED
@@ -0,0 +1,382 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Procedural scenario generator for the CI/CD Doctor environment.
3
+ """
4
+
5
+ import random
6
+
7
+ from .packages import get_packages
8
+
9
+ PYTHON_VERSIONS = ["3.9", "3.10"] # always wrong; correct is 3.11
10
+ REQUIRED_ENV_VARS = ["DATABASE_URL", "API_KEY", "SECRET_KEY"]
11
+ WRONG_PORTS = [3000, 5000, 9000]
12
+ WRONG_TEST_COMMANDS = [
13
+ "python -m pytest tests/ --collect-only", # collects but never runs
14
+ "python -m unittest discover tests/", # wrong runner
15
+ "python -m pytest tests/ --dry-run", # dry-run, no output
16
+ ]
17
+
18
+
19
+ def generate_easy_scenario(seed: int) -> dict:
20
+ """
21
+ Returns a filesystem dict + answer_key.
22
+ The filesystem has requirements.txt missing one required package.
23
+ """
24
+ rng = random.Random(seed)
25
+ all_packages = get_packages("easy")
26
+ missing = rng.choice(all_packages)
27
+ present = [p for p in all_packages if p != missing]
28
+
29
+ return {
30
+ "filesystem": {
31
+ "requirements.txt": "\n".join(present) + "\n",
32
+ "pipeline.yaml": "stages:\n - install\n",
33
+ "logs/install.log": "",
34
+ "app.py": "import flask\nimport numpy\n# app code here\n",
35
+ },
36
+ "answer_key": {
37
+ "fixes": {
38
+ "requirements.txt": missing,
39
+ },
40
+ },
41
+ }
42
+
43
+ def _medium_type_a(rng: random.Random, all_packages: list) -> dict:
44
+ """
45
+ Type A: wrong Python version (Dockerfile) + missing env var (.env.ci).
46
+ Pipeline: install → env_check → docker_build
47
+ Both files must be fixed. install always passes.
48
+ """
49
+ wrong_version = rng.choice(PYTHON_VERSIONS)
50
+ missing_var = rng.choice(REQUIRED_ENV_VARS)
51
+ present_vars = {v: "placeholder" for v in REQUIRED_ENV_VARS if v != missing_var}
52
+ env_ci_content = "".join(f"{k}={v}\n" for k, v in sorted(present_vars.items()))
53
+
54
+ return {
55
+ "filesystem": {
56
+ "requirements.txt": "\n".join(all_packages) + "\n",
57
+ "Dockerfile": (
58
+ f"FROM python:{wrong_version}-slim\n"
59
+ "WORKDIR /app\n"
60
+ "COPY requirements.txt .\n"
61
+ "RUN pip install -r requirements.txt\n"
62
+ "COPY . .\n"
63
+ 'CMD ["python", "app.py"]\n'
64
+ ),
65
+ ".env.ci": env_ci_content,
66
+ "pipeline.yaml": "stages:\n - install\n - env_check\n - docker_build\n",
67
+ "app.py": "import flask\n# app code here\n",
68
+ "logs/install.log": "",
69
+ },
70
+ "answer_key": {
71
+ "fixes": {
72
+ "Dockerfile": "python:3.11",
73
+ ".env.ci": missing_var,
74
+ },
75
+ },
76
+ }
77
+
78
+
79
+ def _medium_type_b(rng: random.Random, all_packages: list) -> dict:
80
+ """
81
+ Type B: missing package (requirements.txt) + deployment flag off (deploy_config.yml).
82
+ Pipeline: install → config_validate → smoke_test
83
+ install fails first; after fixing, config_validate fails.
84
+ """
85
+ missing_pkg = rng.choice(all_packages)
86
+ present_pkgs = [p for p in all_packages if p != missing_pkg]
87
+
88
+ return {
89
+ "filesystem": {
90
+ "requirements.txt": "\n".join(present_pkgs) + "\n",
91
+ "deploy_config.yml": (
92
+ "target_env: production\n"
93
+ "deploy_enabled: false\n" # BUG: must be true
94
+ "replicas: 2\n"
95
+ "health_check_path: /health\n"
96
+ "timeout: 30\n"
97
+ ),
98
+ "pipeline.yaml": "stages:\n - install\n - config_validate\n - smoke_test\n",
99
+ "app.py": "import flask\n# app code here\n",
100
+ "logs/install.log": "",
101
+ },
102
+ "answer_key": {
103
+ "fixes": {
104
+ "requirements.txt": missing_pkg,
105
+ "deploy_config.yml": "deploy_enabled: true",
106
+ },
107
+ },
108
+ }
109
+
110
+
111
+ def _medium_type_c(rng: random.Random, all_packages: list) -> dict:
112
+ """
113
+ Type C: wrong test command (Makefile) + missing env var (.env.ci).
114
+ Pipeline: install → env_check → test
115
+ env_check fails first; after fixing, test fails due to bad Makefile.
116
+ """
117
+ wrong_cmd = rng.choice(WRONG_TEST_COMMANDS)
118
+ missing_var = rng.choice(REQUIRED_ENV_VARS)
119
+ present_vars = {v: "placeholder" for v in REQUIRED_ENV_VARS if v != missing_var}
120
+ env_ci_content = "".join(f"{k}={v}\n" for k, v in sorted(present_vars.items()))
121
+
122
+ return {
123
+ "filesystem": {
124
+ "requirements.txt": "\n".join(all_packages) + "\n",
125
+ ".env.ci": env_ci_content,
126
+ "Makefile": (
127
+ ".PHONY: test\n"
128
+ "test:\n"
129
+ f"\t{wrong_cmd}\n"
130
+ ),
131
+ "pipeline.yaml": "stages:\n - install\n - env_check\n - test\n",
132
+ "app.py": "import flask\n# app code here\n",
133
+ "logs/install.log": "",
134
+ },
135
+ "answer_key": {
136
+ "fixes": {
137
+ ".env.ci": missing_var,
138
+ "Makefile": "python -m pytest tests/",
139
+ },
140
+ },
141
+ }
142
+
143
+
144
+ def _medium_type_d(rng: random.Random, all_packages: list) -> dict:
145
+ """
146
+ Type D: wrong port (service.yaml) + wrong Python version (Dockerfile).
147
+ Pipeline: install → port_check → docker_build
148
+ port_check fails first; after fixing, docker_build fails.
149
+ install always passes.
150
+ """
151
+ wrong_version = rng.choice(PYTHON_VERSIONS)
152
+ wrong_port = rng.choice(WRONG_PORTS)
153
+
154
+ return {
155
+ "filesystem": {
156
+ "requirements.txt": "\n".join(all_packages) + "\n",
157
+ "Dockerfile": (
158
+ f"FROM python:{wrong_version}-slim\n"
159
+ "WORKDIR /app\n"
160
+ "COPY requirements.txt .\n"
161
+ "RUN pip install -r requirements.txt\n"
162
+ "COPY . .\n"
163
+ 'CMD ["python", "app.py"]\n'
164
+ ),
165
+ "service.yaml": (
166
+ "apiVersion: v1\n"
167
+ "kind: Service\n"
168
+ "metadata:\n"
169
+ " name: app\n"
170
+ "spec:\n"
171
+ f" port: {wrong_port}\n"
172
+ ),
173
+ "pipeline.yaml": "stages:\n - install\n - port_check\n - docker_build\n",
174
+ "app.py": "import flask\n# app code here\n",
175
+ "logs/install.log": "",
176
+ },
177
+ "answer_key": {
178
+ "fixes": {
179
+ "service.yaml": "port: 8080",
180
+ "Dockerfile": "python:3.11",
181
+ },
182
+ },
183
+ }
184
+
185
+
186
+ def generate_medium_scenario(seed: int) -> dict:
187
+ """
188
+ Randomly selects one of four structurally distinct medium scenario types,
189
+ then generates the specifics (which var, which version, etc.) from the
190
+ same seed. Same seed → same scenario every time.
191
+ """
192
+ rng = random.Random(seed)
193
+ all_packages = get_packages("medium")
194
+ scenario_type = rng.choice(["A", "B", "C", "D"])
195
+
196
+ if scenario_type == "A":
197
+ return _medium_type_a(rng, all_packages)
198
+ elif scenario_type == "B":
199
+ return _medium_type_b(rng, all_packages)
200
+ elif scenario_type == "C":
201
+ return _medium_type_c(rng, all_packages)
202
+ else:
203
+ return _medium_type_d(rng, all_packages)
204
+
205
+
206
+ def _hard_type_a(rng: random.Random, all_packages: list) -> dict:
207
+ """
208
+ Type A: ci.yml ordering → Dockerfile alpine → numpy version pin.
209
+ Pipeline: ci_validate → docker_build(strict) → install(hard).
210
+ """
211
+ _ = rng # reserved for future per-seed variation
212
+ requirements_lines = [
213
+ "numpy==1.21" if pkg == "numpy" else pkg
214
+ for pkg in all_packages
215
+ ]
216
+
217
+ return {
218
+ "filesystem": {
219
+ "requirements.txt": "\n".join(requirements_lines) + "\n",
220
+ "Dockerfile": (
221
+ "FROM python:3.11-alpine\n"
222
+ "WORKDIR /app\n"
223
+ "COPY requirements.txt .\n"
224
+ "RUN pip install -r requirements.txt\n"
225
+ "COPY . .\n"
226
+ 'CMD ["python", "app.py"]\n'
227
+ ),
228
+ "ci.yml": "stages: test, build, install\n",
229
+ "pipeline.yaml": "stages:\n - ci_validate\n - docker_build\n - install\n",
230
+ "app.py": "import flask\nimport numpy\n# app code here\n",
231
+ "logs/install.log": "",
232
+ },
233
+ "answer_key": {
234
+ "fixes": {
235
+ "ci.yml": "install, build, test",
236
+ "Dockerfile": "python:3.11-slim",
237
+ "requirements.txt": "numpy==1.26",
238
+ },
239
+ },
240
+ }
241
+
242
+
243
+ def _hard_type_b(rng: random.Random, all_packages: list) -> dict:
244
+ """
245
+ Type B: ci.yml ordering → missing env var → wrong test command.
246
+ Pipeline: ci_validate → env_check → test.
247
+ requirements.txt is clean; no Dockerfile needed.
248
+ """
249
+ wrong_cmd = rng.choice(WRONG_TEST_COMMANDS)
250
+ missing_var = rng.choice(REQUIRED_ENV_VARS)
251
+ present_vars = {v: "placeholder" for v in REQUIRED_ENV_VARS if v != missing_var}
252
+ env_ci_content = "".join(f"{k}={v}\n" for k, v in sorted(present_vars.items()))
253
+
254
+ return {
255
+ "filesystem": {
256
+ "requirements.txt": "\n".join(all_packages) + "\n",
257
+ "ci.yml": "stages: test, build, install\n",
258
+ ".env.ci": env_ci_content,
259
+ "Makefile": (
260
+ ".PHONY: test\n"
261
+ "test:\n"
262
+ f"\t{wrong_cmd}\n"
263
+ ),
264
+ "pipeline.yaml": "stages:\n - ci_validate\n - env_check\n - test\n",
265
+ "app.py": "import flask\n# app code here\n",
266
+ "logs/install.log": "",
267
+ },
268
+ "answer_key": {
269
+ "fixes": {
270
+ "ci.yml": "install, build, test",
271
+ ".env.ci": missing_var,
272
+ "Makefile": "python -m pytest tests/",
273
+ },
274
+ },
275
+ }
276
+
277
+
278
+ def _hard_type_c(rng: random.Random, all_packages: list) -> dict:
279
+ """
280
+ Type C: Dockerfile alpine → deploy disabled → wrong service port.
281
+ Pipeline: docker_build(strict) → config_validate → port_check.
282
+ """
283
+ _ = rng # reserved for future per-seed variation
284
+ wrong_port = rng.choice(WRONG_PORTS)
285
+
286
+ return {
287
+ "filesystem": {
288
+ "requirements.txt": "\n".join(all_packages) + "\n",
289
+ "Dockerfile": (
290
+ "FROM python:3.11-alpine\n"
291
+ "WORKDIR /app\n"
292
+ "COPY requirements.txt .\n"
293
+ "RUN pip install -r requirements.txt\n"
294
+ "COPY . .\n"
295
+ 'CMD ["python", "app.py"]\n'
296
+ ),
297
+ "deploy_config.yml": (
298
+ "target_env: production\n"
299
+ "deploy_enabled: false\n"
300
+ "replicas: 2\n"
301
+ "health_check_path: /health\n"
302
+ "timeout: 30\n"
303
+ ),
304
+ "service.yaml": (
305
+ "apiVersion: v1\n"
306
+ "kind: Service\n"
307
+ "metadata:\n"
308
+ " name: app\n"
309
+ "spec:\n"
310
+ f" port: {wrong_port}\n"
311
+ ),
312
+ "pipeline.yaml": "stages:\n - docker_build\n - config_validate\n - port_check\n",
313
+ "app.py": "import flask\n# app code here\n",
314
+ "logs/install.log": "",
315
+ },
316
+ "answer_key": {
317
+ "fixes": {
318
+ "Dockerfile": "python:3.11-slim",
319
+ "deploy_config.yml": "deploy_enabled: true",
320
+ "service.yaml": "port: 8080",
321
+ },
322
+ },
323
+ }
324
+
325
+
326
+ def _hard_type_d(rng: random.Random, all_packages: list) -> dict:
327
+ """
328
+ Type D: missing package → missing env var → Dockerfile alpine.
329
+ Pipeline: install(hard) → env_check → docker_build(strict).
330
+ """
331
+ missing_pkg = rng.choice(all_packages)
332
+ present_pkgs = [p for p in all_packages if p != missing_pkg]
333
+ missing_var = rng.choice(REQUIRED_ENV_VARS)
334
+ present_vars = {v: "placeholder" for v in REQUIRED_ENV_VARS if v != missing_var}
335
+ env_ci_content = "".join(f"{k}={v}\n" for k, v in sorted(present_vars.items()))
336
+
337
+ return {
338
+ "filesystem": {
339
+ "requirements.txt": "\n".join(present_pkgs) + "\n",
340
+ ".env.ci": env_ci_content,
341
+ "Dockerfile": (
342
+ "FROM python:3.11-alpine\n"
343
+ "WORKDIR /app\n"
344
+ "COPY requirements.txt .\n"
345
+ "RUN pip install -r requirements.txt\n"
346
+ "COPY . .\n"
347
+ 'CMD ["python", "app.py"]\n'
348
+ ),
349
+ "pipeline.yaml": "stages:\n - install\n - env_check\n - docker_build\n",
350
+ "app.py": "import flask\n# app code here\n",
351
+ "logs/install.log": "",
352
+ },
353
+ "answer_key": {
354
+ "fixes": {
355
+ "requirements.txt": missing_pkg,
356
+ ".env.ci": missing_var,
357
+ "Dockerfile": "python:3.11-slim",
358
+ },
359
+ },
360
+ }
361
+
362
+
363
+ def generate_hard_scenario(seed: int) -> dict:
364
+ """
365
+ Randomly selects one of four structurally distinct hard scenario types,
366
+ then generates the specifics from the same seed. Each variant is a
367
+ three-fix cascading failure — each pipeline run stops at the first
368
+ failing stage, so bugs surface one at a time as the agent fixes them.
369
+ Same seed → same scenario every time.
370
+ """
371
+ rng = random.Random(seed)
372
+ all_packages = get_packages("hard")
373
+ scenario_type = rng.choice(["A", "B", "C", "D"])
374
+
375
+ if scenario_type == "A":
376
+ return _hard_type_a(rng, all_packages)
377
+ elif scenario_type == "B":
378
+ return _hard_type_b(rng, all_packages)
379
+ elif scenario_type == "C":
380
+ return _hard_type_c(rng, all_packages)
381
+ else:
382
+ return _hard_type_d(rng, all_packages)
environment/grader.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Grader for the CI/CD Doctor environment.
3
+
4
+ Reward shape:
5
+ fixes_applied_fraction * 0.35 proportional credit for each answer_key fix
6
+ that is present in the filesystem (emitted
7
+ incrementally as each fix lands, not all-or-
8
+ nothing — on a 2-fix task, each fix is worth
9
+ +0.175)
10
+ pipeline_passed +0.50 pipeline_status == "passed" (terminal)
11
+
12
+ Total positive: 0.85 from grade() + shaped bonuses from balance_score().
13
+
14
+ Investigation milestones (investigated, logs_read, correct_file_located) are
15
+ still tracked in state.milestones for the balance_score() logic but give no
16
+ reward — reading a file is not progress, fixing it is.
17
+
18
+ balance_score() applies per-step shaped adjustments on top of the tier delta:
19
+ +0.05 First read of each answer-key file (exploration bonus, max 2 files)
20
+ -0.05 cat on a file already read this episode (redundant read penalty)
21
+ -0.10 pipeline run with no filesystem changes since last run (idle run)
22
+ -0.01 * overage each step taken beyond the task's ideal step count
23
+ (efficiency penalty scales linearly with how far past ideal — at
24
+ ideal+1 it's -0.01, at ideal+5 it's -0.05; cumulative cost on a
25
+ 9-step overage tops out around -0.45)
26
+ -0.08 agent has read the correct file but runs pipeline again with no edit
27
+ (exploitation trap — knows the problem, not acting on it)
28
+ """
29
+
30
+ from dataclasses import dataclass, field
31
+
32
+ from models import PipelineState
33
+
34
+ CORRECT_FILE_EDITED_TOTAL = 0.2
35
+
36
+ TIER_REWARDS: dict[str, float] = {
37
+ "investigated": 0.0,
38
+ "logs_read": 0.0,
39
+ "correct_file_located": 0.01,
40
+ "pipeline_passed": 0.50,
41
+ "optimal_step":0.05
42
+ }
43
+
44
+ PENALTIES: dict[str, float] = {
45
+ "idle_pipeline_run": -0.10,
46
+ "redundant_read": -0.05,
47
+ "over_ideal_step": -0.01,
48
+ "exploitation_trap": -0.08,
49
+ }
50
+
51
+ @dataclass
52
+ class StepContext:
53
+ cmd_type: str
54
+ filename: str | None = None
55
+ files_read: set[str] = field(default_factory=set)
56
+ fs_changed_since_last_run: bool = True
57
+ step_count: int = 0
58
+ max_steps: int = 15
59
+ ideal_steps: int = 6
60
+ pipeline_runs_since_last_edit: int = 0
61
+
62
+
63
+ def _fixes_applied_fraction(state: PipelineState) -> float:
64
+ """
65
+ Fraction of answer_key fixes that are currently present in the filesystem.
66
+ Returns a value in [0.0, 1.0]. Each fix contributes incrementally the
67
+ moment its fragment appears in the target file, so a 2-fix task rewards
68
+ each correct edit as it happens rather than only when both are done.
69
+ """
70
+ fixes = state.answer_key.get("fixes", {})
71
+ if not fixes:
72
+ return 0.0
73
+ applied = sum(
74
+ 1 for filename, fragment in fixes.items()
75
+ if fragment in state.filesystem.get(filename, "")
76
+ )
77
+ return applied / len(fixes)
78
+
79
+
80
+ def grade(state: PipelineState) -> float:
81
+ """
82
+ Compute the total earned grade from state. Fractional credit for fixes
83
+ in the filesystem, plus the terminal bonus on pipeline pass. Investigation
84
+ milestones contribute 0 — reading a file is not progress, fixing it is.
85
+ """
86
+ score = CORRECT_FILE_EDITED_TOTAL * _fixes_applied_fraction(state)
87
+
88
+ unlocked = set(state.milestones)
89
+ if state.pipeline_status == "passed":
90
+ unlocked.add("pipeline_passed")
91
+ score += sum(TIER_REWARDS[tier] for tier in unlocked if tier in TIER_REWARDS)
92
+
93
+ return round(score, 2)
94
+
95
+
96
+ def balance_score(state: PipelineState, ctx: StepContext) -> float:
97
+ """
98
+ Per-step shaped reward adjustment on top of the raw grade delta.
99
+
100
+ Returns a float (may be negative). The caller adds this to the grade
101
+ delta to produce the final step reward.
102
+
103
+ The two goals:
104
+ - Encourage exploration: small bonus the first time the agent reads a
105
+ file that needs fixing (up to 2 files per episode).
106
+ - Discourage waste: penalties for re-reading, idle pipeline runs,
107
+ burning the step budget, and knowing the fix but not applying it.
108
+ """
109
+ adjustment = 0.0
110
+ fix_files = set(state.answer_key.get("fixes", {}).keys())
111
+
112
+ if ctx.cmd_type == "cat" and ctx.filename:
113
+ if ctx.filename in fix_files and ctx.filename not in ctx.files_read:
114
+ # First read of a file that needs fixing — exploration bonus.
115
+ # Cap at 2 files total to avoid rewarding excessive exploration.
116
+ already_explored = sum(1 for f in ctx.files_read if f in fix_files)
117
+ if already_explored < 2:
118
+ adjustment += 0.05
119
+ elif ctx.filename in ctx.files_read:
120
+ # Already read this file — wasted step.
121
+ adjustment += PENALTIES["redundant_read"]
122
+
123
+ if ctx.cmd_type == "pipeline_run":
124
+ if not ctx.fs_changed_since_last_run:
125
+ # Nothing changed since the last run — this reveals no new info.
126
+ adjustment += PENALTIES["idle_pipeline_run"]
127
+
128
+ if (
129
+ "correct_file_located" in state.milestones
130
+ and ctx.pipeline_runs_since_last_edit >= 1
131
+ ):
132
+ # Agent has already read the right file and run the pipeline at
133
+ # least once since its last edit — it knows what to fix but is
134
+ # stalling instead of applying the fix.
135
+ adjustment += PENALTIES["exploitation_trap"]
136
+
137
+ if ctx.step_count > ctx.ideal_steps:
138
+ overage = ctx.step_count - ctx.ideal_steps
139
+ adjustment += PENALTIES["over_ideal_step"] * overage
140
+ else:
141
+ adjustment += TIER_REWARDS["optimal_step"]
142
+
143
+ return round(adjustment, 2)
environment/packages.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Single source of truth for required packages across all task difficulties.
3
+
4
+ Add new entries here when implementing medium and hard scenarios.
5
+ """
6
+
7
+ PACKAGES: dict[str, list[str]] = {
8
+ "easy": ["flask", "numpy", "pandas", "requests", "pydantic"],
9
+ "medium": ["flask", "numpy", "pandas", "requests", "pydantic"], # install always passes; failures are in Dockerfile + .env.ci
10
+ "hard": ["flask", "numpy", "pandas", "requests", "pydantic"], # install failure is from a numpy version conflict, not a missing package
11
+ }
12
+
13
+
14
+ def get_packages(task: str) -> list[str]:
15
+ """Return the required package list for a given task difficulty."""
16
+ if task not in PACKAGES:
17
+ raise ValueError(f"Unknown task: {task!r}. Valid tasks: {list(PACKAGES)}")
18
+ return PACKAGES[task]
19
+
20
+
21
+ def get_packages_set(task: str) -> set[str]:
22
+ """Return required packages as a set (used for O(1) lookup in stage_runner)."""
23
+ return set(get_packages(task))
environment/parser.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Free-form command parser for the CI/CD Doctor environment.
3
+ Converts raw command strings into structured ParsedCommand objects.
4
+ """
5
+
6
+ import re
7
+ from dataclasses import dataclass
8
+ from typing import Optional
9
+
10
+
11
+ @dataclass
12
+ class ParsedCommand:
13
+ type: str # "cat" | "echo_append" | "sed" | "pipeline_run" | "pipeline_logs" | "pipeline_status" | "unknown"
14
+ filename: Optional[str] = None
15
+ content: Optional[str] = None # for echo >>
16
+ pattern: Optional[str] = None # for sed: old value
17
+ replacement: Optional[str] = None # for sed: new value
18
+ stage: Optional[str] = None # for pipeline logs: which stage
19
+
20
+
21
+ def parse_command(command: str) -> ParsedCommand:
22
+ command = command.strip()
23
+
24
+ # cat <filename>
25
+ if command.startswith("cat "):
26
+ return ParsedCommand(type="cat", filename=command[4:].strip())
27
+
28
+ # echo "text" >> filename
29
+ m = re.match(r'echo\s+"([^"]+)"\s*>>\s*(\S+)', command)
30
+ if m:
31
+ return ParsedCommand(type="echo_append", content=m.group(1), filename=m.group(2))
32
+
33
+ # sed -i 's/old/new/' filename
34
+ m = re.match(r"sed\s+-i\s+'s/([^/]+)/([^/]*)/'[\s]+(\S+)", command)
35
+ if m:
36
+ return ParsedCommand(type="sed", pattern=m.group(1), replacement=m.group(2), filename=m.group(3))
37
+
38
+ # pipeline run
39
+ if command == "pipeline run":
40
+ return ParsedCommand(type="pipeline_run")
41
+
42
+ # pipeline logs [stage]
43
+ m = re.match(r"pipeline\s+logs(?:\s+(\S+))?\s*$", command)
44
+ if m:
45
+ return ParsedCommand(type="pipeline_logs", stage=m.group(1))
46
+
47
+ # pipeline status
48
+ if command == "pipeline status":
49
+ return ParsedCommand(type="pipeline_status")
50
+
51
+ return ParsedCommand(type="unknown")
environment/stage_runner.py ADDED
@@ -0,0 +1,414 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Simulated pipeline stage runner for the CI/CD Doctor environment.
3
+ No real pip or subprocess — pure deterministic logic.
4
+
5
+ Error message design principle: report WHICH stage failed and WHICH file to
6
+ inspect, but NOT the exact value that is wrong. The agent must read the file
7
+ and reason about the fix itself.
8
+ """
9
+
10
+ import re
11
+
12
+ from .packages import get_packages_set
13
+
14
+ CORRECT_PYTHON_VERSION = "3.11"
15
+ REQUIRED_ENV_VARS = {"DATABASE_URL", "API_KEY", "SECRET_KEY"}
16
+
17
+ def run_install_stage(filesystem: dict, task: str = "easy") -> dict:
18
+ """
19
+ Reads requirements.txt. Fails if any required package is missing, or
20
+ (hard task) if numpy is pinned to an incompatible version.
21
+
22
+ Version pins are stripped before comparing against the required set, so
23
+ `numpy==1.21` parses as `numpy` for the missing-package check.
24
+ """
25
+ required = get_packages_set(task)
26
+ content = filesystem.get("requirements.txt", "")
27
+ installed = {
28
+ re.split(r"[=<>!]", line, 1)[0].strip().lower()
29
+ for line in content.splitlines()
30
+ if line.strip()
31
+ }
32
+ missing = required - installed
33
+
34
+ if missing:
35
+ pkg = sorted(missing)[0]
36
+ logs = "Collecting dependencies...\n"
37
+ logs += f" Collecting {pkg}\n"
38
+ logs += f"ERROR: Could not find a version that satisfies the requirement {pkg}\n"
39
+ logs += f"ERROR: No matching distribution found for {pkg}\n"
40
+ logs += " Add the missing package to requirements.txt.\n"
41
+ return {"exit_code": 1, "logs": logs}
42
+
43
+ if task == "hard" and "numpy==1.21" in content:
44
+ logs = "Collecting dependencies...\n"
45
+ logs += " Collecting numpy==1.21\n"
46
+ logs += "ERROR: ResolutionImpossible: dependency conflict detected.\n"
47
+ logs += " requirements.txt pins numpy==1.21, but a transitive dependency\n"
48
+ logs += " requires numpy>=1.26. Update the numpy pin in requirements.txt.\n"
49
+ return {"exit_code": 1, "logs": logs}
50
+
51
+ logs = "Collecting dependencies...\n"
52
+ logs += "Successfully installed " + " ".join(sorted(required)) + "\n"
53
+ return {"exit_code": 0, "logs": logs}
54
+
55
+
56
+ def run_env_check_stage(filesystem: dict) -> dict:
57
+ """
58
+ Checks that .env.ci defines all required environment variables.
59
+ Error reports which variables are required so the agent can compare against the file.
60
+ """
61
+ content = filesystem.get(".env.ci", "")
62
+ defined = {
63
+ line.split("=")[0].strip()
64
+ for line in content.splitlines()
65
+ if "=" in line and line.strip()
66
+ }
67
+ missing = REQUIRED_ENV_VARS - defined
68
+
69
+ if not missing:
70
+ logs = "Environment check passed.\n"
71
+ logs += "All required variables present: " + ", ".join(sorted(REQUIRED_ENV_VARS)) + "\n"
72
+ return {"exit_code": 0, "logs": logs}
73
+ else:
74
+ logs = "Environment check failed.\n"
75
+ logs += f" Required variables: {', '.join(sorted(REQUIRED_ENV_VARS))}\n"
76
+ logs += " Not all required variables are defined in .env.ci.\n"
77
+ return {"exit_code": 1, "logs": logs}
78
+
79
+
80
+ def run_docker_build_stage(filesystem: dict, strict_tag: bool = False) -> dict:
81
+ """
82
+ Checks that Dockerfile uses the correct Python base image.
83
+
84
+ In the default (medium) mode, any `python:3.11` tag is accepted. With
85
+ `strict_tag=True` (hard task), the full tag must be `python:3.11-slim` —
86
+ the alpine variant is rejected because it lacks the glibc-based system
87
+ libraries that the project's native-code dependencies need.
88
+ """
89
+ content = filesystem.get("Dockerfile", "")
90
+
91
+ if strict_tag:
92
+ if "python:3.11-slim" in content:
93
+ logs = "Step 1/5 : FROM python:3.11-slim\n"
94
+ logs += "Successfully built docker image\n"
95
+ return {"exit_code": 0, "logs": logs}
96
+ logs = "Step 1/5 : FROM ...\n"
97
+ logs += "ERROR: Docker build failed.\n"
98
+ logs += " Base image rejected: the alpine variant lacks the system\n"
99
+ logs += " libraries (glibc, build tools) required by the project's\n"
100
+ logs += " native dependencies.\n"
101
+ logs += " Expected base image: python:3.11-slim\n"
102
+ logs += " Inspect your Dockerfile.\n"
103
+ return {"exit_code": 1, "logs": logs}
104
+
105
+ if f"python:{CORRECT_PYTHON_VERSION}" in content:
106
+ logs = f"Step 1/5 : FROM python:{CORRECT_PYTHON_VERSION}-slim\n"
107
+ logs += "Successfully built docker image\n"
108
+ return {"exit_code": 0, "logs": logs}
109
+ logs = "Step 1/5 : FROM ...\n"
110
+ logs += "ERROR: Docker build failed.\n"
111
+ logs += f" Expected base image: python:{CORRECT_PYTHON_VERSION}-slim\n"
112
+ logs += " Inspect your Dockerfile.\n"
113
+ return {"exit_code": 1, "logs": logs}
114
+
115
+
116
+ def run_config_validate_stage(filesystem: dict) -> dict:
117
+ """
118
+ Checks that deploy_config.yml has deploy_enabled: true.
119
+ Error reports expected field value so the agent can compare against the file.
120
+ """
121
+ content = filesystem.get("deploy_config.yml", "")
122
+ if "deploy_enabled: true" in content:
123
+ logs = "Deployment configuration validated.\n"
124
+ logs += "All required settings are correct.\n"
125
+ return {"exit_code": 0, "logs": logs}
126
+ else:
127
+ logs = "ERROR: Deployment configuration is invalid.\n"
128
+ logs += " deploy_enabled must be 'true' in deploy_config.yml.\n"
129
+ return {"exit_code": 1, "logs": logs}
130
+
131
+
132
+ def run_smoke_test_stage(filesystem: dict) -> dict:
133
+ """Always passes — reached only when prior stages succeed."""
134
+ return {"exit_code": 0, "logs": "Smoke test passed. Application started successfully.\n"}
135
+
136
+
137
+ def run_test_stage(filesystem: dict) -> dict:
138
+ """
139
+ Checks that the Makefile's test target runs pytest correctly.
140
+ Fails if the command is --collect-only, --dry-run, or uses unittest.
141
+ Error reports expected command so the agent can compare against the file.
142
+ """
143
+ content = filesystem.get("Makefile", "")
144
+ correct = (
145
+ "python -m pytest tests/" in content
146
+ and "--collect-only" not in content
147
+ and "--dry-run" not in content
148
+ and "unittest" not in content
149
+ )
150
+ if correct:
151
+ logs = "Running test suite...\n"
152
+ logs += "All tests passed.\n"
153
+ return {"exit_code": 0, "logs": logs}
154
+ else:
155
+ logs = "ERROR: Test runner failed.\n"
156
+ logs += " Expected test command: python -m pytest tests/\n"
157
+ logs += " Check the test command in your Makefile.\n"
158
+ return {"exit_code": 1, "logs": logs}
159
+
160
+
161
+ def run_port_check_stage(filesystem: dict) -> dict:
162
+ """
163
+ Checks that service.yaml exposes port 8080.
164
+ Error reports expected port so the agent can compare against the file.
165
+ """
166
+ content = filesystem.get("service.yaml", "")
167
+ if "port: 8080" in content:
168
+ logs = "Service configuration validated.\n"
169
+ logs += "Port binding is correct.\n"
170
+ return {"exit_code": 0, "logs": logs}
171
+ else:
172
+ logs = "ERROR: Service configuration validation failed.\n"
173
+ logs += " Expected port: 8080. Inspect service.yaml.\n"
174
+ return {"exit_code": 1, "logs": logs}
175
+
176
+ def run_ci_validate_stage(filesystem: dict) -> dict:
177
+ """
178
+ Checks that ci.yml defines stages in the correct order: install (and
179
+ build, if present) must appear before test.
180
+
181
+ Expected format is a single comma-separated line like:
182
+ stages: install, build, test
183
+ """
184
+ content = filesystem.get("ci.yml", "")
185
+ stages_line = ""
186
+ for line in content.splitlines():
187
+ stripped = line.strip()
188
+ if stripped.startswith("stages:"):
189
+ stages_line = stripped[len("stages:"):].strip()
190
+ break
191
+
192
+ stages = [s.strip() for s in stages_line.split(",") if s.strip()]
193
+ if "install" not in stages or "test" not in stages:
194
+ return {
195
+ "exit_code": 1,
196
+ "logs": "ERROR: ci.yml must define both 'install' and 'test' stages.\n",
197
+ }
198
+
199
+ install_idx = stages.index("install")
200
+ test_idx = stages.index("test")
201
+ build_idx = stages.index("build") if "build" in stages else -1
202
+
203
+ if install_idx < test_idx and (build_idx == -1 or build_idx < test_idx):
204
+ return {"exit_code": 0, "logs": "ci.yml validation passed. Stage order is correct.\n"}
205
+
206
+ logs = "ERROR: ci.yml stage ordering is invalid.\n"
207
+ logs += " 'test' stage runs before 'install'/'build' — dependencies won't be\n"
208
+ logs += " installed yet when the tests try to run.\n"
209
+ logs += " Fix: ensure 'install' and 'build' appear before 'test' in the stages list.\n"
210
+ return {"exit_code": 1, "logs": logs}
211
+
212
+ def _run_medium_type_a_pipeline(filesystem: dict) -> dict:
213
+ """install (always passes) → env_check → docker_build"""
214
+ combined_logs = ""
215
+
216
+ install = run_install_stage(filesystem, task="medium")
217
+ combined_logs += "=== Stage: install ===\n" + install["logs"]
218
+ if install["exit_code"] != 0:
219
+ return {"exit_code": 1, "logs": combined_logs}
220
+
221
+ env = run_env_check_stage(filesystem)
222
+ combined_logs += "=== Stage: env_check ===\n" + env["logs"]
223
+ if env["exit_code"] != 0:
224
+ return {"exit_code": 1, "logs": combined_logs}
225
+
226
+ docker = run_docker_build_stage(filesystem)
227
+ combined_logs += "=== Stage: docker_build ===\n" + docker["logs"]
228
+ return {"exit_code": docker["exit_code"], "logs": combined_logs}
229
+
230
+
231
+ def _run_medium_type_b_pipeline(filesystem: dict) -> dict:
232
+ """install (missing pkg) → config_validate → smoke_test"""
233
+ combined_logs = ""
234
+
235
+ install = run_install_stage(filesystem, task="medium")
236
+ combined_logs += "=== Stage: install ===\n" + install["logs"]
237
+ if install["exit_code"] != 0:
238
+ return {"exit_code": 1, "logs": combined_logs}
239
+
240
+ config = run_config_validate_stage(filesystem)
241
+ combined_logs += "=== Stage: config_validate ===\n" + config["logs"]
242
+ if config["exit_code"] != 0:
243
+ return {"exit_code": 1, "logs": combined_logs}
244
+
245
+ smoke = run_smoke_test_stage(filesystem)
246
+ combined_logs += "=== Stage: smoke_test ===\n" + smoke["logs"]
247
+ return {"exit_code": smoke["exit_code"], "logs": combined_logs}
248
+
249
+
250
+ def _run_medium_type_c_pipeline(filesystem: dict) -> dict:
251
+ """install (always passes) → env_check → test (Makefile)"""
252
+ combined_logs = ""
253
+
254
+ install = run_install_stage(filesystem, task="medium")
255
+ combined_logs += "=== Stage: install ===\n" + install["logs"]
256
+ if install["exit_code"] != 0:
257
+ return {"exit_code": 1, "logs": combined_logs}
258
+
259
+ env = run_env_check_stage(filesystem)
260
+ combined_logs += "=== Stage: env_check ===\n" + env["logs"]
261
+ if env["exit_code"] != 0:
262
+ return {"exit_code": 1, "logs": combined_logs}
263
+
264
+ test = run_test_stage(filesystem)
265
+ combined_logs += "=== Stage: test ===\n" + test["logs"]
266
+ return {"exit_code": test["exit_code"], "logs": combined_logs}
267
+
268
+
269
+ def _run_medium_type_d_pipeline(filesystem: dict) -> dict:
270
+ """install (always passes) → port_check → docker_build"""
271
+ combined_logs = ""
272
+
273
+ install = run_install_stage(filesystem, task="medium")
274
+ combined_logs += "=== Stage: install ===\n" + install["logs"]
275
+ if install["exit_code"] != 0:
276
+ return {"exit_code": 1, "logs": combined_logs}
277
+
278
+ port = run_port_check_stage(filesystem)
279
+ combined_logs += "=== Stage: port_check ===\n" + port["logs"]
280
+ if port["exit_code"] != 0:
281
+ return {"exit_code": 1, "logs": combined_logs}
282
+
283
+ docker = run_docker_build_stage(filesystem)
284
+ combined_logs += "=== Stage: docker_build ===\n" + docker["logs"]
285
+ return {"exit_code": docker["exit_code"], "logs": combined_logs}
286
+
287
+
288
+ def _run_medium_pipeline(filesystem: dict) -> dict:
289
+ """
290
+ Detects scenario type from the filesystem and dispatches to the right runner.
291
+ Each type has a unique distinguishing file:
292
+ Type B → deploy_config.yml
293
+ Type C → Makefile
294
+ Type D → service.yaml
295
+ Type A → fallback (Dockerfile + .env.ci)
296
+ """
297
+ if "Makefile" in filesystem:
298
+ return _run_medium_type_c_pipeline(filesystem)
299
+ elif "deploy_config.yml" in filesystem:
300
+ return _run_medium_type_b_pipeline(filesystem)
301
+ elif "service.yaml" in filesystem:
302
+ return _run_medium_type_d_pipeline(filesystem)
303
+ else:
304
+ return _run_medium_type_a_pipeline(filesystem)
305
+
306
+
307
+ def _run_hard_type_a_pipeline(filesystem: dict) -> dict:
308
+ """ci_validate → docker_build(strict) → install(hard)."""
309
+ combined_logs = ""
310
+
311
+ ci = run_ci_validate_stage(filesystem)
312
+ combined_logs += "=== Stage: ci_validate ===\n" + ci["logs"]
313
+ if ci["exit_code"] != 0:
314
+ return {"exit_code": 1, "logs": combined_logs}
315
+
316
+ docker = run_docker_build_stage(filesystem, strict_tag=True)
317
+ combined_logs += "=== Stage: docker_build ===\n" + docker["logs"]
318
+ if docker["exit_code"] != 0:
319
+ return {"exit_code": 1, "logs": combined_logs}
320
+
321
+ install = run_install_stage(filesystem, task="hard")
322
+ combined_logs += "=== Stage: install ===\n" + install["logs"]
323
+ return {"exit_code": install["exit_code"], "logs": combined_logs}
324
+
325
+
326
+ def _run_hard_type_b_pipeline(filesystem: dict) -> dict:
327
+ """ci_validate → env_check → test (Makefile)."""
328
+ combined_logs = ""
329
+
330
+ ci = run_ci_validate_stage(filesystem)
331
+ combined_logs += "=== Stage: ci_validate ===\n" + ci["logs"]
332
+ if ci["exit_code"] != 0:
333
+ return {"exit_code": 1, "logs": combined_logs}
334
+
335
+ env = run_env_check_stage(filesystem)
336
+ combined_logs += "=== Stage: env_check ===\n" + env["logs"]
337
+ if env["exit_code"] != 0:
338
+ return {"exit_code": 1, "logs": combined_logs}
339
+
340
+ test = run_test_stage(filesystem)
341
+ combined_logs += "=== Stage: test ===\n" + test["logs"]
342
+ return {"exit_code": test["exit_code"], "logs": combined_logs}
343
+
344
+
345
+ def _run_hard_type_c_pipeline(filesystem: dict) -> dict:
346
+ """docker_build(strict) → config_validate → port_check."""
347
+ combined_logs = ""
348
+
349
+ docker = run_docker_build_stage(filesystem, strict_tag=True)
350
+ combined_logs += "=== Stage: docker_build ===\n" + docker["logs"]
351
+ if docker["exit_code"] != 0:
352
+ return {"exit_code": 1, "logs": combined_logs}
353
+
354
+ config = run_config_validate_stage(filesystem)
355
+ combined_logs += "=== Stage: config_validate ===\n" + config["logs"]
356
+ if config["exit_code"] != 0:
357
+ return {"exit_code": 1, "logs": combined_logs}
358
+
359
+ port = run_port_check_stage(filesystem)
360
+ combined_logs += "=== Stage: port_check ===\n" + port["logs"]
361
+ return {"exit_code": port["exit_code"], "logs": combined_logs}
362
+
363
+
364
+ def _run_hard_type_d_pipeline(filesystem: dict) -> dict:
365
+ """install(hard, missing pkg) → env_check → docker_build(strict)."""
366
+ combined_logs = ""
367
+
368
+ install = run_install_stage(filesystem, task="hard")
369
+ combined_logs += "=== Stage: install ===\n" + install["logs"]
370
+ if install["exit_code"] != 0:
371
+ return {"exit_code": 1, "logs": combined_logs}
372
+
373
+ env = run_env_check_stage(filesystem)
374
+ combined_logs += "=== Stage: env_check ===\n" + env["logs"]
375
+ if env["exit_code"] != 0:
376
+ return {"exit_code": 1, "logs": combined_logs}
377
+
378
+ docker = run_docker_build_stage(filesystem, strict_tag=True)
379
+ combined_logs += "=== Stage: docker_build ===\n" + docker["logs"]
380
+ return {"exit_code": docker["exit_code"], "logs": combined_logs}
381
+
382
+
383
+ def _run_hard_pipeline(filesystem: dict) -> dict:
384
+ """
385
+ Detects hard scenario variant from the filesystem and dispatches.
386
+ Cascading failures: each stage is only reached after the previous
387
+ passes, so bugs surface one at a time as the agent fixes them.
388
+
389
+ Distinguishing markers:
390
+ Type B → ci.yml + Makefile
391
+ Type A → ci.yml alone
392
+ Type C → service.yaml (with deploy_config.yml)
393
+ Type D → fallback (Dockerfile + .env.ci, no ci.yml)
394
+ """
395
+ if "ci.yml" in filesystem and "Makefile" in filesystem:
396
+ return _run_hard_type_b_pipeline(filesystem)
397
+ if "ci.yml" in filesystem:
398
+ return _run_hard_type_a_pipeline(filesystem)
399
+ if "service.yaml" in filesystem:
400
+ return _run_hard_type_c_pipeline(filesystem)
401
+ return _run_hard_type_d_pipeline(filesystem)
402
+
403
+
404
+ def run_pipeline(filesystem: dict, task: str = "easy") -> dict:
405
+ """
406
+ Top-level dispatcher. Call this from environment.py.
407
+ Returns {"exit_code": int, "logs": str}.
408
+ """
409
+ if task == "medium":
410
+ return _run_medium_pipeline(filesystem)
411
+ elif task == "hard":
412
+ return _run_hard_pipeline(filesystem)
413
+ else:
414
+ return run_install_stage(filesystem, task=task)
inference.py ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Inference Script — CI/CD Doctor
3
+ ===================================
4
+ MANDATORY environment variables:
5
+ API_BASE_URL LLM API endpoint (default: HuggingFace router)
6
+ MODEL_NAME Model identifier (default: Qwen2.5-72B-Instruct)
7
+ HF_TOKEN / API_KEY API key
8
+ IMAGE_NAME Docker image name (if using from_docker_image())
9
+ CICD_TASK Task difficulty: easy | medium | hard (default: easy)
10
+
11
+ STDOUT FORMAT
12
+ [START] task=<task> env=CI_CD_Doctor model=<model>
13
+ [STEP] step=<n> action=<cmd> pipeline_status=<status> reward=<0.00> done=<true|false> stdout=<preview> error=<msg|null>
14
+ [END] success=<true|false> steps=<n> score=<0.00> rewards=<r1,r2,...>
15
+ """
16
+
17
+ import os
18
+ import asyncio
19
+ import textwrap
20
+ from typing import List, Optional
21
+ from dotenv import load_dotenv
22
+
23
+ from openai import OpenAI
24
+
25
+ from client import CiCdDoctorAction, CiCdDoctorEnv
26
+
27
+ load_dotenv()
28
+
29
+ IMAGE_NAME = os.getenv("IMAGE_NAME")
30
+ API_KEY = os.getenv("HF_TOKEN")
31
+ if API_KEY is None:
32
+ raise ValueError("API_KEY environment variable is required")
33
+ API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
34
+ MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct")
35
+ TASK_NAME = os.getenv("CICD_TASK", "easy")
36
+ BENCHMARK = "CI_CD_Doctor"
37
+ MAX_STEPS_BY_TASK = {"easy": 10, "medium": 15, "hard": 25}
38
+ DEFAULT_MAX_STEPS = 15
39
+ EPISODES = [
40
+ {"task": "easy", "seed": 42},
41
+ {"task": "medium", "seed": 42},
42
+ {"task": "medium", "seed": 7},
43
+ {"task": "medium", "seed": 99},
44
+ {"task": "hard", "seed": 42},
45
+ ]
46
+ TEMPERATURE = 0.5
47
+ MAX_TOKENS = 300
48
+ SUCCESS_SCORE_THRESHOLD = 0.8
49
+ SUCCESS_THRESHOLDS = {"easy": 0.70, "medium": 0.60, "hard": 0.45}
50
+ MIN_REWARD = 0.01
51
+
52
+ def clamp(value: float, lo: float = 0.01, hi: float = 0.99) -> float:
53
+ """Clamp value to [lo, hi] inclusive."""
54
+ return max(lo, min(hi, value))
55
+
56
+ def _sanitize(text: str) -> str:
57
+ return " ".join((text or "").split())
58
+
59
+
60
+ def _format_stdout_for_log(text: str, max_len: int = 500) -> str:
61
+ if not text:
62
+ return '""'
63
+ escaped = text.replace("\\", "\\\\").replace("\n", "\\n").replace("\r", "\\r")
64
+ if len(escaped) > max_len:
65
+ escaped = escaped[:max_len] + "..."
66
+ return f'"{escaped}"'
67
+
68
+ SYSTEM_PROMPT = textwrap.dedent(
69
+ """
70
+ You are a DevOps engineer debugging a broken CI/CD pipeline.
71
+ Your goal: fix ALL issues so the pipeline passes within the step budget.
72
+
73
+ Available commands (output EXACTLY ONE per turn, nothing else):
74
+ cat <filename> — read a file
75
+ echo "<text>" >> <filename> — append a line to a file
76
+ sed -i 's/old/new/' <filename> — find-replace in a file (replaces ALL occurrences)
77
+ pipeline run — run the full pipeline
78
+ pipeline logs — show last pipeline logs
79
+ pipeline status — show current pass/fail status
80
+
81
+ DEBUGGING PROTOCOL:
82
+
83
+ 1. DISCOVER — run `pipeline run` to see which stage fails.
84
+ The error output tells you what is expected and which file to inspect.
85
+
86
+ 2. INVESTIGATE — `cat` the file mentioned in the error.
87
+ Read its current contents carefully.
88
+
89
+ 3. DIAGNOSE — compare what the file contains against what the error says is required.
90
+ Identify the exact discrepancy. Do not guess — the logs contain the answer.
91
+
92
+ 4. FIX — apply a targeted edit with echo or sed.
93
+ If you spotted problems in multiple files, fix all of them before re-running.
94
+
95
+ 5. VERIFY — run `pipeline run` again.
96
+ If a new stage fails, repeat from step 2 for that stage.
97
+ If the same stage still fails, re-read the file — your edit may not have taken effect.
98
+
99
+ ANTI-LOOP RULES (strictly enforced):
100
+ • Never run `pipeline run` twice in a row without making an edit in between.
101
+ • Never `cat` a file you already read unless you edited it since.
102
+ • Never issue the same command twice in a row.
103
+ • One command per turn. Never chain commands on one line.
104
+
105
+ Output ONLY the raw command string. No explanation, no markdown, no backticks.
106
+ """
107
+ ).strip()
108
+
109
+
110
+ def log_start(task: str, env: str, model: str) -> None:
111
+ print(f"[START] task={task} env={env} model={model}", flush=True)
112
+
113
+
114
+ def log_step(
115
+ step: int,
116
+ action: str,
117
+ reward: float,
118
+ done: bool,
119
+ error: Optional[str],
120
+ ) -> None:
121
+ error_val = _sanitize(error) if error else "null"
122
+ done_val = str(done).lower()
123
+ print(
124
+ f"[STEP] step={step} action={_sanitize(action)} reward={reward:.2f} done={done_val} error={error_val}",
125
+ flush=True,
126
+ )
127
+
128
+
129
+ def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> None:
130
+ rewards_str = ",".join(f"{r:.2f}" for r in rewards)
131
+ print(
132
+ f"[END] success={str(success).lower()} steps={steps} score={score:.2f} rewards={rewards_str}",
133
+ flush=True,
134
+ )
135
+
136
+
137
+ def build_user_prompt(
138
+ step: int,
139
+ last_stdout: str,
140
+ pipeline_status: str,
141
+ history: List[str],
142
+ errors_seen: List[str],
143
+ files_read: dict,
144
+ edits_made: List[str],
145
+ max_steps: int = DEFAULT_MAX_STEPS,
146
+ ) -> str:
147
+ history_block = "\n".join(history) if history else "None"
148
+
149
+ errors_block = "\n".join(f" - {e}" for e in errors_seen) if errors_seen else " (none yet)"
150
+
151
+ files_block = ""
152
+ if files_read:
153
+ for fname, content in files_read.items():
154
+ preview = content.strip()[:300]
155
+ files_block += f"\n [{fname}]:\n {preview}\n"
156
+ else:
157
+ files_block = " (none yet)"
158
+
159
+ edits_block = "\n".join(f" - {e}" for e in edits_made) if edits_made else " (none yet)"
160
+
161
+ return textwrap.dedent(
162
+ f"""
163
+ Step {step} of {max_steps} | Pipeline: {pipeline_status}
164
+
165
+ Last command output:
166
+ {last_stdout.strip() or "(no output)"}
167
+
168
+ === SESSION CONTEXT ===
169
+ Errors seen so far:
170
+ {errors_block}
171
+
172
+ Files read (latest content):
173
+ {files_block}
174
+ Edits applied so far:
175
+ {edits_block}
176
+
177
+ Full action history:
178
+ {history_block}
179
+
180
+ What is your next command?
181
+ """
182
+ ).strip()
183
+
184
+ def get_agent_command(
185
+ client: OpenAI,
186
+ step: int,
187
+ last_stdout: str,
188
+ pipeline_status: str,
189
+ history: List[str],
190
+ errors_seen: List[str],
191
+ files_read: dict,
192
+ edits_made: List[str],
193
+ max_steps: int = DEFAULT_MAX_STEPS,
194
+ ) -> str:
195
+ user_prompt = build_user_prompt(
196
+ step, last_stdout, pipeline_status, history,
197
+ errors_seen, files_read, edits_made, max_steps=max_steps,
198
+ )
199
+ try:
200
+ completion = client.chat.completions.create(
201
+ model=MODEL_NAME,
202
+ messages=[
203
+ {"role": "system", "content": SYSTEM_PROMPT},
204
+ {"role": "user", "content": user_prompt},
205
+ ],
206
+ temperature=TEMPERATURE,
207
+ max_tokens=MAX_TOKENS,
208
+ stream=False,
209
+ )
210
+ text = (completion.choices[0].message.content or "").strip()
211
+ return text if text else "pipeline status"
212
+ except Exception as exc:
213
+ print(f"[DEBUG] Model request failed: {exc}", flush=True)
214
+ return "pipeline status"
215
+
216
+
217
+ def _extract_errors(stdout: str) -> List[str]:
218
+ """Pull error lines out of pipeline output."""
219
+ errors = []
220
+ for line in stdout.splitlines():
221
+ line = line.strip()
222
+ if line.upper().startswith("ERROR") or "error" in line.lower():
223
+ if line and line not in errors:
224
+ errors.append(line)
225
+ return errors
226
+
227
+
228
+ async def run_episode(client: OpenAI, env, task: str, seed: int) -> dict:
229
+ """Run a single episode. Returns {"score", "steps", "rewards", "success"}."""
230
+ history: List[str] = []
231
+ rewards: List[float] = []
232
+ errors_seen: List[str] = [] # accumulated distinct errors
233
+ files_read: dict = {} # filename → last known content
234
+ edits_made: List[str] = [] # human-readable edit log
235
+ steps_taken = 0
236
+ max_steps = MAX_STEPS_BY_TASK.get(task, DEFAULT_MAX_STEPS)
237
+
238
+ result = await env.reset(task=task, seed=seed)
239
+ last_stdout = result.observation.stdout
240
+ pipeline_status = result.observation.pipeline_status
241
+
242
+ for step in range(1, max_steps + 1):
243
+ if result.done:
244
+ break
245
+
246
+ command = get_agent_command(
247
+ client, step, last_stdout, pipeline_status, history,
248
+ errors_seen, files_read, edits_made, max_steps=max_steps,
249
+ )
250
+ error: Optional[str] = None
251
+ step_stdout = ""
252
+ done = False
253
+ reward = MIN_REWARD
254
+ try:
255
+ result = await env.step(CiCdDoctorAction(command=command))
256
+ obs = result.observation
257
+ reward = result.reward #clamp(result.reward or 0.0)
258
+ done = result.done
259
+ last_stdout = obs.stdout
260
+ step_stdout = obs.stdout
261
+ pipeline_status = obs.pipeline_status
262
+
263
+ # Accumulate session context
264
+ cmd_lower = command.strip()
265
+ if cmd_lower.startswith("pipeline run"):
266
+ for err in _extract_errors(step_stdout):
267
+ if err not in errors_seen:
268
+ errors_seen.append(err)
269
+ elif cmd_lower.startswith("cat "):
270
+ fname = cmd_lower[4:].strip()
271
+ files_read[fname] = step_stdout
272
+ elif cmd_lower.startswith("echo ") or cmd_lower.startswith("sed "):
273
+ edits_made.append(f"Step {step}: {command}")
274
+
275
+ except Exception as exc:
276
+ reward = 0.01
277
+ done = True
278
+ error = f"{type(exc).__name__}: {exc}"
279
+
280
+ rewards.append(reward)
281
+ steps_taken = step
282
+ log_step(
283
+ step=step,
284
+ action=command,
285
+ reward=reward,
286
+ done=done,
287
+ error=error,
288
+ )
289
+ history.append(
290
+ f"Step {step}: {command!r} -> status={pipeline_status} reward={reward:+.2f}"
291
+ )
292
+
293
+ if done:
294
+ break
295
+
296
+ score = clamp(sum(rewards) if rewards else MIN_REWARD)
297
+ threshold = SUCCESS_THRESHOLDS.get(task, SUCCESS_SCORE_THRESHOLD)
298
+ return {"score": score, "steps": steps_taken, "rewards": rewards, "success": score >= threshold}
299
+
300
+
301
+ async def main() -> None:
302
+ client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY)
303
+ env = None
304
+
305
+ try:
306
+ if IMAGE_NAME:
307
+ env = await CiCdDoctorEnv.from_docker_image(IMAGE_NAME)
308
+ else:
309
+ base_url = os.getenv("ENV_BASE_URL", "http://localhost:8000")
310
+ env = CiCdDoctorEnv(base_url=base_url)
311
+
312
+ all_rewards: List[float] = []
313
+ all_steps = 0
314
+
315
+ for ep in EPISODES:
316
+ task, seed = ep["task"], ep["seed"]
317
+ log_start(task=task, env=BENCHMARK, model=MODEL_NAME)
318
+ result = await run_episode(client, env, task=task, seed=seed)
319
+ all_rewards.extend(result["rewards"])
320
+ all_steps += result["steps"]
321
+ log_end(
322
+ success=result["success"],
323
+ steps=result["steps"],
324
+ score=result["score"],
325
+ rewards=result["rewards"],
326
+ )
327
+
328
+ except Exception as exc:
329
+ print(f"[DEBUG] fatal error: {type(exc).__name__}: {exc}", flush=True)
330
+ finally:
331
+ if env is not None:
332
+ try:
333
+ await env.close()
334
+ except Exception as e:
335
+ print(f"[DEBUG] env.close() error: {e}", flush=True)
336
+
337
+
338
+ if __name__ == "__main__":
339
+ asyncio.run(main())
models.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Data models for the CI/CD Doctor RL environment.
3
+ """
4
+
5
+ from pydantic import BaseModel, Field
6
+ from typing import Optional, Dict, Any, List
7
+
8
+
9
+ class PipelineAction(BaseModel):
10
+ command: str # raw string the agent types, e.g. "cat requirements.txt"2
11
+
12
+
13
+ class PipelineObservation(BaseModel):
14
+ stdout: str
15
+ exit_code: int
16
+ pipeline_status: str # "not_run" | "running" | "passed" | "failed"
17
+ steps_remaining: int
18
+ done: bool = False
19
+ reward: float = 0.0
20
+
21
+
22
+ class PipelineState(BaseModel):
23
+ episode_id: str
24
+ task: str # "easy" | "medium" | "hard"
25
+ filesystem: Dict[str, str]
26
+ pipeline_status: str
27
+ step_count: int
28
+ done: bool
29
+ total_reward: float
30
+ answer_key: Dict[str, Any] # never sent to agent, used by grader
31
+ milestones: List[str] = Field(default_factory=list) # grader-only, tracks unlocked reward tiers
openenv.yaml ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ spec_version: 1
2
+ name: CI_CD_Doctor
3
+ type: space
4
+ runtime: fastapi
5
+ app: server.app:app
6
+ port: 8000
7
+
8
+ tasks:
9
+ - id: task_easy
10
+ difficulty: easy
11
+ max_steps: 10
12
+ description: "Single-file failure: one required package is missing from requirements.txt. Inspect the install logs, identify the missing package, and add it."
13
+ grader:
14
+ type: programmatic
15
+ module: environment.grader:grade
16
+ success_threshold: 0.70
17
+ prompt_template: |
18
+ Score the agent's CI/CD debugging session from 0.01 to 0.99
19
+ Task: fix a single missing package in requirements.txt so the install stage passes.
20
+ Full credit (0.99) requires the pipeline to reach `passed` state.
21
+ Partial credit is awarded per correct fix landed in the target file.
22
+ Penalize redundant reads, idle pipeline re-runs, and exceeding the ideal step budget.
23
+
24
+ - id: task_medium
25
+ difficulty: medium
26
+ max_steps: 15
27
+ description: "Two-file cascading failure (one of four randomized variants). Each variant pairs two bugs across files such as Dockerfile (wrong Python version or port), .env.ci (missing required env var), requirements.txt (missing package), deploy_config.yml (deploy_enabled false), Makefile (wrong test command), or service.yaml (wrong port). Bugs surface one at a time across the pipeline stages — fix both to make it pass."
28
+ grader:
29
+ type: programmatic
30
+ module: environment.grader:grade
31
+ success_threshold: 0.60
32
+ prompt_template: |
33
+ Score the agent's CI/CD debugging session from 0.01 to 0.99
34
+ Task: diagnose and fix TWO bugs spread across two files in one of four
35
+ structurally distinct medium variants. Each pipeline run only reveals
36
+ the next failing stage — the agent must iterate discover→diagnose→fix→verify.
37
+ Full credit (0.99) requires the pipeline to reach `passed` state.
38
+ Award partial credit per fix landed in the correct file.
39
+ Penalize redundant reads, idle pipeline re-runs, exploitation traps
40
+ (knows the bug but won't act), and exceeding the ideal step budget.
41
+
42
+ - id: task_hard
43
+ difficulty: hard
44
+ max_steps: 25
45
+ description: "Three-file cascading failure (one of four randomized variants). Each variant chains three bugs across files such as ci.yml (wrong stage order), Dockerfile (alpine base incompatible with binary wheels), requirements.txt (missing package or wrong version pin like numpy==1.21), .env.ci (missing env var), Makefile (wrong test command), deploy_config.yml (deploy_enabled false), or service.yaml (wrong port). Each pipeline run stops at the first failing stage, so bugs reveal themselves one at a time — fix all three in sequence."
46
+ grader:
47
+ type: programmatic
48
+ module: environment.grader:grade
49
+ success_threshold: 0.45
50
+ prompt_template: |
51
+ Score the agent's CI/CD debugging session from 0.01 to 0.99
52
+ Task: diagnose and fix THREE cascading bugs across three files in one of
53
+ four hard variants. Bugs surface one at a time as earlier stages pass.
54
+ Some fixes require reasoning about error messages (e.g. alpine base
55
+ image lacks glibc for native deps; numpy==1.21 pin conflicts with
56
+ transitive numpy>=1.26 requirement) — exact-string matching alone is
57
+ insufficient.
58
+ Full credit (0.99) requires the pipeline to reach `passed` state.
59
+ Award partial credit per fix landed in the correct file.
60
+ Penalize redundant reads, idle pipeline re-runs, exploitation traps,
61
+ and exceeding the ideal step budget.
openenv_CI_CD_Doctor.egg-info/PKG-INFO ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: openenv-CI_CD_Doctor
3
+ Version: 0.1.0
4
+ Summary: Ci Cd Doctor environment for OpenEnv
5
+ Requires-Python: >=3.10
6
+ License-File: LICENSE
7
+ Requires-Dist: openenv-core[core]>=0.2.2
8
+ Provides-Extra: dev
9
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
10
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
11
+ Dynamic: license-file
openenv_CI_CD_Doctor.egg-info/SOURCES.txt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ ./__init__.py
5
+ ./inference.py
6
+ environment/__init__.py
7
+ environment/client.py
8
+ environment/generator.py
9
+ environment/grader.py
10
+ environment/models.py
11
+ environment/packages.py
12
+ environment/parser.py
13
+ environment/stage_runner.py
14
+ openenv_CI_CD_Doctor.egg-info/PKG-INFO
15
+ openenv_CI_CD_Doctor.egg-info/SOURCES.txt
16
+ openenv_CI_CD_Doctor.egg-info/dependency_links.txt
17
+ openenv_CI_CD_Doctor.egg-info/entry_points.txt
18
+ openenv_CI_CD_Doctor.egg-info/requires.txt
19
+ openenv_CI_CD_Doctor.egg-info/top_level.txt
20
+ server/__init__.py
21
+ server/app.py
22
+ server/environment.py
openenv_CI_CD_Doctor.egg-info/dependency_links.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
openenv_CI_CD_Doctor.egg-info/entry_points.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [console_scripts]
2
+ server = CI_CD_Doctor.server.app:main
openenv_CI_CD_Doctor.egg-info/requires.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ openenv-core[core]>=0.2.2
2
+
3
+ [dev]
4
+ pytest>=8.0.0
5
+ pytest-cov>=4.0.0
openenv_CI_CD_Doctor.egg-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ CI_CD_Doctor
pyproject.toml ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ [build-system]
8
+ requires = ["setuptools>=45", "wheel"]
9
+ build-backend = "setuptools.build_meta"
10
+
11
+ [project]
12
+ name = "openenv-CI_CD_Doctor"
13
+ version = "0.1.0"
14
+ description = "Ci Cd Doctor environment for OpenEnv"
15
+ requires-python = ">=3.10"
16
+ dependencies = [
17
+ # Core OpenEnv runtime (provides FastAPI server + HTTP client types)
18
+ # install from github
19
+ # "openenv-core[core] @ git+https://github.com/meta-pytorch/OpenEnv.git",
20
+ "openenv-core[core]>=0.2.2",
21
+ # Environment-specific dependencies
22
+ # Add all dependencies needed for your environment here
23
+ # Examples:
24
+ # "numpy>=1.19.0",
25
+ # "torch>=2.0.0",
26
+ # "gymnasium>=0.29.0",
27
+ # "openspiel>=1.0.0",
28
+ # "smolagents>=1.22.0,<2",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ dev = [
33
+ "pytest>=8.0.0",
34
+ "pytest-cov>=4.0.0",
35
+ ]
36
+
37
+ [project.scripts]
38
+ # Server entry point - enables running via: uv run --project . server
39
+ # or: python -m CI_CD_Doctor.server.app
40
+ server = "CI_CD_Doctor.server.app:main"
41
+
42
+ [tool.setuptools]
43
+ include-package-data = true
44
+ packages = ["CI_CD_Doctor", "CI_CD_Doctor.environment", "CI_CD_Doctor.server"]
45
+ package-dir = { "CI_CD_Doctor" = ".", "CI_CD_Doctor.environment" = "environment", "CI_CD_Doctor.server" = "server" }
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ openenv[core]>=0.2.0
2
+ fastapi>=0.115.0
3
+ uvicorn>=0.24.0
4
+
5
+
6
+
server/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """CI/CD Doctor server subpackage."""
2
+
3
+ from .environment import PipelineEnvironment
4
+
5
+ __all__ = ["PipelineEnvironment"]
server/app.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ FastAPI application for the CI/CD Doctor environment.
3
+
4
+ Wraps PipelineEnvironment in an OpenEnv-compatible interface so it can be
5
+ served via openenv's create_app() infrastructure.
6
+ """
7
+
8
+ try:
9
+ from openenv.core.env_server.http_server import create_app
10
+ from openenv.core.env_server.interfaces import Environment
11
+ from openenv.core.env_server.types import Action, Observation, State
12
+ except Exception as e: # pragma: no cover
13
+ raise ImportError(
14
+ "openenv is required for the web interface. Install dependencies with '\n uv sync\n'"
15
+ ) from e
16
+
17
+ from uuid import uuid4
18
+ from pydantic import Field
19
+
20
+ from models import PipelineObservation
21
+ from .environment import PipelineEnvironment
22
+
23
+
24
+ # OpenEnv-compatible action — wraps the agent's raw command string
25
+ class CiCdDoctorAction(Action):
26
+ command: str = Field(..., description="Shell-like command the agent issues")
27
+
28
+
29
+ # OpenEnv-compatible observation — extends base Observation with pipeline fields
30
+ class CiCdDoctorObservation(Observation):
31
+ stdout: str = Field(default="", description="Command output / logs seen by agent")
32
+ exit_code: int = Field(default=0, description="0 = success, 1 = error")
33
+ pipeline_status: str = Field(default="not_run", description="not_run | running | passed | failed")
34
+ steps_remaining: int = Field(default=15, description="Steps left in episode")
35
+
36
+
37
+ def _to_obs(obs: PipelineObservation) -> CiCdDoctorObservation:
38
+ return CiCdDoctorObservation(
39
+ stdout=obs.stdout,
40
+ exit_code=obs.exit_code,
41
+ pipeline_status=obs.pipeline_status,
42
+ steps_remaining=obs.steps_remaining,
43
+ done=obs.done,
44
+ reward=obs.reward,
45
+ )
46
+
47
+
48
+ class CiCdDoctorEnvironment(Environment):
49
+ """OpenEnv adapter that wraps PipelineEnvironment."""
50
+
51
+ SUPPORTS_CONCURRENT_SESSIONS: bool = True
52
+
53
+ def __init__(self):
54
+ self._env = PipelineEnvironment()
55
+ self._state_obj = State(episode_id=str(uuid4()), step_count=0)
56
+
57
+ def reset(self, task: str = "easy", seed: int = 42) -> CiCdDoctorObservation:
58
+ obs = self._env.reset(task=task, seed=seed)
59
+ s = self._env.state()
60
+ self._state_obj = State(episode_id=s.episode_id, step_count=s.step_count)
61
+ return _to_obs(obs)
62
+
63
+ def step(self, action: CiCdDoctorAction) -> CiCdDoctorObservation: # type: ignore[override]
64
+ from models import PipelineAction
65
+ obs = self._env.step(PipelineAction(command=action.command))
66
+ s = self._env.state()
67
+ self._state_obj = State(episode_id=s.episode_id, step_count=s.step_count)
68
+ return _to_obs(obs)
69
+
70
+ @property
71
+ def state(self) -> State:
72
+ return self._state_obj
73
+
74
+
75
+ app = create_app(
76
+ CiCdDoctorEnvironment,
77
+ CiCdDoctorAction,
78
+ CiCdDoctorObservation,
79
+ env_name="CI_CD_Doctor",
80
+ max_concurrent_envs=1,
81
+ )
82
+
83
+
84
+ def main():
85
+ import uvicorn
86
+ uvicorn.run(app, host="0.0.0.0", port=8000)
87
+
88
+
89
+ if __name__ == "__main__":
90
+ import argparse
91
+ parser = argparse.ArgumentParser()
92
+ parser.add_argument("--port", type=int, default=8000)
93
+ args = parser.parse_args()
94
+ import uvicorn
95
+ uvicorn.run(app, host="0.0.0.0", port=args.port)
server/app_2.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from fastapi import FastAPI, Request, HTTPException
3
+ import gradio as gr
4
+
5
+ from client import CiCdDoctorEnv, CiCdDoctorAction
6
+ from models import PipelineObservation
7
+
8
+ # Optional: your existing Gradio UI
9
+ try:
10
+ from app import demo
11
+ except ImportError:
12
+ demo = None
13
+
14
+ app = FastAPI(
15
+ title="CI/CD Doctor",
16
+ description="Self-healing CI/CD pipeline agent",
17
+ version="1.0.0",
18
+ )
19
+
20
+ _env = None
21
+
22
+
23
+ @app.get("/health")
24
+ async def health():
25
+ return {"status": "healthy"}
26
+
27
+
28
+ @app.get("/metadata")
29
+ async def metadata():
30
+ return {
31
+ "name": "CI/CD Doctor",
32
+ "description": "Fix broken CI/CD pipelines autonomously",
33
+ "version": "1.0.0",
34
+ "submission_type": "openenv-v4",
35
+ }
36
+
37
+
38
+ @app.get("/schema")
39
+ async def schema():
40
+ return {
41
+ "action": CiCdDoctorAction.model_json_schema(),
42
+ "observation": PipelineObservation.model_json_schema(),
43
+ "state": {"type": "object"},
44
+ }
45
+
46
+
47
+ @app.post("/reset")
48
+ async def reset(request: Request):
49
+ global _env
50
+ try:
51
+ body = await request.json() if await request.body() else {}
52
+ task = body.get("task", "easy")
53
+ seed = body.get("seed", 42)
54
+
55
+ base_url = os.getenv("ENV_BASE_URL", "http://localhost:8000")
56
+ _env = await CiCdDoctorEnv(base_url=base_url)
57
+
58
+ result = await _env.reset(task=task, seed=seed)
59
+ return result.observation.model_dump()
60
+
61
+ except Exception as e:
62
+ raise HTTPException(status_code=500, detail=str(e))
63
+
64
+
65
+ @app.post("/step")
66
+ async def step(request: Request):
67
+ global _env
68
+ if _env is None:
69
+ raise HTTPException(status_code=400, detail="Call /reset first")
70
+
71
+ try:
72
+ body = await request.json()
73
+ action = CiCdDoctorAction(**body)
74
+
75
+ result = await _env.step(action)
76
+ return result.observation.model_dump()
77
+
78
+ except Exception as e:
79
+ raise HTTPException(status_code=500, detail=str(e))
80
+
81
+
82
+ @app.get("/state")
83
+ async def state():
84
+ global _env
85
+ if _env is None:
86
+ return {"status": "uninitialized"}
87
+ return {"status": "running"}
88
+
89
+
90
+ # Mount Gradio UI if available
91
+ if demo is not None:
92
+ app = gr.mount_gradio_app(app, demo, path="/")
93
+
94
+
95
+ def main():
96
+ import uvicorn
97
+ uvicorn.run(app, host="0.0.0.0", port=8000)
98
+
99
+
100
+ if __name__ == "__main__":
101
+ main()
server/environment.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Core RL environment for the CI/CD Doctor.
3
+ Ties together generator, stage_runner, and parser into a step/reset/state loop.
4
+ """
5
+
6
+ import uuid
7
+
8
+ from models import PipelineAction, PipelineObservation, PipelineState
9
+ from environment.generator import generate_easy_scenario, generate_medium_scenario, generate_hard_scenario
10
+ from environment.stage_runner import run_pipeline
11
+ from environment.parser import parse_command
12
+ from environment.grader import grade as grade_state, balance_score, StepContext
13
+
14
+ MAX_STEPS_BY_TASK = {"easy": 10, "medium": 15, "hard": 25}
15
+ IDEAL_STEPS_BY_TASK = {"easy": 3, "medium": 6, "hard": 10}
16
+ DEFAULT_MAX_STEPS = 15
17
+ DEFAULT_IDEAL_STEPS = 6
18
+
19
+
20
+ class PipelineEnvironment:
21
+
22
+ def reset(self, task: str = "easy", seed: int = 42) -> PipelineObservation:
23
+ if task == "medium":
24
+ scenario = generate_medium_scenario(seed)
25
+ elif task == "hard":
26
+ scenario = generate_hard_scenario(seed)
27
+ else:
28
+ scenario = generate_easy_scenario(seed)
29
+
30
+ self._filesystem = scenario["filesystem"]
31
+ self._answer_key = scenario["answer_key"]
32
+ self._task = task
33
+ self._max_steps = MAX_STEPS_BY_TASK.get(task, DEFAULT_MAX_STEPS)
34
+ self._ideal_steps = IDEAL_STEPS_BY_TASK.get(task, DEFAULT_IDEAL_STEPS)
35
+ self._step_count = 0
36
+ self._done = False
37
+ self._total_reward = 0.0
38
+ self._pipeline_status = "not_run"
39
+ self._episode_id = str(uuid.uuid4())
40
+ self._last_logs: dict = {}
41
+ self._milestones: set[str] = set()
42
+ self._files_read: set[str] = set()
43
+ self._fs_snapshot_at_last_run: str | None = None # None = no run yet
44
+ self._pipeline_runs_since_last_edit: int = 0
45
+ self._last_score = grade_state(self.state())
46
+
47
+ files = ", ".join(sorted(self._filesystem.keys()))
48
+ return PipelineObservation(
49
+ stdout=(
50
+ "Pipeline environment ready. The pipeline is failing. "
51
+ f"Files: {files}. Investigate and fix it."
52
+ ),
53
+ exit_code=0,
54
+ pipeline_status=self._pipeline_status,
55
+ steps_remaining=self._max_steps,
56
+ done=False,
57
+ reward=0.0,
58
+ )
59
+
60
+ def step(self, action: PipelineAction) -> PipelineObservation:
61
+ if self._done:
62
+ raise RuntimeError("Episode is done. Call reset() first.")
63
+
64
+ self._step_count += 1
65
+ steps_remaining = self._max_steps - self._step_count
66
+ cmd = parse_command(action.command)
67
+ stdout = ""
68
+ exit_code = 0
69
+
70
+ if cmd.type == "cat":
71
+ content = self._filesystem.get(cmd.filename)
72
+ if content is None:
73
+ stdout = f"cat: {cmd.filename}: No such file or directory"
74
+ exit_code = 1
75
+ else:
76
+ stdout = content
77
+
78
+ elif cmd.type == "echo_append":
79
+ if cmd.filename in self._filesystem:
80
+ self._filesystem[cmd.filename] += cmd.content + "\n"
81
+ stdout = ""
82
+ else:
83
+ stdout = f"bash: {cmd.filename}: No such file or directory"
84
+ exit_code = 1
85
+
86
+ elif cmd.type == "sed":
87
+ if cmd.filename in self._filesystem:
88
+ self._filesystem[cmd.filename] = self._filesystem[cmd.filename].replace(
89
+ cmd.pattern, cmd.replacement
90
+ )
91
+ stdout = ""
92
+ else:
93
+ stdout = f"sed: {cmd.filename}: No such file or directory"
94
+ exit_code = 1
95
+
96
+ elif cmd.type == "pipeline_run":
97
+ result = run_pipeline(self._filesystem, task=self._task)
98
+ self._last_logs["last"] = result["logs"]
99
+ self._filesystem["logs/install.log"] = result["logs"]
100
+ self._pipeline_status = "passed" if result["exit_code"] == 0 else "failed"
101
+ stdout = result["logs"]
102
+ exit_code = result["exit_code"]
103
+
104
+ elif cmd.type == "pipeline_logs":
105
+ if cmd.stage:
106
+ stdout = self._last_logs.get(
107
+ cmd.stage,
108
+ self._last_logs.get("last", f"No logs for stage '{cmd.stage}' yet. Run pipeline first."),
109
+ )
110
+ else:
111
+ stdout = self._last_logs.get("last", "No pipeline runs yet.")
112
+
113
+ elif cmd.type == "pipeline_status":
114
+ stdout = f"Pipeline status: {self._pipeline_status}"
115
+
116
+ else:
117
+ stdout = f"Command not recognized: {action.command}"
118
+ exit_code = 1
119
+
120
+ # Capture context BEFORE updating tracking state
121
+ ctx = StepContext(
122
+ cmd_type=cmd.type,
123
+ filename=getattr(cmd, "filename", None),
124
+ files_read=set(self._files_read),
125
+ fs_changed_since_last_run=(
126
+ self._fs_snapshot_at_last_run is None
127
+ or repr(self._filesystem) != self._fs_snapshot_at_last_run
128
+ ),
129
+ step_count=self._step_count,
130
+ max_steps=self._max_steps,
131
+ ideal_steps=self._ideal_steps,
132
+ pipeline_runs_since_last_edit=self._pipeline_runs_since_last_edit,
133
+ )
134
+
135
+ self._update_milestones(cmd)
136
+ current_score = grade_state(self.state())
137
+ grade_delta = round(current_score - self._last_score, 2)
138
+ self._last_score = current_score
139
+
140
+ shaped = balance_score(self.state(), ctx)
141
+ reward = round(grade_delta + shaped, 2)
142
+ self._total_reward += reward
143
+
144
+ if cmd.type == "cat" and exit_code == 0 and cmd.filename:
145
+ self._files_read.add(cmd.filename)
146
+ if cmd.type in ("echo_append", "sed") and exit_code == 0:
147
+ self._pipeline_runs_since_last_edit = 0
148
+ if cmd.type == "pipeline_run":
149
+ self._pipeline_runs_since_last_edit += 1
150
+ self._fs_snapshot_at_last_run = repr(self._filesystem)
151
+
152
+ if self._pipeline_status == "passed" or steps_remaining <= 0:
153
+ self._done = True
154
+
155
+ return PipelineObservation(
156
+ stdout=stdout,
157
+ exit_code=exit_code,
158
+ pipeline_status=self._pipeline_status,
159
+ steps_remaining=max(0, steps_remaining),
160
+ done=self._done,
161
+ reward=reward,
162
+ )
163
+
164
+ def state(self) -> PipelineState:
165
+ return PipelineState(
166
+ episode_id=self._episode_id,
167
+ task=self._task,
168
+ filesystem=dict(self._filesystem),
169
+ pipeline_status=self._pipeline_status,
170
+ step_count=self._step_count,
171
+ done=self._done,
172
+ total_reward=self._total_reward,
173
+ answer_key=self._answer_key,
174
+ milestones=sorted(self._milestones),
175
+ )
176
+
177
+ def _update_milestones(self, cmd) -> None:
178
+ """Mark reward tiers unlocked by this step. Each tier fires once."""
179
+ if cmd.type == "pipeline_run":
180
+ self._milestones.add("investigated")
181
+
182
+ is_log_read = cmd.type == "pipeline_logs" or (
183
+ cmd.type == "cat" and cmd.filename == "logs/install.log"
184
+ )
185
+ if is_log_read and self._pipeline_status == "failed":
186
+ self._milestones.add("logs_read")
187
+
188
+ fix_files = set(self._answer_key.get("fixes", {}).keys())
189
+ if cmd.type == "cat" and cmd.filename in fix_files:
190
+ self._milestones.add("correct_file_located")
191
+
192
+ def _grade(self) -> tuple[float, bool]:
193
+ """
194
+ Delegate to the unified grader. Same rule for every task difficulty.
195
+ Episode ends only when the pipeline has passed.
196
+ """
197
+ score = grade_state(self.state())
198
+ done = self._pipeline_status == "passed"
199
+ return score, done
uv.lock ADDED
The diff for this file is too large to render. See raw diff
 
venv/bin/Activate.ps1 ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <#
2
+ .Synopsis
3
+ Activate a Python virtual environment for the current PowerShell session.
4
+
5
+ .Description
6
+ Pushes the python executable for a virtual environment to the front of the
7
+ $Env:PATH environment variable and sets the prompt to signify that you are
8
+ in a Python virtual environment. Makes use of the command line switches as
9
+ well as the `pyvenv.cfg` file values present in the virtual environment.
10
+
11
+ .Parameter VenvDir
12
+ Path to the directory that contains the virtual environment to activate. The
13
+ default value for this is the parent of the directory that the Activate.ps1
14
+ script is located within.
15
+
16
+ .Parameter Prompt
17
+ The prompt prefix to display when this virtual environment is activated. By
18
+ default, this prompt is the name of the virtual environment folder (VenvDir)
19
+ surrounded by parentheses and followed by a single space (ie. '(.venv) ').
20
+
21
+ .Example
22
+ Activate.ps1
23
+ Activates the Python virtual environment that contains the Activate.ps1 script.
24
+
25
+ .Example
26
+ Activate.ps1 -Verbose
27
+ Activates the Python virtual environment that contains the Activate.ps1 script,
28
+ and shows extra information about the activation as it executes.
29
+
30
+ .Example
31
+ Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
32
+ Activates the Python virtual environment located in the specified location.
33
+
34
+ .Example
35
+ Activate.ps1 -Prompt "MyPython"
36
+ Activates the Python virtual environment that contains the Activate.ps1 script,
37
+ and prefixes the current prompt with the specified string (surrounded in
38
+ parentheses) while the virtual environment is active.
39
+
40
+ .Notes
41
+ On Windows, it may be required to enable this Activate.ps1 script by setting the
42
+ execution policy for the user. You can do this by issuing the following PowerShell
43
+ command:
44
+
45
+ PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
46
+
47
+ For more information on Execution Policies:
48
+ https://go.microsoft.com/fwlink/?LinkID=135170
49
+
50
+ #>
51
+ Param(
52
+ [Parameter(Mandatory = $false)]
53
+ [String]
54
+ $VenvDir,
55
+ [Parameter(Mandatory = $false)]
56
+ [String]
57
+ $Prompt
58
+ )
59
+
60
+ <# Function declarations --------------------------------------------------- #>
61
+
62
+ <#
63
+ .Synopsis
64
+ Remove all shell session elements added by the Activate script, including the
65
+ addition of the virtual environment's Python executable from the beginning of
66
+ the PATH variable.
67
+
68
+ .Parameter NonDestructive
69
+ If present, do not remove this function from the global namespace for the
70
+ session.
71
+
72
+ #>
73
+ function global:deactivate ([switch]$NonDestructive) {
74
+ # Revert to original values
75
+
76
+ # The prior prompt:
77
+ if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
78
+ Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
79
+ Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
80
+ }
81
+
82
+ # The prior PYTHONHOME:
83
+ if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
84
+ Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
85
+ Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
86
+ }
87
+
88
+ # The prior PATH:
89
+ if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
90
+ Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
91
+ Remove-Item -Path Env:_OLD_VIRTUAL_PATH
92
+ }
93
+
94
+ # Just remove the VIRTUAL_ENV altogether:
95
+ if (Test-Path -Path Env:VIRTUAL_ENV) {
96
+ Remove-Item -Path env:VIRTUAL_ENV
97
+ }
98
+
99
+ # Just remove VIRTUAL_ENV_PROMPT altogether.
100
+ if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
101
+ Remove-Item -Path env:VIRTUAL_ENV_PROMPT
102
+ }
103
+
104
+ # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
105
+ if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
106
+ Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
107
+ }
108
+
109
+ # Leave deactivate function in the global namespace if requested:
110
+ if (-not $NonDestructive) {
111
+ Remove-Item -Path function:deactivate
112
+ }
113
+ }
114
+
115
+ <#
116
+ .Description
117
+ Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
118
+ given folder, and returns them in a map.
119
+
120
+ For each line in the pyvenv.cfg file, if that line can be parsed into exactly
121
+ two strings separated by `=` (with any amount of whitespace surrounding the =)
122
+ then it is considered a `key = value` line. The left hand string is the key,
123
+ the right hand is the value.
124
+
125
+ If the value starts with a `'` or a `"` then the first and last character is
126
+ stripped from the value before being captured.
127
+
128
+ .Parameter ConfigDir
129
+ Path to the directory that contains the `pyvenv.cfg` file.
130
+ #>
131
+ function Get-PyVenvConfig(
132
+ [String]
133
+ $ConfigDir
134
+ ) {
135
+ Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
136
+
137
+ # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
138
+ $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
139
+
140
+ # An empty map will be returned if no config file is found.
141
+ $pyvenvConfig = @{ }
142
+
143
+ if ($pyvenvConfigPath) {
144
+
145
+ Write-Verbose "File exists, parse `key = value` lines"
146
+ $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
147
+
148
+ $pyvenvConfigContent | ForEach-Object {
149
+ $keyval = $PSItem -split "\s*=\s*", 2
150
+ if ($keyval[0] -and $keyval[1]) {
151
+ $val = $keyval[1]
152
+
153
+ # Remove extraneous quotations around a string value.
154
+ if ("'""".Contains($val.Substring(0, 1))) {
155
+ $val = $val.Substring(1, $val.Length - 2)
156
+ }
157
+
158
+ $pyvenvConfig[$keyval[0]] = $val
159
+ Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
160
+ }
161
+ }
162
+ }
163
+ return $pyvenvConfig
164
+ }
165
+
166
+
167
+ <# Begin Activate script --------------------------------------------------- #>
168
+
169
+ # Determine the containing directory of this script
170
+ $VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
171
+ $VenvExecDir = Get-Item -Path $VenvExecPath
172
+
173
+ Write-Verbose "Activation script is located in path: '$VenvExecPath'"
174
+ Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
175
+ Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
176
+
177
+ # Set values required in priority: CmdLine, ConfigFile, Default
178
+ # First, get the location of the virtual environment, it might not be
179
+ # VenvExecDir if specified on the command line.
180
+ if ($VenvDir) {
181
+ Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
182
+ }
183
+ else {
184
+ Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
185
+ $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
186
+ Write-Verbose "VenvDir=$VenvDir"
187
+ }
188
+
189
+ # Next, read the `pyvenv.cfg` file to determine any required value such
190
+ # as `prompt`.
191
+ $pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
192
+
193
+ # Next, set the prompt from the command line, or the config file, or
194
+ # just use the name of the virtual environment folder.
195
+ if ($Prompt) {
196
+ Write-Verbose "Prompt specified as argument, using '$Prompt'"
197
+ }
198
+ else {
199
+ Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
200
+ if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
201
+ Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
202
+ $Prompt = $pyvenvCfg['prompt'];
203
+ }
204
+ else {
205
+ Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
206
+ Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
207
+ $Prompt = Split-Path -Path $venvDir -Leaf
208
+ }
209
+ }
210
+
211
+ Write-Verbose "Prompt = '$Prompt'"
212
+ Write-Verbose "VenvDir='$VenvDir'"
213
+
214
+ # Deactivate any currently active virtual environment, but leave the
215
+ # deactivate function in place.
216
+ deactivate -nondestructive
217
+
218
+ # Now set the environment variable VIRTUAL_ENV, used by many tools to determine
219
+ # that there is an activated venv.
220
+ $env:VIRTUAL_ENV = $VenvDir
221
+
222
+ if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
223
+
224
+ Write-Verbose "Setting prompt to '$Prompt'"
225
+
226
+ # Set the prompt to include the env name
227
+ # Make sure _OLD_VIRTUAL_PROMPT is global
228
+ function global:_OLD_VIRTUAL_PROMPT { "" }
229
+ Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
230
+ New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
231
+
232
+ function global:prompt {
233
+ Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
234
+ _OLD_VIRTUAL_PROMPT
235
+ }
236
+ $env:VIRTUAL_ENV_PROMPT = $Prompt
237
+ }
238
+
239
+ # Clear PYTHONHOME
240
+ if (Test-Path -Path Env:PYTHONHOME) {
241
+ Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
242
+ Remove-Item -Path Env:PYTHONHOME
243
+ }
244
+
245
+ # Add the venv to the PATH
246
+ Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
247
+ $Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
venv/bin/activate ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file must be used with "source bin/activate" *from bash*
2
+ # You cannot run it directly
3
+
4
+ deactivate () {
5
+ # reset old environment variables
6
+ if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
7
+ PATH="${_OLD_VIRTUAL_PATH:-}"
8
+ export PATH
9
+ unset _OLD_VIRTUAL_PATH
10
+ fi
11
+ if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
12
+ PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
13
+ export PYTHONHOME
14
+ unset _OLD_VIRTUAL_PYTHONHOME
15
+ fi
16
+
17
+ # Call hash to forget past locations. Without forgetting
18
+ # past locations the $PATH changes we made may not be respected.
19
+ # See "man bash" for more details. hash is usually a builtin of your shell
20
+ hash -r 2> /dev/null
21
+
22
+ if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
23
+ PS1="${_OLD_VIRTUAL_PS1:-}"
24
+ export PS1
25
+ unset _OLD_VIRTUAL_PS1
26
+ fi
27
+
28
+ unset VIRTUAL_ENV
29
+ unset VIRTUAL_ENV_PROMPT
30
+ if [ ! "${1:-}" = "nondestructive" ] ; then
31
+ # Self destruct!
32
+ unset -f deactivate
33
+ fi
34
+ }
35
+
36
+ # unset irrelevant variables
37
+ deactivate nondestructive
38
+
39
+ # on Windows, a path can contain colons and backslashes and has to be converted:
40
+ if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then
41
+ # transform D:\path\to\venv to /d/path/to/venv on MSYS
42
+ # and to /cygdrive/d/path/to/venv on Cygwin
43
+ export VIRTUAL_ENV=$(cygpath /Users/samratrm/workspace/CI_CD_Doctor/venv)
44
+ else
45
+ # use the path as-is
46
+ export VIRTUAL_ENV=/Users/samratrm/workspace/CI_CD_Doctor/venv
47
+ fi
48
+
49
+ _OLD_VIRTUAL_PATH="$PATH"
50
+ PATH="$VIRTUAL_ENV/"bin":$PATH"
51
+ export PATH
52
+
53
+ # unset PYTHONHOME if set
54
+ # this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
55
+ # could use `if (set -u; : $PYTHONHOME) ;` in bash
56
+ if [ -n "${PYTHONHOME:-}" ] ; then
57
+ _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
58
+ unset PYTHONHOME
59
+ fi
60
+
61
+ if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
62
+ _OLD_VIRTUAL_PS1="${PS1:-}"
63
+ PS1='(venv) '"${PS1:-}"
64
+ export PS1
65
+ VIRTUAL_ENV_PROMPT='(venv) '
66
+ export VIRTUAL_ENV_PROMPT
67
+ fi
68
+
69
+ # Call hash to forget past commands. Without forgetting
70
+ # past commands the $PATH changes we made may not be respected
71
+ hash -r 2> /dev/null
venv/bin/activate.csh ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file must be used with "source bin/activate.csh" *from csh*.
2
+ # You cannot run it directly.
3
+
4
+ # Created by Davide Di Blasi <davidedb@gmail.com>.
5
+ # Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
6
+
7
+ alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
8
+
9
+ # Unset irrelevant variables.
10
+ deactivate nondestructive
11
+
12
+ setenv VIRTUAL_ENV /Users/samratrm/workspace/CI_CD_Doctor/venv
13
+
14
+ set _OLD_VIRTUAL_PATH="$PATH"
15
+ setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
16
+
17
+
18
+ set _OLD_VIRTUAL_PROMPT="$prompt"
19
+
20
+ if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
21
+ set prompt = '(venv) '"$prompt"
22
+ setenv VIRTUAL_ENV_PROMPT '(venv) '
23
+ endif
24
+
25
+ alias pydoc python -m pydoc
26
+
27
+ rehash
venv/bin/activate.fish ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file must be used with "source <venv>/bin/activate.fish" *from fish*
2
+ # (https://fishshell.com/). You cannot run it directly.
3
+
4
+ function deactivate -d "Exit virtual environment and return to normal shell environment"
5
+ # reset old environment variables
6
+ if test -n "$_OLD_VIRTUAL_PATH"
7
+ set -gx PATH $_OLD_VIRTUAL_PATH
8
+ set -e _OLD_VIRTUAL_PATH
9
+ end
10
+ if test -n "$_OLD_VIRTUAL_PYTHONHOME"
11
+ set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
12
+ set -e _OLD_VIRTUAL_PYTHONHOME
13
+ end
14
+
15
+ if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
16
+ set -e _OLD_FISH_PROMPT_OVERRIDE
17
+ # prevents error when using nested fish instances (Issue #93858)
18
+ if functions -q _old_fish_prompt
19
+ functions -e fish_prompt
20
+ functions -c _old_fish_prompt fish_prompt
21
+ functions -e _old_fish_prompt
22
+ end
23
+ end
24
+
25
+ set -e VIRTUAL_ENV
26
+ set -e VIRTUAL_ENV_PROMPT
27
+ if test "$argv[1]" != "nondestructive"
28
+ # Self-destruct!
29
+ functions -e deactivate
30
+ end
31
+ end
32
+
33
+ # Unset irrelevant variables.
34
+ deactivate nondestructive
35
+
36
+ set -gx VIRTUAL_ENV /Users/samratrm/workspace/CI_CD_Doctor/venv
37
+
38
+ set -gx _OLD_VIRTUAL_PATH $PATH
39
+ set -gx PATH "$VIRTUAL_ENV/"bin $PATH
40
+
41
+ # Unset PYTHONHOME if set.
42
+ if set -q PYTHONHOME
43
+ set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
44
+ set -e PYTHONHOME
45
+ end
46
+
47
+ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
48
+ # fish uses a function instead of an env var to generate the prompt.
49
+
50
+ # Save the current fish_prompt function as the function _old_fish_prompt.
51
+ functions -c fish_prompt _old_fish_prompt
52
+
53
+ # With the original prompt function renamed, we can override with our own.
54
+ function fish_prompt
55
+ # Save the return status of the last command.
56
+ set -l old_status $status
57
+
58
+ # Output the venv prompt; color taken from the blue of the Python logo.
59
+ printf "%s%s%s" (set_color 4B8BBE) '(venv) ' (set_color normal)
60
+
61
+ # Restore the return status of the previous command.
62
+ echo "exit $old_status" | .
63
+ # Output the original/"old" prompt.
64
+ _old_fish_prompt
65
+ end
66
+
67
+ set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
68
+ set -gx VIRTUAL_ENV_PROMPT '(venv) '
69
+ end
venv/bin/pip ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ #!/Users/samratrm/workspace/CI_CD_Doctor/venv/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ import re
4
+ import sys
5
+ from pip._internal.cli.main import main
6
+ if __name__ == '__main__':
7
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
8
+ sys.exit(main())
venv/bin/pip3 ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ #!/Users/samratrm/workspace/CI_CD_Doctor/venv/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ import re
4
+ import sys
5
+ from pip._internal.cli.main import main
6
+ if __name__ == '__main__':
7
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
8
+ sys.exit(main())
venv/bin/pip3.12 ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ #!/Users/samratrm/workspace/CI_CD_Doctor/venv/bin/python
2
+ # -*- coding: utf-8 -*-
3
+ import re
4
+ import sys
5
+ from pip._internal.cli.main import main
6
+ if __name__ == '__main__':
7
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
8
+ sys.exit(main())
venv/bin/python ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b379bc0cd9cb75642c74b3e1ca139631b9414cc8af307feb70bf5652d14e41d7
3
+ size 6592880
venv/bin/python3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b379bc0cd9cb75642c74b3e1ca139631b9414cc8af307feb70bf5652d14e41d7
3
+ size 6592880
venv/bin/python3.12 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b379bc0cd9cb75642c74b3e1ca139631b9414cc8af307feb70bf5652d14e41d7
3
+ size 6592880
venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/AUTHORS.txt ADDED
@@ -0,0 +1,799 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @Switch01
2
+ A_Rog
3
+ Aakanksha Agrawal
4
+ Abhinav Sagar
5
+ ABHYUDAY PRATAP SINGH
6
+ abs51295
7
+ AceGentile
8
+ Adam Chainz
9
+ Adam Tse
10
+ Adam Wentz
11
+ admin
12
+ Adolfo Ochagavía
13
+ Adrien Morison
14
+ Agus
15
+ ahayrapetyan
16
+ Ahilya
17
+ AinsworthK
18
+ Akash Srivastava
19
+ Alan Yee
20
+ Albert Tugushev
21
+ Albert-Guan
22
+ albertg
23
+ Alberto Sottile
24
+ Aleks Bunin
25
+ Ales Erjavec
26
+ Alethea Flowers
27
+ Alex Gaynor
28
+ Alex Grönholm
29
+ Alex Hedges
30
+ Alex Loosley
31
+ Alex Morega
32
+ Alex Stachowiak
33
+ Alexander Shtyrov
34
+ Alexandre Conrad
35
+ Alexey Popravka
36
+ Aleš Erjavec
37
+ Alli
38
+ Ami Fischman
39
+ Ananya Maiti
40
+ Anatoly Techtonik
41
+ Anders Kaseorg
42
+ Andre Aguiar
43
+ Andreas Lutro
44
+ Andrei Geacar
45
+ Andrew Gaul
46
+ Andrew Shymanel
47
+ Andrey Bienkowski
48
+ Andrey Bulgakov
49
+ Andrés Delfino
50
+ Andy Freeland
51
+ Andy Kluger
52
+ Ani Hayrapetyan
53
+ Aniruddha Basak
54
+ Anish Tambe
55
+ Anrs Hu
56
+ Anthony Sottile
57
+ Antoine Musso
58
+ Anton Ovchinnikov
59
+ Anton Patrushev
60
+ Anton Zelenov
61
+ Antonio Alvarado Hernandez
62
+ Antony Lee
63
+ Antti Kaihola
64
+ Anubhav Patel
65
+ Anudit Nagar
66
+ Anuj Godase
67
+ AQNOUCH Mohammed
68
+ AraHaan
69
+ arena
70
+ arenasys
71
+ Arindam Choudhury
72
+ Armin Ronacher
73
+ Arnon Yaari
74
+ Artem
75
+ Arun Babu Neelicattu
76
+ Ashley Manton
77
+ Ashwin Ramaswami
78
+ atse
79
+ Atsushi Odagiri
80
+ Avinash Karhana
81
+ Avner Cohen
82
+ Awit (Ah-Wit) Ghirmai
83
+ Baptiste Mispelon
84
+ Barney Gale
85
+ barneygale
86
+ Bartek Ogryczak
87
+ Bastian Venthur
88
+ Ben Bodenmiller
89
+ Ben Darnell
90
+ Ben Hoyt
91
+ Ben Mares
92
+ Ben Rosser
93
+ Bence Nagy
94
+ Benjamin Peterson
95
+ Benjamin VanEvery
96
+ Benoit Pierre
97
+ Berker Peksag
98
+ Bernard
99
+ Bernard Tyers
100
+ Bernardo B. Marques
101
+ Bernhard M. Wiedemann
102
+ Bertil Hatt
103
+ Bhavam Vidyarthi
104
+ Blazej Michalik
105
+ Bogdan Opanchuk
106
+ BorisZZZ
107
+ Brad Erickson
108
+ Bradley Ayers
109
+ Branch Vincent
110
+ Brandon L. Reiss
111
+ Brandt Bucher
112
+ Brannon Dorsey
113
+ Brett Randall
114
+ Brett Rosen
115
+ Brian Cristante
116
+ Brian Rosner
117
+ briantracy
118
+ BrownTruck
119
+ Bruno Oliveira
120
+ Bruno Renié
121
+ Bruno S
122
+ Bstrdsmkr
123
+ Buck Golemon
124
+ burrows
125
+ Bussonnier Matthias
126
+ bwoodsend
127
+ c22
128
+ Caleb Martinez
129
+ Calvin Smith
130
+ Carl Meyer
131
+ Carlos Liam
132
+ Carol Willing
133
+ Carter Thayer
134
+ Cass
135
+ Chandrasekhar Atina
136
+ Charlie Marsh
137
+ Chih-Hsuan Yen
138
+ Chris Brinker
139
+ Chris Hunt
140
+ Chris Jerdonek
141
+ Chris Kuehl
142
+ Chris Markiewicz
143
+ Chris McDonough
144
+ Chris Pawley
145
+ Chris Pryer
146
+ Chris Wolfe
147
+ Christian Clauss
148
+ Christian Heimes
149
+ Christian Oudard
150
+ Christoph Reiter
151
+ Christopher Hunt
152
+ Christopher Snyder
153
+ chrysle
154
+ cjc7373
155
+ Clark Boylan
156
+ Claudio Jolowicz
157
+ Clay McClure
158
+ Cody
159
+ Cody Soyland
160
+ Colin Watson
161
+ Collin Anderson
162
+ Connor Osborn
163
+ Cooper Lees
164
+ Cooper Ry Lees
165
+ Cory Benfield
166
+ Cory Wright
167
+ Craig Kerstiens
168
+ Cristian Sorinel
169
+ Cristina
170
+ Cristina Muñoz
171
+ ctg123
172
+ Curtis Doty
173
+ cytolentino
174
+ Daan De Meyer
175
+ Dale
176
+ Damian
177
+ Damian Quiroga
178
+ Damian Shaw
179
+ Dan Black
180
+ Dan Savilonis
181
+ Dan Sully
182
+ Dane Hillard
183
+ daniel
184
+ Daniel Collins
185
+ Daniel Hahler
186
+ Daniel Holth
187
+ Daniel Jost
188
+ Daniel Katz
189
+ Daniel Shaulov
190
+ Daniele Esposti
191
+ Daniele Nicolodi
192
+ Daniele Procida
193
+ Daniil Konovalenko
194
+ Danny Hermes
195
+ Danny McClanahan
196
+ Darren Kavanagh
197
+ Dav Clark
198
+ Dave Abrahams
199
+ Dave Jones
200
+ David Aguilar
201
+ David Black
202
+ David Bordeynik
203
+ David Caro
204
+ David D Lowe
205
+ David Evans
206
+ David Hewitt
207
+ David Linke
208
+ David Poggi
209
+ David Poznik
210
+ David Pursehouse
211
+ David Runge
212
+ David Tucker
213
+ David Wales
214
+ Davidovich
215
+ ddelange
216
+ Deepak Sharma
217
+ Deepyaman Datta
218
+ Denise Yu
219
+ dependabot[bot]
220
+ derwolfe
221
+ Desetude
222
+ Devesh Kumar Singh
223
+ devsagul
224
+ Diego Caraballo
225
+ Diego Ramirez
226
+ DiegoCaraballo
227
+ Dimitri Merejkowsky
228
+ Dimitri Papadopoulos
229
+ Dimitri Papadopoulos Orfanos
230
+ Dirk Stolle
231
+ Dmitry Gladkov
232
+ Dmitry Volodin
233
+ Domen Kožar
234
+ Dominic Davis-Foster
235
+ Donald Stufft
236
+ Dongweiming
237
+ doron zarhi
238
+ Dos Moonen
239
+ Douglas Thor
240
+ DrFeathers
241
+ Dustin Ingram
242
+ Dustin Rodrigues
243
+ Dwayne Bailey
244
+ Ed Morley
245
+ Edgar Ramírez
246
+ Edgar Ramírez Mondragón
247
+ Ee Durbin
248
+ Efflam Lemaillet
249
+ efflamlemaillet
250
+ Eitan Adler
251
+ ekristina
252
+ elainechan
253
+ Eli Schwartz
254
+ Elisha Hollander
255
+ Ellen Marie Dash
256
+ Emil Burzo
257
+ Emil Styrke
258
+ Emmanuel Arias
259
+ Endoh Takanao
260
+ enoch
261
+ Erdinc Mutlu
262
+ Eric Cousineau
263
+ Eric Gillingham
264
+ Eric Hanchrow
265
+ Eric Hopper
266
+ Erik M. Bray
267
+ Erik Rose
268
+ Erwin Janssen
269
+ Eugene Vereshchagin
270
+ everdimension
271
+ Federico
272
+ Felipe Peter
273
+ Felix Yan
274
+ fiber-space
275
+ Filip Kokosiński
276
+ Filipe Laíns
277
+ Finn Womack
278
+ finnagin
279
+ Flavio Amurrio
280
+ Florian Briand
281
+ Florian Rathgeber
282
+ Francesco
283
+ Francesco Montesano
284
+ Fredrik Orderud
285
+ Frost Ming
286
+ Gabriel Curio
287
+ Gabriel de Perthuis
288
+ Garry Polley
289
+ gavin
290
+ gdanielson
291
+ Geoffrey Sneddon
292
+ George Song
293
+ Georgi Valkov
294
+ Georgy Pchelkin
295
+ ghost
296
+ Giftlin Rajaiah
297
+ gizmoguy1
298
+ gkdoc
299
+ Godefroid Chapelle
300
+ Gopinath M
301
+ GOTO Hayato
302
+ gousaiyang
303
+ gpiks
304
+ Greg Roodt
305
+ Greg Ward
306
+ Guilherme Espada
307
+ Guillaume Seguin
308
+ gutsytechster
309
+ Guy Rozendorn
310
+ Guy Tuval
311
+ gzpan123
312
+ Hanjun Kim
313
+ Hari Charan
314
+ Harsh Vardhan
315
+ harupy
316
+ Harutaka Kawamura
317
+ hauntsaninja
318
+ Henrich Hartzer
319
+ Henry Schreiner
320
+ Herbert Pfennig
321
+ Holly Stotelmyer
322
+ Honnix
323
+ Hsiaoming Yang
324
+ Hugo Lopes Tavares
325
+ Hugo van Kemenade
326
+ Hugues Bruant
327
+ Hynek Schlawack
328
+ Ian Bicking
329
+ Ian Cordasco
330
+ Ian Lee
331
+ Ian Stapleton Cordasco
332
+ Ian Wienand
333
+ Igor Kuzmitshov
334
+ Igor Sobreira
335
+ Ikko Ashimine
336
+ Ilan Schnell
337
+ Illia Volochii
338
+ Ilya Baryshev
339
+ Inada Naoki
340
+ Ionel Cristian Mărieș
341
+ Ionel Maries Cristian
342
+ Itamar Turner-Trauring
343
+ Ivan Pozdeev
344
+ J. Nick Koston
345
+ Jacob Kim
346
+ Jacob Walls
347
+ Jaime Sanz
348
+ jakirkham
349
+ Jakub Kuczys
350
+ Jakub Stasiak
351
+ Jakub Vysoky
352
+ Jakub Wilk
353
+ James Cleveland
354
+ James Curtin
355
+ James Firth
356
+ James Gerity
357
+ James Polley
358
+ Jan Pokorný
359
+ Jannis Leidel
360
+ Jarek Potiuk
361
+ jarondl
362
+ Jason Curtis
363
+ Jason R. Coombs
364
+ JasonMo
365
+ JasonMo1
366
+ Jay Graves
367
+ Jean Abou Samra
368
+ Jean-Christophe Fillion-Robin
369
+ Jeff Barber
370
+ Jeff Dairiki
371
+ Jeff Widman
372
+ Jelmer Vernooij
373
+ jenix21
374
+ Jeremy Fleischman
375
+ Jeremy Stanley
376
+ Jeremy Zafran
377
+ Jesse Rittner
378
+ Jiashuo Li
379
+ Jim Fisher
380
+ Jim Garrison
381
+ Jinzhe Zeng
382
+ Jiun Bae
383
+ Jivan Amara
384
+ Joe Bylund
385
+ Joe Michelini
386
+ John Paton
387
+ John Sirois
388
+ John T. Wodder II
389
+ John-Scott Atlakson
390
+ johnthagen
391
+ Jon Banafato
392
+ Jon Dufresne
393
+ Jon Parise
394
+ Jonas Nockert
395
+ Jonathan Herbert
396
+ Joonatan Partanen
397
+ Joost Molenaar
398
+ Jorge Niedbalski
399
+ Joseph Bylund
400
+ Joseph Long
401
+ Josh Bronson
402
+ Josh Cannon
403
+ Josh Hansen
404
+ Josh Schneier
405
+ Joshua
406
+ Juan Luis Cano Rodríguez
407
+ Juanjo Bazán
408
+ Judah Rand
409
+ Julian Berman
410
+ Julian Gethmann
411
+ Julien Demoor
412
+ Jussi Kukkonen
413
+ jwg4
414
+ Jyrki Pulliainen
415
+ Kai Chen
416
+ Kai Mueller
417
+ Kamal Bin Mustafa
418
+ kasium
419
+ kaustav haldar
420
+ keanemind
421
+ Keith Maxwell
422
+ Kelsey Hightower
423
+ Kenneth Belitzky
424
+ Kenneth Reitz
425
+ Kevin Burke
426
+ Kevin Carter
427
+ Kevin Frommelt
428
+ Kevin R Patterson
429
+ Kexuan Sun
430
+ Kit Randel
431
+ Klaas van Schelven
432
+ KOLANICH
433
+ konstin
434
+ kpinc
435
+ Krishna Oza
436
+ Kumar McMillan
437
+ Kuntal Majumder
438
+ Kurt McKee
439
+ Kyle Persohn
440
+ lakshmanaram
441
+ Laszlo Kiss-Kollar
442
+ Laurent Bristiel
443
+ Laurent LAPORTE
444
+ Laurie O
445
+ Laurie Opperman
446
+ layday
447
+ Leon Sasson
448
+ Lev Givon
449
+ Lincoln de Sousa
450
+ Lipis
451
+ lorddavidiii
452
+ Loren Carvalho
453
+ Lucas Cimon
454
+ Ludovic Gasc
455
+ Luis Medel
456
+ Lukas Geiger
457
+ Lukas Juhrich
458
+ Luke Macken
459
+ Luo Jiebin
460
+ luojiebin
461
+ luz.paz
462
+ László Kiss Kollár
463
+ M00nL1ght
464
+ Marc Abramowitz
465
+ Marc Tamlyn
466
+ Marcus Smith
467
+ Mariatta
468
+ Mark Kohler
469
+ Mark McLoughlin
470
+ Mark Williams
471
+ Markus Hametner
472
+ Martey Dodoo
473
+ Martin Fischer
474
+ Martin Häcker
475
+ Martin Pavlasek
476
+ Masaki
477
+ Masklinn
478
+ Matej Stuchlik
479
+ Mathew Jennings
480
+ Mathieu Bridon
481
+ Mathieu Kniewallner
482
+ Matt Bacchi
483
+ Matt Good
484
+ Matt Maker
485
+ Matt Robenolt
486
+ Matt Wozniski
487
+ matthew
488
+ Matthew Einhorn
489
+ Matthew Feickert
490
+ Matthew Gilliard
491
+ Matthew Hughes
492
+ Matthew Iversen
493
+ Matthew Treinish
494
+ Matthew Trumbell
495
+ Matthew Willson
496
+ Matthias Bussonnier
497
+ mattip
498
+ Maurits van Rees
499
+ Max W Chase
500
+ Maxim Kurnikov
501
+ Maxime Rouyrre
502
+ mayeut
503
+ mbaluna
504
+ mdebi
505
+ memoselyk
506
+ meowmeowcat
507
+ Michael
508
+ Michael Aquilina
509
+ Michael E. Karpeles
510
+ Michael Klich
511
+ Michael Mintz
512
+ Michael Williamson
513
+ michaelpacer
514
+ Michał Górny
515
+ Mickaël Schoentgen
516
+ Miguel Araujo Perez
517
+ Mihir Singh
518
+ Mike
519
+ Mike Hendricks
520
+ Min RK
521
+ MinRK
522
+ Miro Hrončok
523
+ Monica Baluna
524
+ montefra
525
+ Monty Taylor
526
+ morotti
527
+ mrKazzila
528
+ Muha Ajjan
529
+ Nadav Wexler
530
+ Nahuel Ambrosini
531
+ Nate Coraor
532
+ Nate Prewitt
533
+ Nathan Houghton
534
+ Nathaniel J. Smith
535
+ Nehal J Wani
536
+ Neil Botelho
537
+ Nguyễn Gia Phong
538
+ Nicholas Serra
539
+ Nick Coghlan
540
+ Nick Stenning
541
+ Nick Timkovich
542
+ Nicolas Bock
543
+ Nicole Harris
544
+ Nikhil Benesch
545
+ Nikhil Ladha
546
+ Nikita Chepanov
547
+ Nikolay Korolev
548
+ Nipunn Koorapati
549
+ Nitesh Sharma
550
+ Niyas Sait
551
+ Noah
552
+ Noah Gorny
553
+ Nowell Strite
554
+ NtaleGrey
555
+ nvdv
556
+ OBITORASU
557
+ Ofek Lev
558
+ ofrinevo
559
+ Oliver Freund
560
+ Oliver Jeeves
561
+ Oliver Mannion
562
+ Oliver Tonnhofer
563
+ Olivier Girardot
564
+ Olivier Grisel
565
+ Ollie Rutherfurd
566
+ OMOTO Kenji
567
+ Omry Yadan
568
+ onlinejudge95
569
+ Oren Held
570
+ Oscar Benjamin
571
+ Oz N Tiram
572
+ Pachwenko
573
+ Patrick Dubroy
574
+ Patrick Jenkins
575
+ Patrick Lawson
576
+ patricktokeeffe
577
+ Patrik Kopkan
578
+ Paul Ganssle
579
+ Paul Kehrer
580
+ Paul Moore
581
+ Paul Nasrat
582
+ Paul Oswald
583
+ Paul van der Linden
584
+ Paulus Schoutsen
585
+ Pavel Safronov
586
+ Pavithra Eswaramoorthy
587
+ Pawel Jasinski
588
+ Paweł Szramowski
589
+ Pekka Klärck
590
+ Peter Gessler
591
+ Peter Lisák
592
+ Peter Shen
593
+ Peter Waller
594
+ Petr Viktorin
595
+ petr-tik
596
+ Phaneendra Chiruvella
597
+ Phil Elson
598
+ Phil Freo
599
+ Phil Pennock
600
+ Phil Whelan
601
+ Philip Jägenstedt
602
+ Philip Molloy
603
+ Philippe Ombredanne
604
+ Pi Delport
605
+ Pierre-Yves Rofes
606
+ Pieter Degroote
607
+ pip
608
+ Prabakaran Kumaresshan
609
+ Prabhjyotsing Surjit Singh Sodhi
610
+ Prabhu Marappan
611
+ Pradyun Gedam
612
+ Prashant Sharma
613
+ Pratik Mallya
614
+ pre-commit-ci[bot]
615
+ Preet Thakkar
616
+ Preston Holmes
617
+ Przemek Wrzos
618
+ Pulkit Goyal
619
+ q0w
620
+ Qiangning Hong
621
+ Qiming Xu
622
+ Quentin Lee
623
+ Quentin Pradet
624
+ R. David Murray
625
+ Rafael Caricio
626
+ Ralf Schmitt
627
+ Ran Benita
628
+ Razzi Abuissa
629
+ rdb
630
+ Reece Dunham
631
+ Remi Rampin
632
+ Rene Dudfield
633
+ Riccardo Magliocchetti
634
+ Riccardo Schirone
635
+ Richard Jones
636
+ Richard Si
637
+ Ricky Ng-Adam
638
+ Rishi
639
+ rmorotti
640
+ RobberPhex
641
+ Robert Collins
642
+ Robert McGibbon
643
+ Robert Pollak
644
+ Robert T. McGibbon
645
+ robin elisha robinson
646
+ Roey Berman
647
+ Rohan Jain
648
+ Roman Bogorodskiy
649
+ Roman Donchenko
650
+ Romuald Brunet
651
+ ronaudinho
652
+ Ronny Pfannschmidt
653
+ Rory McCann
654
+ Ross Brattain
655
+ Roy Wellington Ⅳ
656
+ Ruairidh MacLeod
657
+ Russell Keith-Magee
658
+ Ryan Shepherd
659
+ Ryan Wooden
660
+ ryneeverett
661
+ S. Guliaev
662
+ Sachi King
663
+ Salvatore Rinchiera
664
+ sandeepkiran-js
665
+ Sander Van Balen
666
+ Savio Jomton
667
+ schlamar
668
+ Scott Kitterman
669
+ Sean
670
+ seanj
671
+ Sebastian Jordan
672
+ Sebastian Schaetz
673
+ Segev Finer
674
+ SeongSoo Cho
675
+ Sergey Vasilyev
676
+ Seth Michael Larson
677
+ Seth Woodworth
678
+ Shahar Epstein
679
+ Shantanu
680
+ shenxianpeng
681
+ shireenrao
682
+ Shivansh-007
683
+ Shixian Sheng
684
+ Shlomi Fish
685
+ Shovan Maity
686
+ Simeon Visser
687
+ Simon Cross
688
+ Simon Pichugin
689
+ sinoroc
690
+ sinscary
691
+ snook92
692
+ socketubs
693
+ Sorin Sbarnea
694
+ Srinivas Nyayapati
695
+ Srishti Hegde
696
+ Stavros Korokithakis
697
+ Stefan Scherfke
698
+ Stefano Rivera
699
+ Stephan Erb
700
+ Stephen Rosen
701
+ stepshal
702
+ Steve (Gadget) Barnes
703
+ Steve Barnes
704
+ Steve Dower
705
+ Steve Kowalik
706
+ Steven Myint
707
+ Steven Silvester
708
+ stonebig
709
+ studioj
710
+ Stéphane Bidoul
711
+ Stéphane Bidoul (ACSONE)
712
+ Stéphane Klein
713
+ Sumana Harihareswara
714
+ Surbhi Sharma
715
+ Sviatoslav Sydorenko
716
+ Sviatoslav Sydorenko (Святослав Сидоренко)
717
+ Swat009
718
+ Sylvain
719
+ Takayuki SHIMIZUKAWA
720
+ Taneli Hukkinen
721
+ tbeswick
722
+ Thiago
723
+ Thijs Triemstra
724
+ Thomas Fenzl
725
+ Thomas Grainger
726
+ Thomas Guettler
727
+ Thomas Johansson
728
+ Thomas Kluyver
729
+ Thomas Smith
730
+ Thomas VINCENT
731
+ Tim D. Smith
732
+ Tim Gates
733
+ Tim Harder
734
+ Tim Heap
735
+ tim smith
736
+ tinruufu
737
+ Tobias Hermann
738
+ Tom Forbes
739
+ Tom Freudenheim
740
+ Tom V
741
+ Tomas Hrnciar
742
+ Tomas Orsava
743
+ Tomer Chachamu
744
+ Tommi Enenkel | AnB
745
+ Tomáš Hrnčiar
746
+ Tony Beswick
747
+ Tony Narlock
748
+ Tony Zhaocheng Tan
749
+ TonyBeswick
750
+ toonarmycaptain
751
+ Toshio Kuratomi
752
+ toxinu
753
+ Travis Swicegood
754
+ Tushar Sadhwani
755
+ Tzu-ping Chung
756
+ Valentin Haenel
757
+ Victor Stinner
758
+ victorvpaulo
759
+ Vikram - Google
760
+ Viktor Szépe
761
+ Ville Skyttä
762
+ Vinay Sajip
763
+ Vincent Philippon
764
+ Vinicyus Macedo
765
+ Vipul Kumar
766
+ Vitaly Babiy
767
+ Vladimir Fokow
768
+ Vladimir Rutsky
769
+ W. Trevor King
770
+ Wil Tan
771
+ Wilfred Hughes
772
+ William Edwards
773
+ William ML Leslie
774
+ William T Olson
775
+ William Woodruff
776
+ Wilson Mo
777
+ wim glenn
778
+ Winson Luk
779
+ Wolfgang Maier
780
+ Wu Zhenyu
781
+ XAMES3
782
+ Xavier Fernandez
783
+ Xianpeng Shen
784
+ xoviat
785
+ xtreak
786
+ YAMAMOTO Takashi
787
+ Yen Chi Hsuan
788
+ Yeray Diaz Diaz
789
+ Yoval P
790
+ Yu Jian
791
+ Yuan Jing Vincent Yan
792
+ Yusuke Hayashi
793
+ Zearin
794
+ Zhiping Deng
795
+ ziebam
796
+ Zvezdan Petkovic
797
+ Łukasz Langa
798
+ Роман Донченко
799
+ Семён Марьясин
venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2008-present The pip developers (see AUTHORS.txt file)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/METADATA ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.1
2
+ Name: pip
3
+ Version: 24.3.1
4
+ Summary: The PyPA recommended tool for installing Python packages.
5
+ Author-email: The pip developers <distutils-sig@python.org>
6
+ License: MIT
7
+ Project-URL: Homepage, https://pip.pypa.io/
8
+ Project-URL: Documentation, https://pip.pypa.io
9
+ Project-URL: Source, https://github.com/pypa/pip
10
+ Project-URL: Changelog, https://pip.pypa.io/en/stable/news/
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Topic :: Software Development :: Build Tools
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: Implementation :: CPython
25
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
26
+ Requires-Python: >=3.8
27
+ Description-Content-Type: text/x-rst
28
+ License-File: LICENSE.txt
29
+ License-File: AUTHORS.txt
30
+
31
+ pip - The Python Package Installer
32
+ ==================================
33
+
34
+ .. |pypi-version| image:: https://img.shields.io/pypi/v/pip.svg
35
+ :target: https://pypi.org/project/pip/
36
+ :alt: PyPI
37
+
38
+ .. |python-versions| image:: https://img.shields.io/pypi/pyversions/pip
39
+ :target: https://pypi.org/project/pip
40
+ :alt: PyPI - Python Version
41
+
42
+ .. |docs-badge| image:: https://readthedocs.org/projects/pip/badge/?version=latest
43
+ :target: https://pip.pypa.io/en/latest
44
+ :alt: Documentation
45
+
46
+ |pypi-version| |python-versions| |docs-badge|
47
+
48
+ pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes.
49
+
50
+ Please take a look at our documentation for how to install and use pip:
51
+
52
+ * `Installation`_
53
+ * `Usage`_
54
+
55
+ We release updates regularly, with a new version every 3 months. Find more details in our documentation:
56
+
57
+ * `Release notes`_
58
+ * `Release process`_
59
+
60
+ If you find bugs, need help, or want to talk to the developers, please use our mailing lists or chat rooms:
61
+
62
+ * `Issue tracking`_
63
+ * `Discourse channel`_
64
+ * `User IRC`_
65
+
66
+ If you want to get involved head over to GitHub to get the source code, look at our development documentation and feel free to jump on the developer mailing lists and chat rooms:
67
+
68
+ * `GitHub page`_
69
+ * `Development documentation`_
70
+ * `Development IRC`_
71
+
72
+ Code of Conduct
73
+ ---------------
74
+
75
+ Everyone interacting in the pip project's codebases, issue trackers, chat
76
+ rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
77
+
78
+ .. _package installer: https://packaging.python.org/guides/tool-recommendations/
79
+ .. _Python Package Index: https://pypi.org
80
+ .. _Installation: https://pip.pypa.io/en/stable/installation/
81
+ .. _Usage: https://pip.pypa.io/en/stable/
82
+ .. _Release notes: https://pip.pypa.io/en/stable/news.html
83
+ .. _Release process: https://pip.pypa.io/en/latest/development/release-process/
84
+ .. _GitHub page: https://github.com/pypa/pip
85
+ .. _Development documentation: https://pip.pypa.io/en/latest/development
86
+ .. _Issue tracking: https://github.com/pypa/pip/issues
87
+ .. _Discourse channel: https://discuss.python.org/c/packaging
88
+ .. _User IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa
89
+ .. _Development IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa-dev
90
+ .. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/RECORD ADDED
@@ -0,0 +1,853 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ../../../bin/pip,sha256=-GAEslaMUmzRvnu-5P-uZ9Y9RYbZJJs3TaMHZj_bRUw,259
2
+ ../../../bin/pip3,sha256=-GAEslaMUmzRvnu-5P-uZ9Y9RYbZJJs3TaMHZj_bRUw,259
3
+ ../../../bin/pip3.12,sha256=-GAEslaMUmzRvnu-5P-uZ9Y9RYbZJJs3TaMHZj_bRUw,259
4
+ pip-24.3.1.dist-info/AUTHORS.txt,sha256=Cbb630k8EL9FkBzX9Vpi6hpYWrLSlh08eXodL5u0eLI,10925
5
+ pip-24.3.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
6
+ pip-24.3.1.dist-info/LICENSE.txt,sha256=Y0MApmnUmurmWxLGxIySTFGkzfPR_whtw0VtyLyqIQQ,1093
7
+ pip-24.3.1.dist-info/METADATA,sha256=V8iCNK1GYbC82PWsLMsASDh9AO4veocRlM4Pn9q2KFI,3677
8
+ pip-24.3.1.dist-info/RECORD,,
9
+ pip-24.3.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ pip-24.3.1.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
11
+ pip-24.3.1.dist-info/entry_points.txt,sha256=eeIjuzfnfR2PrhbjnbzFU6MnSS70kZLxwaHHq6M-bD0,87
12
+ pip-24.3.1.dist-info/top_level.txt,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
13
+ pip/__init__.py,sha256=faXY_neeYrA_88plEhkyhwAaYeds7wu5U1iGwP24J0s,357
14
+ pip/__main__.py,sha256=WzbhHXTbSE6gBY19mNN9m4s5o_365LOvTYSgqgbdBhE,854
15
+ pip/__pip-runner__.py,sha256=cPPWuJ6NK_k-GzfvlejLFgwzmYUROmpAR6QC3Q-vkXQ,1450
16
+ pip/__pycache__/__init__.cpython-312.pyc,,
17
+ pip/__pycache__/__main__.cpython-312.pyc,,
18
+ pip/__pycache__/__pip-runner__.cpython-312.pyc,,
19
+ pip/_internal/__init__.py,sha256=MfcoOluDZ8QMCFYal04IqOJ9q6m2V7a0aOsnI-WOxUo,513
20
+ pip/_internal/__pycache__/__init__.cpython-312.pyc,,
21
+ pip/_internal/__pycache__/build_env.cpython-312.pyc,,
22
+ pip/_internal/__pycache__/cache.cpython-312.pyc,,
23
+ pip/_internal/__pycache__/configuration.cpython-312.pyc,,
24
+ pip/_internal/__pycache__/exceptions.cpython-312.pyc,,
25
+ pip/_internal/__pycache__/main.cpython-312.pyc,,
26
+ pip/_internal/__pycache__/pyproject.cpython-312.pyc,,
27
+ pip/_internal/__pycache__/self_outdated_check.cpython-312.pyc,,
28
+ pip/_internal/__pycache__/wheel_builder.cpython-312.pyc,,
29
+ pip/_internal/build_env.py,sha256=wsTPOWyPTKvUREUcO585OU01kbQufpdigY8fVHv3WIw,10584
30
+ pip/_internal/cache.py,sha256=Jb698p5PNigRtpW5o26wQNkkUv4MnQ94mc471wL63A0,10369
31
+ pip/_internal/cli/__init__.py,sha256=FkHBgpxxb-_gd6r1FjnNhfMOzAUYyXoXKJ6abijfcFU,132
32
+ pip/_internal/cli/__pycache__/__init__.cpython-312.pyc,,
33
+ pip/_internal/cli/__pycache__/autocompletion.cpython-312.pyc,,
34
+ pip/_internal/cli/__pycache__/base_command.cpython-312.pyc,,
35
+ pip/_internal/cli/__pycache__/cmdoptions.cpython-312.pyc,,
36
+ pip/_internal/cli/__pycache__/command_context.cpython-312.pyc,,
37
+ pip/_internal/cli/__pycache__/index_command.cpython-312.pyc,,
38
+ pip/_internal/cli/__pycache__/main.cpython-312.pyc,,
39
+ pip/_internal/cli/__pycache__/main_parser.cpython-312.pyc,,
40
+ pip/_internal/cli/__pycache__/parser.cpython-312.pyc,,
41
+ pip/_internal/cli/__pycache__/progress_bars.cpython-312.pyc,,
42
+ pip/_internal/cli/__pycache__/req_command.cpython-312.pyc,,
43
+ pip/_internal/cli/__pycache__/spinners.cpython-312.pyc,,
44
+ pip/_internal/cli/__pycache__/status_codes.cpython-312.pyc,,
45
+ pip/_internal/cli/autocompletion.py,sha256=Lli3Mr6aDNu7ZkJJFFvwD2-hFxNI6Avz8OwMyS5TVrs,6865
46
+ pip/_internal/cli/base_command.py,sha256=F8nUcSM-Y-MQljJUe724-yxmc5viFXHyM_zH70NmIh4,8289
47
+ pip/_internal/cli/cmdoptions.py,sha256=mDqBr0d0hoztbRJs-PWtcKpqNAc7khU6ZpoesZKocT8,30110
48
+ pip/_internal/cli/command_context.py,sha256=RHgIPwtObh5KhMrd3YZTkl8zbVG-6Okml7YbFX4Ehg0,774
49
+ pip/_internal/cli/index_command.py,sha256=-0oPTruZGkLSMrWDleZ6UtcKP3G-SImRRuhH0RfVE3o,5631
50
+ pip/_internal/cli/main.py,sha256=BDZef-bWe9g9Jpr4OVs4dDf-845HJsKw835T7AqEnAc,2817
51
+ pip/_internal/cli/main_parser.py,sha256=laDpsuBDl6kyfywp9eMMA9s84jfH2TJJn-vmL0GG90w,4338
52
+ pip/_internal/cli/parser.py,sha256=VCMtduzECUV87KaHNu-xJ-wLNL82yT3x16V4XBxOAqI,10825
53
+ pip/_internal/cli/progress_bars.py,sha256=VgydyqjZvfhqpuNcFDn00QNuA9GxRe9CKrRG8jhPuKU,2723
54
+ pip/_internal/cli/req_command.py,sha256=DqeFhmUMs6o6Ev8qawAcOoYNdAZsfyKS0MZI5jsJYwQ,12250
55
+ pip/_internal/cli/spinners.py,sha256=hIJ83GerdFgFCdobIA23Jggetegl_uC4Sp586nzFbPE,5118
56
+ pip/_internal/cli/status_codes.py,sha256=sEFHUaUJbqv8iArL3HAtcztWZmGOFX01hTesSytDEh0,116
57
+ pip/_internal/commands/__init__.py,sha256=5oRO9O3dM2vGuh0bFw4HOVletryrz5HHMmmPWwJrH9U,3882
58
+ pip/_internal/commands/__pycache__/__init__.cpython-312.pyc,,
59
+ pip/_internal/commands/__pycache__/cache.cpython-312.pyc,,
60
+ pip/_internal/commands/__pycache__/check.cpython-312.pyc,,
61
+ pip/_internal/commands/__pycache__/completion.cpython-312.pyc,,
62
+ pip/_internal/commands/__pycache__/configuration.cpython-312.pyc,,
63
+ pip/_internal/commands/__pycache__/debug.cpython-312.pyc,,
64
+ pip/_internal/commands/__pycache__/download.cpython-312.pyc,,
65
+ pip/_internal/commands/__pycache__/freeze.cpython-312.pyc,,
66
+ pip/_internal/commands/__pycache__/hash.cpython-312.pyc,,
67
+ pip/_internal/commands/__pycache__/help.cpython-312.pyc,,
68
+ pip/_internal/commands/__pycache__/index.cpython-312.pyc,,
69
+ pip/_internal/commands/__pycache__/inspect.cpython-312.pyc,,
70
+ pip/_internal/commands/__pycache__/install.cpython-312.pyc,,
71
+ pip/_internal/commands/__pycache__/list.cpython-312.pyc,,
72
+ pip/_internal/commands/__pycache__/search.cpython-312.pyc,,
73
+ pip/_internal/commands/__pycache__/show.cpython-312.pyc,,
74
+ pip/_internal/commands/__pycache__/uninstall.cpython-312.pyc,,
75
+ pip/_internal/commands/__pycache__/wheel.cpython-312.pyc,,
76
+ pip/_internal/commands/cache.py,sha256=xg76_ZFEBC6zoQ3gXLRfMZJft4z2a0RwH4GEFZC6nnU,7944
77
+ pip/_internal/commands/check.py,sha256=Hr_4eiMd9cgVDgEvjtIdw915NmL7ROIWW8enkr8slPQ,2268
78
+ pip/_internal/commands/completion.py,sha256=HT4lD0bgsflHq2IDgYfiEdp7IGGtE7s6MgI3xn0VQEw,4287
79
+ pip/_internal/commands/configuration.py,sha256=n98enwp6y0b5G6fiRQjaZo43FlJKYve_daMhN-4BRNc,9766
80
+ pip/_internal/commands/debug.py,sha256=DNDRgE9YsKrbYzU0s3VKi8rHtKF4X13CJ_br_8PUXO0,6797
81
+ pip/_internal/commands/download.py,sha256=0qB0nys6ZEPsog451lDsjL5Bx7Z97t-B80oFZKhpzKM,5273
82
+ pip/_internal/commands/freeze.py,sha256=2Vt72BYTSm9rzue6d8dNzt8idxWK4Db6Hd-anq7GQ80,3203
83
+ pip/_internal/commands/hash.py,sha256=EVVOuvGtoPEdFi8SNnmdqlCQrhCxV-kJsdwtdcCnXGQ,1703
84
+ pip/_internal/commands/help.py,sha256=gcc6QDkcgHMOuAn5UxaZwAStsRBrnGSn_yxjS57JIoM,1132
85
+ pip/_internal/commands/index.py,sha256=RAXxmJwFhVb5S1BYzb5ifX3sn9Na8v2CCVYwSMP8pao,4731
86
+ pip/_internal/commands/inspect.py,sha256=PGrY9TRTRCM3y5Ml8Bdk8DEOXquWRfscr4DRo1LOTPc,3189
87
+ pip/_internal/commands/install.py,sha256=iqesiLIZc6Op9uihMQFYRhAA2DQRZUxbM4z1BwXoFls,29428
88
+ pip/_internal/commands/list.py,sha256=oiIzSjLP6__d7dIS3q0Xb5ywsaOThBWRqMyjjKzkPdM,12769
89
+ pip/_internal/commands/search.py,sha256=fWkUQVx_gm8ebbFAlCgqtxKXT9rNahpJ-BI__3HNZpg,5626
90
+ pip/_internal/commands/show.py,sha256=IG9L5uo8w6UA4tI_IlmaxLCoNKPa5JNJCljj3NWs0OE,7507
91
+ pip/_internal/commands/uninstall.py,sha256=7pOR7enK76gimyxQbzxcG1OsyLXL3DvX939xmM8Fvtg,3892
92
+ pip/_internal/commands/wheel.py,sha256=eJRhr_qoNNxWAkkdJCNiQM7CXd4E1_YyQhsqJnBPGGg,6414
93
+ pip/_internal/configuration.py,sha256=XkAiBS0hpzsM-LF0Qu5hvPWO_Bs67-oQKRYFBuMbESs,14006
94
+ pip/_internal/distributions/__init__.py,sha256=Hq6kt6gXBgjNit5hTTWLAzeCNOKoB-N0pGYSqehrli8,858
95
+ pip/_internal/distributions/__pycache__/__init__.cpython-312.pyc,,
96
+ pip/_internal/distributions/__pycache__/base.cpython-312.pyc,,
97
+ pip/_internal/distributions/__pycache__/installed.cpython-312.pyc,,
98
+ pip/_internal/distributions/__pycache__/sdist.cpython-312.pyc,,
99
+ pip/_internal/distributions/__pycache__/wheel.cpython-312.pyc,,
100
+ pip/_internal/distributions/base.py,sha256=QeB9qvKXDIjLdPBDE5fMgpfGqMMCr-govnuoQnGuiF8,1783
101
+ pip/_internal/distributions/installed.py,sha256=QinHFbWAQ8oE0pbD8MFZWkwlnfU1QYTccA1vnhrlYOU,842
102
+ pip/_internal/distributions/sdist.py,sha256=PlcP4a6-R6c98XnOM-b6Lkb3rsvh9iG4ok8shaanrzs,6751
103
+ pip/_internal/distributions/wheel.py,sha256=THBYfnv7VVt8mYhMYUtH13S1E7FDwtDyDfmUcl8ai0E,1317
104
+ pip/_internal/exceptions.py,sha256=2_byISIv3kSnI_9T-Esfxrt0LnTRgcUHyxu0twsHjQY,26481
105
+ pip/_internal/index/__init__.py,sha256=vpt-JeTZefh8a-FC22ZeBSXFVbuBcXSGiILhQZJaNpQ,30
106
+ pip/_internal/index/__pycache__/__init__.cpython-312.pyc,,
107
+ pip/_internal/index/__pycache__/collector.cpython-312.pyc,,
108
+ pip/_internal/index/__pycache__/package_finder.cpython-312.pyc,,
109
+ pip/_internal/index/__pycache__/sources.cpython-312.pyc,,
110
+ pip/_internal/index/collector.py,sha256=RdPO0JLAlmyBWPAWYHPyRoGjz3GNAeTngCNkbGey_mE,16265
111
+ pip/_internal/index/package_finder.py,sha256=yRC4xsyudwKnNoU6IXvNoyqYo5ScT7lB6Wa-z2eh7cs,37666
112
+ pip/_internal/index/sources.py,sha256=lPBLK5Xiy8Q6IQMio26Wl7ocfZOKkgGklIBNyUJ23fI,8632
113
+ pip/_internal/locations/__init__.py,sha256=UaAxeZ_f93FyouuFf4p7SXYF-4WstXuEvd3LbmPCAno,14925
114
+ pip/_internal/locations/__pycache__/__init__.cpython-312.pyc,,
115
+ pip/_internal/locations/__pycache__/_distutils.cpython-312.pyc,,
116
+ pip/_internal/locations/__pycache__/_sysconfig.cpython-312.pyc,,
117
+ pip/_internal/locations/__pycache__/base.cpython-312.pyc,,
118
+ pip/_internal/locations/_distutils.py,sha256=x6nyVLj7X11Y4khIdf-mFlxMl2FWadtVEgeb8upc_WI,6013
119
+ pip/_internal/locations/_sysconfig.py,sha256=IGzds60qsFneRogC-oeBaY7bEh3lPt_v47kMJChQXsU,7724
120
+ pip/_internal/locations/base.py,sha256=RQiPi1d4FVM2Bxk04dQhXZ2PqkeljEL2fZZ9SYqIQ78,2556
121
+ pip/_internal/main.py,sha256=r-UnUe8HLo5XFJz8inTcOOTiu_sxNhgHb6VwlGUllOI,340
122
+ pip/_internal/metadata/__init__.py,sha256=9pU3W3s-6HtjFuYhWcLTYVmSaziklPv7k2x8p7X1GmA,4339
123
+ pip/_internal/metadata/__pycache__/__init__.cpython-312.pyc,,
124
+ pip/_internal/metadata/__pycache__/_json.cpython-312.pyc,,
125
+ pip/_internal/metadata/__pycache__/base.cpython-312.pyc,,
126
+ pip/_internal/metadata/__pycache__/pkg_resources.cpython-312.pyc,,
127
+ pip/_internal/metadata/_json.py,sha256=P0cAJrH_mtmMZvlZ16ZXm_-izA4lpr5wy08laICuiaA,2644
128
+ pip/_internal/metadata/base.py,sha256=ft0K5XNgI4ETqZnRv2-CtvgYiMOMAeGMAzxT-f6VLJA,25298
129
+ pip/_internal/metadata/importlib/__init__.py,sha256=jUUidoxnHcfITHHaAWG1G2i5fdBYklv_uJcjo2x7VYE,135
130
+ pip/_internal/metadata/importlib/__pycache__/__init__.cpython-312.pyc,,
131
+ pip/_internal/metadata/importlib/__pycache__/_compat.cpython-312.pyc,,
132
+ pip/_internal/metadata/importlib/__pycache__/_dists.cpython-312.pyc,,
133
+ pip/_internal/metadata/importlib/__pycache__/_envs.cpython-312.pyc,,
134
+ pip/_internal/metadata/importlib/_compat.py,sha256=c6av8sP8BBjAZuFSJow1iWfygUXNM3xRTCn5nqw6B9M,2796
135
+ pip/_internal/metadata/importlib/_dists.py,sha256=anh0mLI-FYRPUhAdipd0Va3YJJc6HelCKQ0bFhY10a0,8017
136
+ pip/_internal/metadata/importlib/_envs.py,sha256=UUB980XSrDWrMpQ1_G45i0r8Hqlg_tg3IPQ63mEqbNc,7431
137
+ pip/_internal/metadata/pkg_resources.py,sha256=U07ETAINSGeSRBfWUG93E4tZZbaW_f7PGzEqZN0hulc,10542
138
+ pip/_internal/models/__init__.py,sha256=3DHUd_qxpPozfzouoqa9g9ts1Czr5qaHfFxbnxriepM,63
139
+ pip/_internal/models/__pycache__/__init__.cpython-312.pyc,,
140
+ pip/_internal/models/__pycache__/candidate.cpython-312.pyc,,
141
+ pip/_internal/models/__pycache__/direct_url.cpython-312.pyc,,
142
+ pip/_internal/models/__pycache__/format_control.cpython-312.pyc,,
143
+ pip/_internal/models/__pycache__/index.cpython-312.pyc,,
144
+ pip/_internal/models/__pycache__/installation_report.cpython-312.pyc,,
145
+ pip/_internal/models/__pycache__/link.cpython-312.pyc,,
146
+ pip/_internal/models/__pycache__/scheme.cpython-312.pyc,,
147
+ pip/_internal/models/__pycache__/search_scope.cpython-312.pyc,,
148
+ pip/_internal/models/__pycache__/selection_prefs.cpython-312.pyc,,
149
+ pip/_internal/models/__pycache__/target_python.cpython-312.pyc,,
150
+ pip/_internal/models/__pycache__/wheel.cpython-312.pyc,,
151
+ pip/_internal/models/candidate.py,sha256=zzgFRuw_kWPjKpGw7LC0ZUMD2CQ2EberUIYs8izjdCA,753
152
+ pip/_internal/models/direct_url.py,sha256=uBtY2HHd3TO9cKQJWh0ThvE5FRr-MWRYChRU4IG9HZE,6578
153
+ pip/_internal/models/format_control.py,sha256=wtsQqSK9HaUiNxQEuB-C62eVimw6G4_VQFxV9-_KDBE,2486
154
+ pip/_internal/models/index.py,sha256=tYnL8oxGi4aSNWur0mG8DAP7rC6yuha_MwJO8xw0crI,1030
155
+ pip/_internal/models/installation_report.py,sha256=zRVZoaz-2vsrezj_H3hLOhMZCK9c7TbzWgC-jOalD00,2818
156
+ pip/_internal/models/link.py,sha256=jHax9O-9zlSzEwjBCDkx0OXjKXwBDwOuPwn-PsR8dCs,21034
157
+ pip/_internal/models/scheme.py,sha256=PakmHJM3e8OOWSZFtfz1Az7f1meONJnkGuQxFlt3wBE,575
158
+ pip/_internal/models/search_scope.py,sha256=67NEnsYY84784S-MM7ekQuo9KXLH-7MzFntXjapvAo0,4531
159
+ pip/_internal/models/selection_prefs.py,sha256=qaFfDs3ciqoXPg6xx45N1jPLqccLJw4N0s4P0PyHTQ8,2015
160
+ pip/_internal/models/target_python.py,sha256=2XaH2rZ5ZF-K5wcJbEMGEl7SqrTToDDNkrtQ2v_v_-Q,4271
161
+ pip/_internal/models/wheel.py,sha256=G7dND_s4ebPkEL7RJ1qCY0QhUUWIIK6AnjWgRATF5no,4539
162
+ pip/_internal/network/__init__.py,sha256=jf6Tt5nV_7zkARBrKojIXItgejvoegVJVKUbhAa5Ioc,50
163
+ pip/_internal/network/__pycache__/__init__.cpython-312.pyc,,
164
+ pip/_internal/network/__pycache__/auth.cpython-312.pyc,,
165
+ pip/_internal/network/__pycache__/cache.cpython-312.pyc,,
166
+ pip/_internal/network/__pycache__/download.cpython-312.pyc,,
167
+ pip/_internal/network/__pycache__/lazy_wheel.cpython-312.pyc,,
168
+ pip/_internal/network/__pycache__/session.cpython-312.pyc,,
169
+ pip/_internal/network/__pycache__/utils.cpython-312.pyc,,
170
+ pip/_internal/network/__pycache__/xmlrpc.cpython-312.pyc,,
171
+ pip/_internal/network/auth.py,sha256=D4gASjUrqoDFlSt6gQ767KAAjv6PUyJU0puDlhXNVRE,20809
172
+ pip/_internal/network/cache.py,sha256=48A971qCzKNFvkb57uGEk7-0xaqPS0HWj2711QNTxkU,3935
173
+ pip/_internal/network/download.py,sha256=FLOP29dPYECBiAi7eEjvAbNkyzaKNqbyjOT2m8HPW8U,6048
174
+ pip/_internal/network/lazy_wheel.py,sha256=PBdoMoNQQIA84Fhgne38jWF52W4x_KtsHjxgv4dkRKA,7622
175
+ pip/_internal/network/session.py,sha256=XmanBKjVwPFmh1iJ58q6TDh9xabH37gREuQJ_feuZGA,18741
176
+ pip/_internal/network/utils.py,sha256=Inaxel-NxBu4PQWkjyErdnfewsFCcgHph7dzR1-FboY,4088
177
+ pip/_internal/network/xmlrpc.py,sha256=sAxzOacJ-N1NXGPvap9jC3zuYWSnnv3GXtgR2-E2APA,1838
178
+ pip/_internal/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
179
+ pip/_internal/operations/__pycache__/__init__.cpython-312.pyc,,
180
+ pip/_internal/operations/__pycache__/check.cpython-312.pyc,,
181
+ pip/_internal/operations/__pycache__/freeze.cpython-312.pyc,,
182
+ pip/_internal/operations/__pycache__/prepare.cpython-312.pyc,,
183
+ pip/_internal/operations/build/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
184
+ pip/_internal/operations/build/__pycache__/__init__.cpython-312.pyc,,
185
+ pip/_internal/operations/build/__pycache__/build_tracker.cpython-312.pyc,,
186
+ pip/_internal/operations/build/__pycache__/metadata.cpython-312.pyc,,
187
+ pip/_internal/operations/build/__pycache__/metadata_editable.cpython-312.pyc,,
188
+ pip/_internal/operations/build/__pycache__/metadata_legacy.cpython-312.pyc,,
189
+ pip/_internal/operations/build/__pycache__/wheel.cpython-312.pyc,,
190
+ pip/_internal/operations/build/__pycache__/wheel_editable.cpython-312.pyc,,
191
+ pip/_internal/operations/build/__pycache__/wheel_legacy.cpython-312.pyc,,
192
+ pip/_internal/operations/build/build_tracker.py,sha256=-ARW_TcjHCOX7D2NUOGntB4Fgc6b4aolsXkAK6BWL7w,4774
193
+ pip/_internal/operations/build/metadata.py,sha256=9S0CUD8U3QqZeXp-Zyt8HxwU90lE4QrnYDgrqZDzBnc,1422
194
+ pip/_internal/operations/build/metadata_editable.py,sha256=VLL7LvntKE8qxdhUdEJhcotFzUsOSI8NNS043xULKew,1474
195
+ pip/_internal/operations/build/metadata_legacy.py,sha256=8i6i1QZX9m_lKPStEFsHKM0MT4a-CD408JOw99daLmo,2190
196
+ pip/_internal/operations/build/wheel.py,sha256=sT12FBLAxDC6wyrDorh8kvcZ1jG5qInCRWzzP-UkJiQ,1075
197
+ pip/_internal/operations/build/wheel_editable.py,sha256=yOtoH6zpAkoKYEUtr8FhzrYnkNHQaQBjWQ2HYae1MQg,1417
198
+ pip/_internal/operations/build/wheel_legacy.py,sha256=K-6kNhmj-1xDF45ny1yheMerF0ui4EoQCLzEoHh6-tc,3045
199
+ pip/_internal/operations/check.py,sha256=L24vRL8VWbyywdoeAhM89WCd8zLTnjIbULlKelUgIec,5912
200
+ pip/_internal/operations/freeze.py,sha256=V59yEyCSz_YhZuhH09-6aV_zvYBMrS_IxFFNqn2QzlA,9864
201
+ pip/_internal/operations/install/__init__.py,sha256=mX7hyD2GNBO2mFGokDQ30r_GXv7Y_PLdtxcUv144e-s,51
202
+ pip/_internal/operations/install/__pycache__/__init__.cpython-312.pyc,,
203
+ pip/_internal/operations/install/__pycache__/editable_legacy.cpython-312.pyc,,
204
+ pip/_internal/operations/install/__pycache__/wheel.cpython-312.pyc,,
205
+ pip/_internal/operations/install/editable_legacy.py,sha256=PoEsNEPGbIZ2yQphPsmYTKLOCMs4gv5OcCdzW124NcA,1283
206
+ pip/_internal/operations/install/wheel.py,sha256=X5Iz9yUg5LlK5VNQ9g2ikc6dcRu8EPi_SUi5iuEDRgo,27615
207
+ pip/_internal/operations/prepare.py,sha256=joWJwPkuqGscQgVNImLK71e9hRapwKvRCM8HclysmvU,28118
208
+ pip/_internal/pyproject.py,sha256=rw4fwlptDp1hZgYoplwbAGwWA32sWQkp7ysf8Ju6iXc,7287
209
+ pip/_internal/req/__init__.py,sha256=HxBFtZy_BbCclLgr26waMtpzYdO5T3vxePvpGAXSt5s,2653
210
+ pip/_internal/req/__pycache__/__init__.cpython-312.pyc,,
211
+ pip/_internal/req/__pycache__/constructors.cpython-312.pyc,,
212
+ pip/_internal/req/__pycache__/req_file.cpython-312.pyc,,
213
+ pip/_internal/req/__pycache__/req_install.cpython-312.pyc,,
214
+ pip/_internal/req/__pycache__/req_set.cpython-312.pyc,,
215
+ pip/_internal/req/__pycache__/req_uninstall.cpython-312.pyc,,
216
+ pip/_internal/req/constructors.py,sha256=v1qzCN1mIldwx-nCrPc8JO4lxkm3Fv8M5RWvt8LISjc,18430
217
+ pip/_internal/req/req_file.py,sha256=gOOJTzL-mDRPcQhjwqjDrjn4V-3rK9TnEFnU3v8RA4Q,18752
218
+ pip/_internal/req/req_install.py,sha256=yhT98NGDoAEk03jznTJnYCznzhiMEEA2ocgsUG_dcNU,35788
219
+ pip/_internal/req/req_set.py,sha256=j3esG0s6SzoVReX9rWn4rpYNtyET_fwxbwJPRimvRxo,2858
220
+ pip/_internal/req/req_uninstall.py,sha256=qzDIxJo-OETWqGais7tSMCDcWbATYABT-Tid3ityF0s,23853
221
+ pip/_internal/resolution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
222
+ pip/_internal/resolution/__pycache__/__init__.cpython-312.pyc,,
223
+ pip/_internal/resolution/__pycache__/base.cpython-312.pyc,,
224
+ pip/_internal/resolution/base.py,sha256=qlmh325SBVfvG6Me9gc5Nsh5sdwHBwzHBq6aEXtKsLA,583
225
+ pip/_internal/resolution/legacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
226
+ pip/_internal/resolution/legacy/__pycache__/__init__.cpython-312.pyc,,
227
+ pip/_internal/resolution/legacy/__pycache__/resolver.cpython-312.pyc,,
228
+ pip/_internal/resolution/legacy/resolver.py,sha256=3HZiJBRd1FTN6jQpI4qRO8-TbLYeIbUTS6PFvXnXs2w,24068
229
+ pip/_internal/resolution/resolvelib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
230
+ pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-312.pyc,,
231
+ pip/_internal/resolution/resolvelib/__pycache__/base.cpython-312.pyc,,
232
+ pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-312.pyc,,
233
+ pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-312.pyc,,
234
+ pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-312.pyc,,
235
+ pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-312.pyc,,
236
+ pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-312.pyc,,
237
+ pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-312.pyc,,
238
+ pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-312.pyc,,
239
+ pip/_internal/resolution/resolvelib/base.py,sha256=DCf669FsqyQY5uqXeePDHQY1e4QO-pBzWH8O0s9-K94,5023
240
+ pip/_internal/resolution/resolvelib/candidates.py,sha256=5UZ1upNnmqsP-nmEZaDYxaBgCoejw_e2WVGmmAvBxXc,20001
241
+ pip/_internal/resolution/resolvelib/factory.py,sha256=511CaUR41LqjALuFafLVfx15WRvMhxYTdjQCoSvp4gw,32661
242
+ pip/_internal/resolution/resolvelib/found_candidates.py,sha256=9hrTyQqFvl9I7Tji79F1AxHv39Qh1rkJ_7deSHSMfQc,6383
243
+ pip/_internal/resolution/resolvelib/provider.py,sha256=bcsFnYvlmtB80cwVdW1fIwgol8ZNr1f1VHyRTkz47SM,9935
244
+ pip/_internal/resolution/resolvelib/reporter.py,sha256=00JtoXEkTlw0-rl_sl54d71avwOsJHt9GGHcrj5Sza0,3168
245
+ pip/_internal/resolution/resolvelib/requirements.py,sha256=7JG4Z72e5Yk4vU0S5ulGvbqTy4FMQGYhY5zQhX9zTtY,8065
246
+ pip/_internal/resolution/resolvelib/resolver.py,sha256=nLJOsVMEVi2gQUVJoUFKMZAeu2f7GRMjGMvNSWyz0Bc,12592
247
+ pip/_internal/self_outdated_check.py,sha256=pkjQixuWyQ1vrVxZAaYD6SSHgXuFUnHZybXEWTkh0S0,8145
248
+ pip/_internal/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
249
+ pip/_internal/utils/__pycache__/__init__.cpython-312.pyc,,
250
+ pip/_internal/utils/__pycache__/_jaraco_text.cpython-312.pyc,,
251
+ pip/_internal/utils/__pycache__/_log.cpython-312.pyc,,
252
+ pip/_internal/utils/__pycache__/appdirs.cpython-312.pyc,,
253
+ pip/_internal/utils/__pycache__/compat.cpython-312.pyc,,
254
+ pip/_internal/utils/__pycache__/compatibility_tags.cpython-312.pyc,,
255
+ pip/_internal/utils/__pycache__/datetime.cpython-312.pyc,,
256
+ pip/_internal/utils/__pycache__/deprecation.cpython-312.pyc,,
257
+ pip/_internal/utils/__pycache__/direct_url_helpers.cpython-312.pyc,,
258
+ pip/_internal/utils/__pycache__/egg_link.cpython-312.pyc,,
259
+ pip/_internal/utils/__pycache__/encoding.cpython-312.pyc,,
260
+ pip/_internal/utils/__pycache__/entrypoints.cpython-312.pyc,,
261
+ pip/_internal/utils/__pycache__/filesystem.cpython-312.pyc,,
262
+ pip/_internal/utils/__pycache__/filetypes.cpython-312.pyc,,
263
+ pip/_internal/utils/__pycache__/glibc.cpython-312.pyc,,
264
+ pip/_internal/utils/__pycache__/hashes.cpython-312.pyc,,
265
+ pip/_internal/utils/__pycache__/logging.cpython-312.pyc,,
266
+ pip/_internal/utils/__pycache__/misc.cpython-312.pyc,,
267
+ pip/_internal/utils/__pycache__/packaging.cpython-312.pyc,,
268
+ pip/_internal/utils/__pycache__/retry.cpython-312.pyc,,
269
+ pip/_internal/utils/__pycache__/setuptools_build.cpython-312.pyc,,
270
+ pip/_internal/utils/__pycache__/subprocess.cpython-312.pyc,,
271
+ pip/_internal/utils/__pycache__/temp_dir.cpython-312.pyc,,
272
+ pip/_internal/utils/__pycache__/unpacking.cpython-312.pyc,,
273
+ pip/_internal/utils/__pycache__/urls.cpython-312.pyc,,
274
+ pip/_internal/utils/__pycache__/virtualenv.cpython-312.pyc,,
275
+ pip/_internal/utils/__pycache__/wheel.cpython-312.pyc,,
276
+ pip/_internal/utils/_jaraco_text.py,sha256=M15uUPIh5NpP1tdUGBxRau6q1ZAEtI8-XyLEETscFfE,3350
277
+ pip/_internal/utils/_log.py,sha256=-jHLOE_THaZz5BFcCnoSL9EYAtJ0nXem49s9of4jvKw,1015
278
+ pip/_internal/utils/appdirs.py,sha256=swgcTKOm3daLeXTW6v5BUS2Ti2RvEnGRQYH_yDXklAo,1665
279
+ pip/_internal/utils/compat.py,sha256=ckkFveBiYQjRWjkNsajt_oWPS57tJvE8XxoC4OIYgCY,2399
280
+ pip/_internal/utils/compatibility_tags.py,sha256=OWq5axHpW-MEEPztGdvgADrgJPAcV9a88Rxm4Z8VBs8,6272
281
+ pip/_internal/utils/datetime.py,sha256=m21Y3wAtQc-ji6Veb6k_M5g6A0ZyFI4egchTdnwh-pQ,242
282
+ pip/_internal/utils/deprecation.py,sha256=k7Qg_UBAaaTdyq82YVARA6D7RmcGTXGv7fnfcgigj4Q,3707
283
+ pip/_internal/utils/direct_url_helpers.py,sha256=r2MRtkVDACv9AGqYODBUC9CjwgtsUU1s68hmgfCJMtA,3196
284
+ pip/_internal/utils/egg_link.py,sha256=0FePZoUYKv4RGQ2t6x7w5Z427wbA_Uo3WZnAkrgsuqo,2463
285
+ pip/_internal/utils/encoding.py,sha256=qqsXDtiwMIjXMEiIVSaOjwH5YmirCaK-dIzb6-XJsL0,1169
286
+ pip/_internal/utils/entrypoints.py,sha256=YlhLTRl2oHBAuqhc-zmL7USS67TPWVHImjeAQHreZTQ,3064
287
+ pip/_internal/utils/filesystem.py,sha256=ajvA-q4ocliW9kPp8Yquh-4vssXbu-UKbo5FV9V4X64,4950
288
+ pip/_internal/utils/filetypes.py,sha256=i8XAQ0eFCog26Fw9yV0Yb1ygAqKYB1w9Cz9n0fj8gZU,716
289
+ pip/_internal/utils/glibc.py,sha256=vUkWq_1pJuzcYNcGKLlQmABoUiisK8noYY1yc8Wq4w4,3734
290
+ pip/_internal/utils/hashes.py,sha256=XGGLL0AG8-RhWnyz87xF6MFZ--BKadHU35D47eApCKI,4972
291
+ pip/_internal/utils/logging.py,sha256=7BFKB1uFjdxD5crM-GtwA5T2qjbQ2LPD-gJDuJeDNTg,11606
292
+ pip/_internal/utils/misc.py,sha256=NRV0_2fFhzy1jhvInSBv4dqCmTwct8PV7Kp0m-BPRGM,23530
293
+ pip/_internal/utils/packaging.py,sha256=iI3LH43lVNR4hWBOqF6lFsZq4aycb2j0UcHlmDmcqUg,2109
294
+ pip/_internal/utils/retry.py,sha256=mhFbykXjhTnZfgzeuy-vl9c8nECnYn_CMtwNJX2tYzQ,1392
295
+ pip/_internal/utils/setuptools_build.py,sha256=ouXpud-jeS8xPyTPsXJ-m34NPvK5os45otAzdSV_IJE,4435
296
+ pip/_internal/utils/subprocess.py,sha256=EsvqSRiSMHF98T8Txmu6NLU3U--MpTTQjtNgKP0P--M,8988
297
+ pip/_internal/utils/temp_dir.py,sha256=5qOXe8M4JeY6vaFQM867d5zkp1bSwMZ-KT5jymmP0Zg,9310
298
+ pip/_internal/utils/unpacking.py,sha256=eyDkSsk4nW8ZfiSjNzJduCznpHyaGHVv3ak_LMGsiEM,11951
299
+ pip/_internal/utils/urls.py,sha256=qceSOZb5lbNDrHNsv7_S4L4Ytszja5NwPKUMnZHbYnM,1599
300
+ pip/_internal/utils/virtualenv.py,sha256=S6f7csYorRpiD6cvn3jISZYc3I8PJC43H5iMFpRAEDU,3456
301
+ pip/_internal/utils/wheel.py,sha256=b442jkydFHjXzDy6cMR7MpzWBJ1Q82hR5F33cmcHV3g,4494
302
+ pip/_internal/vcs/__init__.py,sha256=UAqvzpbi0VbZo3Ub6skEeZAw-ooIZR-zX_WpCbxyCoU,596
303
+ pip/_internal/vcs/__pycache__/__init__.cpython-312.pyc,,
304
+ pip/_internal/vcs/__pycache__/bazaar.cpython-312.pyc,,
305
+ pip/_internal/vcs/__pycache__/git.cpython-312.pyc,,
306
+ pip/_internal/vcs/__pycache__/mercurial.cpython-312.pyc,,
307
+ pip/_internal/vcs/__pycache__/subversion.cpython-312.pyc,,
308
+ pip/_internal/vcs/__pycache__/versioncontrol.cpython-312.pyc,,
309
+ pip/_internal/vcs/bazaar.py,sha256=EKStcQaKpNu0NK4p5Q10Oc4xb3DUxFw024XrJy40bFQ,3528
310
+ pip/_internal/vcs/git.py,sha256=3tpc9LQA_J4IVW5r5NvWaaSeDzcmJOrSFZN0J8vIKfU,18177
311
+ pip/_internal/vcs/mercurial.py,sha256=oULOhzJ2Uie-06d1omkL-_Gc6meGaUkyogvqG9ZCyPs,5249
312
+ pip/_internal/vcs/subversion.py,sha256=ddTugHBqHzV3ebKlU5QXHPN4gUqlyXbOx8q8NgXKvs8,11735
313
+ pip/_internal/vcs/versioncontrol.py,sha256=cvf_-hnTAjQLXJ3d17FMNhQfcO1AcKWUF10tfrYyP-c,22440
314
+ pip/_internal/wheel_builder.py,sha256=DL3A8LKeRj_ACp11WS5wSgASgPFqeyAeXJKdXfmaWXU,11799
315
+ pip/_vendor/__init__.py,sha256=JYuAXvClhInxIrA2FTp5p-uuWVL7WV6-vEpTs46-Qh4,4873
316
+ pip/_vendor/__pycache__/__init__.cpython-312.pyc,,
317
+ pip/_vendor/__pycache__/typing_extensions.cpython-312.pyc,,
318
+ pip/_vendor/cachecontrol/__init__.py,sha256=GiYoagwPEiJ_xR_lbwWGaoCiPtF_rz4isjfjdDAgHU4,676
319
+ pip/_vendor/cachecontrol/__pycache__/__init__.cpython-312.pyc,,
320
+ pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-312.pyc,,
321
+ pip/_vendor/cachecontrol/__pycache__/adapter.cpython-312.pyc,,
322
+ pip/_vendor/cachecontrol/__pycache__/cache.cpython-312.pyc,,
323
+ pip/_vendor/cachecontrol/__pycache__/controller.cpython-312.pyc,,
324
+ pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-312.pyc,,
325
+ pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-312.pyc,,
326
+ pip/_vendor/cachecontrol/__pycache__/serialize.cpython-312.pyc,,
327
+ pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-312.pyc,,
328
+ pip/_vendor/cachecontrol/_cmd.py,sha256=iist2EpzJvDVIhMAxXq8iFnTBsiZAd6iplxfmNboNyk,1737
329
+ pip/_vendor/cachecontrol/adapter.py,sha256=fByO_Pd_EOemjWbuocvBWdN85xT0q_TBm2lxS6vD4fk,6355
330
+ pip/_vendor/cachecontrol/cache.py,sha256=OTQj72tUf8C1uEgczdl3Gc8vkldSzsTITKtDGKMx4z8,1952
331
+ pip/_vendor/cachecontrol/caches/__init__.py,sha256=dtrrroK5BnADR1GWjCZ19aZ0tFsMfvFBtLQQU1sp_ag,303
332
+ pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-312.pyc,,
333
+ pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-312.pyc,,
334
+ pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-312.pyc,,
335
+ pip/_vendor/cachecontrol/caches/file_cache.py,sha256=9AlmmTJc6cslb6k5z_6q0sGPHVrMj8zv-uWy-simmfE,5406
336
+ pip/_vendor/cachecontrol/caches/redis_cache.py,sha256=9rmqwtYu_ljVkW6_oLqbC7EaX_a8YT_yLuna-eS0dgo,1386
337
+ pip/_vendor/cachecontrol/controller.py,sha256=o-ejGJlBmpKK8QQLyTPJj0t7siU8XVHXuV8MCybCxQ8,18575
338
+ pip/_vendor/cachecontrol/filewrapper.py,sha256=STttGmIPBvZzt2b51dUOwoWX5crcMCpKZOisM3f5BNc,4292
339
+ pip/_vendor/cachecontrol/heuristics.py,sha256=IYe4QmHERWsMvtxNrp920WeaIsaTTyqLB14DSheSbtY,4834
340
+ pip/_vendor/cachecontrol/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
341
+ pip/_vendor/cachecontrol/serialize.py,sha256=HQd2IllQ05HzPkVLMXTF2uX5mjEQjDBkxCqUJUODpZk,5163
342
+ pip/_vendor/cachecontrol/wrapper.py,sha256=hsGc7g8QGQTT-4f8tgz3AM5qwScg6FO0BSdLSRdEvpU,1417
343
+ pip/_vendor/certifi/__init__.py,sha256=p_GYZrjUwPBUhpLlCZoGb0miKBKSqDAyZC5DvIuqbHQ,94
344
+ pip/_vendor/certifi/__main__.py,sha256=1k3Cr95vCxxGRGDljrW3wMdpZdL3Nhf0u1n-k2qdsCY,255
345
+ pip/_vendor/certifi/__pycache__/__init__.cpython-312.pyc,,
346
+ pip/_vendor/certifi/__pycache__/__main__.cpython-312.pyc,,
347
+ pip/_vendor/certifi/__pycache__/core.cpython-312.pyc,,
348
+ pip/_vendor/certifi/cacert.pem,sha256=lO3rZukXdPyuk6BWUJFOKQliWaXH6HGh9l1GGrUgG0c,299427
349
+ pip/_vendor/certifi/core.py,sha256=2SRT5rIcQChFDbe37BQa-kULxAgJ8qN6l1jfqTp4HIs,4486
350
+ pip/_vendor/certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
351
+ pip/_vendor/distlib/__init__.py,sha256=dcwgYGYGQqAEawBXPDtIx80DO_3cOmFv8HTc8JMzknQ,625
352
+ pip/_vendor/distlib/__pycache__/__init__.cpython-312.pyc,,
353
+ pip/_vendor/distlib/__pycache__/compat.cpython-312.pyc,,
354
+ pip/_vendor/distlib/__pycache__/database.cpython-312.pyc,,
355
+ pip/_vendor/distlib/__pycache__/index.cpython-312.pyc,,
356
+ pip/_vendor/distlib/__pycache__/locators.cpython-312.pyc,,
357
+ pip/_vendor/distlib/__pycache__/manifest.cpython-312.pyc,,
358
+ pip/_vendor/distlib/__pycache__/markers.cpython-312.pyc,,
359
+ pip/_vendor/distlib/__pycache__/metadata.cpython-312.pyc,,
360
+ pip/_vendor/distlib/__pycache__/resources.cpython-312.pyc,,
361
+ pip/_vendor/distlib/__pycache__/scripts.cpython-312.pyc,,
362
+ pip/_vendor/distlib/__pycache__/util.cpython-312.pyc,,
363
+ pip/_vendor/distlib/__pycache__/version.cpython-312.pyc,,
364
+ pip/_vendor/distlib/__pycache__/wheel.cpython-312.pyc,,
365
+ pip/_vendor/distlib/compat.py,sha256=2jRSjRI4o-vlXeTK2BCGIUhkc6e9ZGhSsacRM5oseTw,41467
366
+ pip/_vendor/distlib/database.py,sha256=mHy_LxiXIsIVRb-T0-idBrVLw3Ffij5teHCpbjmJ9YU,51160
367
+ pip/_vendor/distlib/index.py,sha256=lTbw268rRhj8dw1sib3VZ_0EhSGgoJO3FKJzSFMOaeA,20797
368
+ pip/_vendor/distlib/locators.py,sha256=oBeAZpFuPQSY09MgNnLfQGGAXXvVO96BFpZyKMuK4tM,51026
369
+ pip/_vendor/distlib/manifest.py,sha256=3qfmAmVwxRqU1o23AlfXrQGZzh6g_GGzTAP_Hb9C5zQ,14168
370
+ pip/_vendor/distlib/markers.py,sha256=X6sDvkFGcYS8gUW8hfsWuKEKAqhQZAJ7iXOMLxRYjYk,5164
371
+ pip/_vendor/distlib/metadata.py,sha256=zil3sg2EUfLXVigljY2d_03IJt-JSs7nX-73fECMX2s,38724
372
+ pip/_vendor/distlib/resources.py,sha256=LwbPksc0A1JMbi6XnuPdMBUn83X7BPuFNWqPGEKI698,10820
373
+ pip/_vendor/distlib/scripts.py,sha256=BJliaDAZaVB7WAkwokgC3HXwLD2iWiHaVI50H7C6eG8,18608
374
+ pip/_vendor/distlib/t32.exe,sha256=a0GV5kCoWsMutvliiCKmIgV98eRZ33wXoS-XrqvJQVs,97792
375
+ pip/_vendor/distlib/t64-arm.exe,sha256=68TAa32V504xVBnufojh0PcenpR3U4wAqTqf-MZqbPw,182784
376
+ pip/_vendor/distlib/t64.exe,sha256=gaYY8hy4fbkHYTTnA4i26ct8IQZzkBG2pRdy0iyuBrc,108032
377
+ pip/_vendor/distlib/util.py,sha256=vMPGvsS4j9hF6Y9k3Tyom1aaHLb0rFmZAEyzeAdel9w,66682
378
+ pip/_vendor/distlib/version.py,sha256=s5VIs8wBn0fxzGxWM_aA2ZZyx525HcZbMvcTlTyZ3Rg,23727
379
+ pip/_vendor/distlib/w32.exe,sha256=R4csx3-OGM9kL4aPIzQKRo5TfmRSHZo6QWyLhDhNBks,91648
380
+ pip/_vendor/distlib/w64-arm.exe,sha256=xdyYhKj0WDcVUOCb05blQYvzdYIKMbmJn2SZvzkcey4,168448
381
+ pip/_vendor/distlib/w64.exe,sha256=ejGf-rojoBfXseGLpya6bFTFPWRG21X5KvU8J5iU-K0,101888
382
+ pip/_vendor/distlib/wheel.py,sha256=DFIVguEQHCdxnSdAO0dfFsgMcvVZitg7bCOuLwZ7A_s,43979
383
+ pip/_vendor/distro/__init__.py,sha256=2fHjF-SfgPvjyNZ1iHh_wjqWdR_Yo5ODHwZC0jLBPhc,981
384
+ pip/_vendor/distro/__main__.py,sha256=bu9d3TifoKciZFcqRBuygV3GSuThnVD_m2IK4cz96Vs,64
385
+ pip/_vendor/distro/__pycache__/__init__.cpython-312.pyc,,
386
+ pip/_vendor/distro/__pycache__/__main__.cpython-312.pyc,,
387
+ pip/_vendor/distro/__pycache__/distro.cpython-312.pyc,,
388
+ pip/_vendor/distro/distro.py,sha256=XqbefacAhDT4zr_trnbA15eY8vdK4GTghgmvUGrEM_4,49430
389
+ pip/_vendor/distro/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
390
+ pip/_vendor/idna/__init__.py,sha256=KJQN1eQBr8iIK5SKrJ47lXvxG0BJ7Lm38W4zT0v_8lk,849
391
+ pip/_vendor/idna/__pycache__/__init__.cpython-312.pyc,,
392
+ pip/_vendor/idna/__pycache__/codec.cpython-312.pyc,,
393
+ pip/_vendor/idna/__pycache__/compat.cpython-312.pyc,,
394
+ pip/_vendor/idna/__pycache__/core.cpython-312.pyc,,
395
+ pip/_vendor/idna/__pycache__/idnadata.cpython-312.pyc,,
396
+ pip/_vendor/idna/__pycache__/intranges.cpython-312.pyc,,
397
+ pip/_vendor/idna/__pycache__/package_data.cpython-312.pyc,,
398
+ pip/_vendor/idna/__pycache__/uts46data.cpython-312.pyc,,
399
+ pip/_vendor/idna/codec.py,sha256=PS6m-XmdST7Wj7J7ulRMakPDt5EBJyYrT3CPtjh-7t4,3426
400
+ pip/_vendor/idna/compat.py,sha256=0_sOEUMT4CVw9doD3vyRhX80X19PwqFoUBs7gWsFME4,321
401
+ pip/_vendor/idna/core.py,sha256=lyhpoe2vulEaB_65xhXmoKgO-xUqFDvcwxu5hpNNO4E,12663
402
+ pip/_vendor/idna/idnadata.py,sha256=dqRwytzkjIHMBa2R1lYvHDwACenZPt8eGVu1Y8UBE-E,78320
403
+ pip/_vendor/idna/intranges.py,sha256=YBr4fRYuWH7kTKS2tXlFjM24ZF1Pdvcir-aywniInqg,1881
404
+ pip/_vendor/idna/package_data.py,sha256=Tkt0KnIeyIlnHddOaz9WSkkislNgokJAuE-p5GorMqo,21
405
+ pip/_vendor/idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
406
+ pip/_vendor/idna/uts46data.py,sha256=1KuksWqLuccPXm2uyRVkhfiFLNIhM_H2m4azCcnOqEU,206503
407
+ pip/_vendor/msgpack/__init__.py,sha256=gsMP7JTECZNUSjvOyIbdhNOkpB9Z8BcGwabVGY2UcdQ,1077
408
+ pip/_vendor/msgpack/__pycache__/__init__.cpython-312.pyc,,
409
+ pip/_vendor/msgpack/__pycache__/exceptions.cpython-312.pyc,,
410
+ pip/_vendor/msgpack/__pycache__/ext.cpython-312.pyc,,
411
+ pip/_vendor/msgpack/__pycache__/fallback.cpython-312.pyc,,
412
+ pip/_vendor/msgpack/exceptions.py,sha256=dCTWei8dpkrMsQDcjQk74ATl9HsIBH0ybt8zOPNqMYc,1081
413
+ pip/_vendor/msgpack/ext.py,sha256=fKp00BqDLjUtZnPd70Llr138zk8JsCuSpJkkZ5S4dt8,5629
414
+ pip/_vendor/msgpack/fallback.py,sha256=wdUWJkWX2gzfRW9BBCTOuIE1Wvrf5PtBtR8ZtY7G_EE,33175
415
+ pip/_vendor/packaging/__init__.py,sha256=dtw2bNmWCQ9WnMoK3bk_elL1svSlikXtLpZhCFIB9SE,496
416
+ pip/_vendor/packaging/__pycache__/__init__.cpython-312.pyc,,
417
+ pip/_vendor/packaging/__pycache__/_elffile.cpython-312.pyc,,
418
+ pip/_vendor/packaging/__pycache__/_manylinux.cpython-312.pyc,,
419
+ pip/_vendor/packaging/__pycache__/_musllinux.cpython-312.pyc,,
420
+ pip/_vendor/packaging/__pycache__/_parser.cpython-312.pyc,,
421
+ pip/_vendor/packaging/__pycache__/_structures.cpython-312.pyc,,
422
+ pip/_vendor/packaging/__pycache__/_tokenizer.cpython-312.pyc,,
423
+ pip/_vendor/packaging/__pycache__/markers.cpython-312.pyc,,
424
+ pip/_vendor/packaging/__pycache__/metadata.cpython-312.pyc,,
425
+ pip/_vendor/packaging/__pycache__/requirements.cpython-312.pyc,,
426
+ pip/_vendor/packaging/__pycache__/specifiers.cpython-312.pyc,,
427
+ pip/_vendor/packaging/__pycache__/tags.cpython-312.pyc,,
428
+ pip/_vendor/packaging/__pycache__/utils.cpython-312.pyc,,
429
+ pip/_vendor/packaging/__pycache__/version.cpython-312.pyc,,
430
+ pip/_vendor/packaging/_elffile.py,sha256=_LcJW4YNKywYsl4169B2ukKRqwxjxst_8H0FRVQKlz8,3282
431
+ pip/_vendor/packaging/_manylinux.py,sha256=Xo4V0PZz8sbuVCbTni0t1CR0AHeir_7ib4lTmV8scD4,9586
432
+ pip/_vendor/packaging/_musllinux.py,sha256=p9ZqNYiOItGee8KcZFeHF_YcdhVwGHdK6r-8lgixvGQ,2694
433
+ pip/_vendor/packaging/_parser.py,sha256=s_TvTvDNK0NrM2QB3VKThdWFM4Nc0P6JnkObkl3MjpM,10236
434
+ pip/_vendor/packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431
435
+ pip/_vendor/packaging/_tokenizer.py,sha256=J6v5H7Jzvb-g81xp_2QACKwO7LxHQA6ikryMU7zXwN8,5273
436
+ pip/_vendor/packaging/markers.py,sha256=dWKSqn5Sp-jDmOG-W3GfLHKjwhf1IsznbT71VlBoB5M,10671
437
+ pip/_vendor/packaging/metadata.py,sha256=KINuSkJ12u-SyoKNTy_pHNGAfMUtxNvZ53qA1zAKcKI,32349
438
+ pip/_vendor/packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
439
+ pip/_vendor/packaging/requirements.py,sha256=gYyRSAdbrIyKDY66ugIDUQjRMvxkH2ALioTmX3tnL6o,2947
440
+ pip/_vendor/packaging/specifiers.py,sha256=HfGgfNJRvrzC759gnnoojHyiWs_DYmcw5PEh5jHH-YE,39738
441
+ pip/_vendor/packaging/tags.py,sha256=Fo6_cit95-7QfcMb16XtI7AUiSMgdwA_hCO_9lV2pz4,21388
442
+ pip/_vendor/packaging/utils.py,sha256=NAdYUwnlAOpkat_RthavX8a07YuVxgGL_vwrx73GSDM,5287
443
+ pip/_vendor/packaging/version.py,sha256=wE4sSVlF-d1H6HFC1vszEe35CwTig_fh4HHIFg95hFE,16210
444
+ pip/_vendor/pkg_resources/__init__.py,sha256=jrhDRbOubP74QuPXxd7U7Po42PH2l-LZ2XfcO7llpZ4,124463
445
+ pip/_vendor/pkg_resources/__pycache__/__init__.cpython-312.pyc,,
446
+ pip/_vendor/platformdirs/__init__.py,sha256=FTA6LGNm40GwNZt3gG3uLAacWvf2E_2HTmH0rAALGR8,22285
447
+ pip/_vendor/platformdirs/__main__.py,sha256=jBJ8zb7Mpx5ebcqF83xrpO94MaeCpNGHVf9cvDN2JLg,1505
448
+ pip/_vendor/platformdirs/__pycache__/__init__.cpython-312.pyc,,
449
+ pip/_vendor/platformdirs/__pycache__/__main__.cpython-312.pyc,,
450
+ pip/_vendor/platformdirs/__pycache__/android.cpython-312.pyc,,
451
+ pip/_vendor/platformdirs/__pycache__/api.cpython-312.pyc,,
452
+ pip/_vendor/platformdirs/__pycache__/macos.cpython-312.pyc,,
453
+ pip/_vendor/platformdirs/__pycache__/unix.cpython-312.pyc,,
454
+ pip/_vendor/platformdirs/__pycache__/version.cpython-312.pyc,,
455
+ pip/_vendor/platformdirs/__pycache__/windows.cpython-312.pyc,,
456
+ pip/_vendor/platformdirs/android.py,sha256=xZXY9Jd46WOsxT2U6-5HsNtDZ-IQqxcEUrBLl3hYk4o,9016
457
+ pip/_vendor/platformdirs/api.py,sha256=QBYdUac2eC521ek_y53uD1Dcq-lJX8IgSRVd4InC6uc,8996
458
+ pip/_vendor/platformdirs/macos.py,sha256=wftsbsvq6nZ0WORXSiCrZNkRHz_WKuktl0a6mC7MFkI,5580
459
+ pip/_vendor/platformdirs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
460
+ pip/_vendor/platformdirs/unix.py,sha256=Cci9Wqt35dAMsg6HT9nRGHSBW5obb0pR3AE1JJnsCXg,10643
461
+ pip/_vendor/platformdirs/version.py,sha256=r7F76tZRjgQKzrpx_I0_ZMQOMU-PS7eGnHD7zEK3KB0,411
462
+ pip/_vendor/platformdirs/windows.py,sha256=IFpiohUBwxPtCzlyKwNtxyW4Jk8haa6W8o59mfrDXVo,10125
463
+ pip/_vendor/pygments/__init__.py,sha256=7N1oiaWulw_nCsTY4EEixYLz15pWY5u4uPAFFi-ielU,2983
464
+ pip/_vendor/pygments/__main__.py,sha256=isIhBxLg65nLlXukG4VkMuPfNdd7gFzTZ_R_z3Q8diY,353
465
+ pip/_vendor/pygments/__pycache__/__init__.cpython-312.pyc,,
466
+ pip/_vendor/pygments/__pycache__/__main__.cpython-312.pyc,,
467
+ pip/_vendor/pygments/__pycache__/cmdline.cpython-312.pyc,,
468
+ pip/_vendor/pygments/__pycache__/console.cpython-312.pyc,,
469
+ pip/_vendor/pygments/__pycache__/filter.cpython-312.pyc,,
470
+ pip/_vendor/pygments/__pycache__/formatter.cpython-312.pyc,,
471
+ pip/_vendor/pygments/__pycache__/lexer.cpython-312.pyc,,
472
+ pip/_vendor/pygments/__pycache__/modeline.cpython-312.pyc,,
473
+ pip/_vendor/pygments/__pycache__/plugin.cpython-312.pyc,,
474
+ pip/_vendor/pygments/__pycache__/regexopt.cpython-312.pyc,,
475
+ pip/_vendor/pygments/__pycache__/scanner.cpython-312.pyc,,
476
+ pip/_vendor/pygments/__pycache__/sphinxext.cpython-312.pyc,,
477
+ pip/_vendor/pygments/__pycache__/style.cpython-312.pyc,,
478
+ pip/_vendor/pygments/__pycache__/token.cpython-312.pyc,,
479
+ pip/_vendor/pygments/__pycache__/unistring.cpython-312.pyc,,
480
+ pip/_vendor/pygments/__pycache__/util.cpython-312.pyc,,
481
+ pip/_vendor/pygments/cmdline.py,sha256=LIVzmAunlk9sRJJp54O4KRy9GDIN4Wu13v9p9QzfGPM,23656
482
+ pip/_vendor/pygments/console.py,sha256=yhP9UsLAVmWKVQf2446JJewkA7AiXeeTf4Ieg3Oi2fU,1718
483
+ pip/_vendor/pygments/filter.py,sha256=_ADNPCskD8_GmodHi6_LoVgPU3Zh336aBCT5cOeTMs0,1910
484
+ pip/_vendor/pygments/filters/__init__.py,sha256=RdedK2KWKXlKwR7cvkfr3NUj9YiZQgMgilRMFUg2jPA,40392
485
+ pip/_vendor/pygments/filters/__pycache__/__init__.cpython-312.pyc,,
486
+ pip/_vendor/pygments/formatter.py,sha256=jDWBTndlBH2Z5IYZFVDnP0qn1CaTQjTWt7iAGtCnJEg,4390
487
+ pip/_vendor/pygments/formatters/__init__.py,sha256=8No-NUs8rBTSSBJIv4hSEQt2M0cFB4hwAT0snVc2QGE,5385
488
+ pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-312.pyc,,
489
+ pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-312.pyc,,
490
+ pip/_vendor/pygments/formatters/__pycache__/bbcode.cpython-312.pyc,,
491
+ pip/_vendor/pygments/formatters/__pycache__/groff.cpython-312.pyc,,
492
+ pip/_vendor/pygments/formatters/__pycache__/html.cpython-312.pyc,,
493
+ pip/_vendor/pygments/formatters/__pycache__/img.cpython-312.pyc,,
494
+ pip/_vendor/pygments/formatters/__pycache__/irc.cpython-312.pyc,,
495
+ pip/_vendor/pygments/formatters/__pycache__/latex.cpython-312.pyc,,
496
+ pip/_vendor/pygments/formatters/__pycache__/other.cpython-312.pyc,,
497
+ pip/_vendor/pygments/formatters/__pycache__/pangomarkup.cpython-312.pyc,,
498
+ pip/_vendor/pygments/formatters/__pycache__/rtf.cpython-312.pyc,,
499
+ pip/_vendor/pygments/formatters/__pycache__/svg.cpython-312.pyc,,
500
+ pip/_vendor/pygments/formatters/__pycache__/terminal.cpython-312.pyc,,
501
+ pip/_vendor/pygments/formatters/__pycache__/terminal256.cpython-312.pyc,,
502
+ pip/_vendor/pygments/formatters/_mapping.py,sha256=1Cw37FuQlNacnxRKmtlPX4nyLoX9_ttko5ZwscNUZZ4,4176
503
+ pip/_vendor/pygments/formatters/bbcode.py,sha256=3JQLI45tcrQ_kRUMjuab6C7Hb0XUsbVWqqbSn9cMjkI,3320
504
+ pip/_vendor/pygments/formatters/groff.py,sha256=M39k0PaSSZRnxWjqBSVPkF0mu1-Vr7bm6RsFvs-CNN4,5106
505
+ pip/_vendor/pygments/formatters/html.py,sha256=SE2jc3YCqbMS3rZW9EAmDlAUhdVxJ52gA4dileEvCGU,35669
506
+ pip/_vendor/pygments/formatters/img.py,sha256=MwA4xWPLOwh6j7Yc6oHzjuqSPt0M1fh5r-5BTIIUfsU,23287
507
+ pip/_vendor/pygments/formatters/irc.py,sha256=dp1Z0l_ObJ5NFh9MhqLGg5ptG5hgJqedT2Vkutt9v0M,4981
508
+ pip/_vendor/pygments/formatters/latex.py,sha256=XMmhOCqUKDBQtG5mGJNAFYxApqaC5puo5cMmPfK3944,19306
509
+ pip/_vendor/pygments/formatters/other.py,sha256=56PMJOliin-rAUdnRM0i1wsV1GdUPd_dvQq0_UPfF9c,5034
510
+ pip/_vendor/pygments/formatters/pangomarkup.py,sha256=y16U00aVYYEFpeCfGXlYBSMacG425CbfoG8oKbKegIg,2218
511
+ pip/_vendor/pygments/formatters/rtf.py,sha256=ZT90dmcKyJboIB0mArhL7IhE467GXRN0G7QAUgG03To,11957
512
+ pip/_vendor/pygments/formatters/svg.py,sha256=KKsiophPupHuxm0So-MsbQEWOT54IAiSF7hZPmxtKXE,7174
513
+ pip/_vendor/pygments/formatters/terminal.py,sha256=AojNG4MlKq2L6IsC_VnXHu4AbHCBn9Otog6u45XvxeI,4674
514
+ pip/_vendor/pygments/formatters/terminal256.py,sha256=kGkNUVo3FpwjytIDS0if79EuUoroAprcWt3igrcIqT0,11753
515
+ pip/_vendor/pygments/lexer.py,sha256=TYHDt___gNW4axTl2zvPZff-VQi8fPaIh5OKRcVSjUM,35349
516
+ pip/_vendor/pygments/lexers/__init__.py,sha256=pIlxyQJuu_syh9lE080cq8ceVbEVcKp0osAFU5fawJU,12115
517
+ pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-312.pyc,,
518
+ pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-312.pyc,,
519
+ pip/_vendor/pygments/lexers/__pycache__/python.cpython-312.pyc,,
520
+ pip/_vendor/pygments/lexers/_mapping.py,sha256=61-h3zr103m01OS5BUq_AfUiL9YI06Ves9ipQ7k4vr4,76097
521
+ pip/_vendor/pygments/lexers/python.py,sha256=2J_YJrPTr_A6fJY_qKiKv0GpgPwHMrlMSeo59qN3fe4,53687
522
+ pip/_vendor/pygments/modeline.py,sha256=gtRYZBS-CKOCDXHhGZqApboHBaZwGH8gznN3O6nuxj4,1005
523
+ pip/_vendor/pygments/plugin.py,sha256=ioeJ3QeoJ-UQhZpY9JL7vbxsTVuwwM7BCu-Jb8nN0AU,1891
524
+ pip/_vendor/pygments/regexopt.py,sha256=Hky4EB13rIXEHQUNkwmCrYqtIlnXDehNR3MztafZ43w,3072
525
+ pip/_vendor/pygments/scanner.py,sha256=NDy3ofK_fHRFK4hIDvxpamG871aewqcsIb6sgTi7Fhk,3092
526
+ pip/_vendor/pygments/sphinxext.py,sha256=iOptJBcqOGPwMEJ2p70PvwpZPIGdvdZ8dxvq6kzxDgA,7981
527
+ pip/_vendor/pygments/style.py,sha256=rSCZWFpg1_DwFMXDU0nEVmAcBHpuQGf9RxvOPPQvKLQ,6420
528
+ pip/_vendor/pygments/styles/__init__.py,sha256=qUk6_1z5KmT8EdJFZYgESmG6P_HJF_2vVrDD7HSCGYY,2042
529
+ pip/_vendor/pygments/styles/__pycache__/__init__.cpython-312.pyc,,
530
+ pip/_vendor/pygments/styles/__pycache__/_mapping.cpython-312.pyc,,
531
+ pip/_vendor/pygments/styles/_mapping.py,sha256=6lovFUE29tz6EsV3XYY4hgozJ7q1JL7cfO3UOlgnS8w,3312
532
+ pip/_vendor/pygments/token.py,sha256=qZwT7LSPy5YBY3JgDjut642CCy7JdQzAfmqD9NmT5j0,6226
533
+ pip/_vendor/pygments/unistring.py,sha256=p5c1i-HhoIhWemy9CUsaN9o39oomYHNxXll0Xfw6tEA,63208
534
+ pip/_vendor/pygments/util.py,sha256=2tj2nS1X9_OpcuSjf8dOET2bDVZhs8cEKd_uT6-Fgg8,10031
535
+ pip/_vendor/pyproject_hooks/__init__.py,sha256=kCehmy0UaBa9oVMD7ZIZrnswfnP3LXZ5lvnNJAL5JBM,491
536
+ pip/_vendor/pyproject_hooks/__pycache__/__init__.cpython-312.pyc,,
537
+ pip/_vendor/pyproject_hooks/__pycache__/_compat.cpython-312.pyc,,
538
+ pip/_vendor/pyproject_hooks/__pycache__/_impl.cpython-312.pyc,,
539
+ pip/_vendor/pyproject_hooks/_compat.py,sha256=by6evrYnqkisiM-MQcvOKs5bgDMzlOSgZqRHNqf04zE,138
540
+ pip/_vendor/pyproject_hooks/_impl.py,sha256=61GJxzQip0IInhuO69ZI5GbNQ82XEDUB_1Gg5_KtUoc,11920
541
+ pip/_vendor/pyproject_hooks/_in_process/__init__.py,sha256=9gQATptbFkelkIy0OfWFEACzqxXJMQDWCH9rBOAZVwQ,546
542
+ pip/_vendor/pyproject_hooks/_in_process/__pycache__/__init__.cpython-312.pyc,,
543
+ pip/_vendor/pyproject_hooks/_in_process/__pycache__/_in_process.cpython-312.pyc,,
544
+ pip/_vendor/pyproject_hooks/_in_process/_in_process.py,sha256=m2b34c917IW5o-Q_6TYIHlsK9lSUlNiyrITTUH_zwew,10927
545
+ pip/_vendor/requests/__init__.py,sha256=HlB_HzhrzGtfD_aaYUwUh1zWXLZ75_YCLyit75d0Vz8,5057
546
+ pip/_vendor/requests/__pycache__/__init__.cpython-312.pyc,,
547
+ pip/_vendor/requests/__pycache__/__version__.cpython-312.pyc,,
548
+ pip/_vendor/requests/__pycache__/_internal_utils.cpython-312.pyc,,
549
+ pip/_vendor/requests/__pycache__/adapters.cpython-312.pyc,,
550
+ pip/_vendor/requests/__pycache__/api.cpython-312.pyc,,
551
+ pip/_vendor/requests/__pycache__/auth.cpython-312.pyc,,
552
+ pip/_vendor/requests/__pycache__/certs.cpython-312.pyc,,
553
+ pip/_vendor/requests/__pycache__/compat.cpython-312.pyc,,
554
+ pip/_vendor/requests/__pycache__/cookies.cpython-312.pyc,,
555
+ pip/_vendor/requests/__pycache__/exceptions.cpython-312.pyc,,
556
+ pip/_vendor/requests/__pycache__/help.cpython-312.pyc,,
557
+ pip/_vendor/requests/__pycache__/hooks.cpython-312.pyc,,
558
+ pip/_vendor/requests/__pycache__/models.cpython-312.pyc,,
559
+ pip/_vendor/requests/__pycache__/packages.cpython-312.pyc,,
560
+ pip/_vendor/requests/__pycache__/sessions.cpython-312.pyc,,
561
+ pip/_vendor/requests/__pycache__/status_codes.cpython-312.pyc,,
562
+ pip/_vendor/requests/__pycache__/structures.cpython-312.pyc,,
563
+ pip/_vendor/requests/__pycache__/utils.cpython-312.pyc,,
564
+ pip/_vendor/requests/__version__.py,sha256=FVfglgZmNQnmYPXpOohDU58F5EUb_-VnSTaAesS187g,435
565
+ pip/_vendor/requests/_internal_utils.py,sha256=nMQymr4hs32TqVo5AbCrmcJEhvPUh7xXlluyqwslLiQ,1495
566
+ pip/_vendor/requests/adapters.py,sha256=J7VeVxKBvawbtlX2DERVo05J9BXTcWYLMHNd1Baa-bk,27607
567
+ pip/_vendor/requests/api.py,sha256=_Zb9Oa7tzVIizTKwFrPjDEY9ejtm_OnSRERnADxGsQs,6449
568
+ pip/_vendor/requests/auth.py,sha256=kF75tqnLctZ9Mf_hm9TZIj4cQWnN5uxRz8oWsx5wmR0,10186
569
+ pip/_vendor/requests/certs.py,sha256=PVPooB0jP5hkZEULSCwC074532UFbR2Ptgu0I5zwmCs,575
570
+ pip/_vendor/requests/compat.py,sha256=Mo9f9xZpefod8Zm-n9_StJcVTmwSukXR2p3IQyyVXvU,1485
571
+ pip/_vendor/requests/cookies.py,sha256=bNi-iqEj4NPZ00-ob-rHvzkvObzN3lEpgw3g6paS3Xw,18590
572
+ pip/_vendor/requests/exceptions.py,sha256=D1wqzYWne1mS2rU43tP9CeN1G7QAy7eqL9o1god6Ejw,4272
573
+ pip/_vendor/requests/help.py,sha256=hRKaf9u0G7fdwrqMHtF3oG16RKktRf6KiwtSq2Fo1_0,3813
574
+ pip/_vendor/requests/hooks.py,sha256=CiuysiHA39V5UfcCBXFIx83IrDpuwfN9RcTUgv28ftQ,733
575
+ pip/_vendor/requests/models.py,sha256=x4K4CmH-lC0l2Kb-iPfMN4dRXxHEcbOaEWBL_i09AwI,35483
576
+ pip/_vendor/requests/packages.py,sha256=_ZQDCJTJ8SP3kVWunSqBsRZNPzj2c1WFVqbdr08pz3U,1057
577
+ pip/_vendor/requests/sessions.py,sha256=ykTI8UWGSltOfH07HKollH7kTBGw4WhiBVaQGmckTw4,30495
578
+ pip/_vendor/requests/status_codes.py,sha256=iJUAeA25baTdw-6PfD0eF4qhpINDJRJI-yaMqxs4LEI,4322
579
+ pip/_vendor/requests/structures.py,sha256=-IbmhVz06S-5aPSZuUthZ6-6D9XOjRuTXHOabY041XM,2912
580
+ pip/_vendor/requests/utils.py,sha256=L79vnFbzJ3SFLKtJwpoWe41Tozi3RlZv94pY1TFIyow,33631
581
+ pip/_vendor/resolvelib/__init__.py,sha256=h509TdEcpb5-44JonaU3ex2TM15GVBLjM9CNCPwnTTs,537
582
+ pip/_vendor/resolvelib/__pycache__/__init__.cpython-312.pyc,,
583
+ pip/_vendor/resolvelib/__pycache__/providers.cpython-312.pyc,,
584
+ pip/_vendor/resolvelib/__pycache__/reporters.cpython-312.pyc,,
585
+ pip/_vendor/resolvelib/__pycache__/resolvers.cpython-312.pyc,,
586
+ pip/_vendor/resolvelib/__pycache__/structs.cpython-312.pyc,,
587
+ pip/_vendor/resolvelib/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
588
+ pip/_vendor/resolvelib/compat/__pycache__/__init__.cpython-312.pyc,,
589
+ pip/_vendor/resolvelib/compat/__pycache__/collections_abc.cpython-312.pyc,,
590
+ pip/_vendor/resolvelib/compat/collections_abc.py,sha256=uy8xUZ-NDEw916tugUXm8HgwCGiMO0f-RcdnpkfXfOs,156
591
+ pip/_vendor/resolvelib/providers.py,sha256=fuuvVrCetu5gsxPB43ERyjfO8aReS3rFQHpDgiItbs4,5871
592
+ pip/_vendor/resolvelib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
593
+ pip/_vendor/resolvelib/reporters.py,sha256=TSbRmWzTc26w0ggsV1bxVpeWDB8QNIre6twYl7GIZBE,1601
594
+ pip/_vendor/resolvelib/resolvers.py,sha256=G8rsLZSq64g5VmIq-lB7UcIJ1gjAxIQJmTF4REZleQ0,20511
595
+ pip/_vendor/resolvelib/structs.py,sha256=0_1_XO8z_CLhegP3Vpf9VJ3zJcfLm0NOHRM-i0Ykz3o,4963
596
+ pip/_vendor/rich/__init__.py,sha256=dRxjIL-SbFVY0q3IjSMrfgBTHrm1LZDgLOygVBwiYZc,6090
597
+ pip/_vendor/rich/__main__.py,sha256=eO7Cq8JnrgG8zVoeImiAs92q3hXNMIfp0w5lMsO7Q2Y,8477
598
+ pip/_vendor/rich/__pycache__/__init__.cpython-312.pyc,,
599
+ pip/_vendor/rich/__pycache__/__main__.cpython-312.pyc,,
600
+ pip/_vendor/rich/__pycache__/_cell_widths.cpython-312.pyc,,
601
+ pip/_vendor/rich/__pycache__/_emoji_codes.cpython-312.pyc,,
602
+ pip/_vendor/rich/__pycache__/_emoji_replace.cpython-312.pyc,,
603
+ pip/_vendor/rich/__pycache__/_export_format.cpython-312.pyc,,
604
+ pip/_vendor/rich/__pycache__/_extension.cpython-312.pyc,,
605
+ pip/_vendor/rich/__pycache__/_fileno.cpython-312.pyc,,
606
+ pip/_vendor/rich/__pycache__/_inspect.cpython-312.pyc,,
607
+ pip/_vendor/rich/__pycache__/_log_render.cpython-312.pyc,,
608
+ pip/_vendor/rich/__pycache__/_loop.cpython-312.pyc,,
609
+ pip/_vendor/rich/__pycache__/_null_file.cpython-312.pyc,,
610
+ pip/_vendor/rich/__pycache__/_palettes.cpython-312.pyc,,
611
+ pip/_vendor/rich/__pycache__/_pick.cpython-312.pyc,,
612
+ pip/_vendor/rich/__pycache__/_ratio.cpython-312.pyc,,
613
+ pip/_vendor/rich/__pycache__/_spinners.cpython-312.pyc,,
614
+ pip/_vendor/rich/__pycache__/_stack.cpython-312.pyc,,
615
+ pip/_vendor/rich/__pycache__/_timer.cpython-312.pyc,,
616
+ pip/_vendor/rich/__pycache__/_win32_console.cpython-312.pyc,,
617
+ pip/_vendor/rich/__pycache__/_windows.cpython-312.pyc,,
618
+ pip/_vendor/rich/__pycache__/_windows_renderer.cpython-312.pyc,,
619
+ pip/_vendor/rich/__pycache__/_wrap.cpython-312.pyc,,
620
+ pip/_vendor/rich/__pycache__/abc.cpython-312.pyc,,
621
+ pip/_vendor/rich/__pycache__/align.cpython-312.pyc,,
622
+ pip/_vendor/rich/__pycache__/ansi.cpython-312.pyc,,
623
+ pip/_vendor/rich/__pycache__/bar.cpython-312.pyc,,
624
+ pip/_vendor/rich/__pycache__/box.cpython-312.pyc,,
625
+ pip/_vendor/rich/__pycache__/cells.cpython-312.pyc,,
626
+ pip/_vendor/rich/__pycache__/color.cpython-312.pyc,,
627
+ pip/_vendor/rich/__pycache__/color_triplet.cpython-312.pyc,,
628
+ pip/_vendor/rich/__pycache__/columns.cpython-312.pyc,,
629
+ pip/_vendor/rich/__pycache__/console.cpython-312.pyc,,
630
+ pip/_vendor/rich/__pycache__/constrain.cpython-312.pyc,,
631
+ pip/_vendor/rich/__pycache__/containers.cpython-312.pyc,,
632
+ pip/_vendor/rich/__pycache__/control.cpython-312.pyc,,
633
+ pip/_vendor/rich/__pycache__/default_styles.cpython-312.pyc,,
634
+ pip/_vendor/rich/__pycache__/diagnose.cpython-312.pyc,,
635
+ pip/_vendor/rich/__pycache__/emoji.cpython-312.pyc,,
636
+ pip/_vendor/rich/__pycache__/errors.cpython-312.pyc,,
637
+ pip/_vendor/rich/__pycache__/file_proxy.cpython-312.pyc,,
638
+ pip/_vendor/rich/__pycache__/filesize.cpython-312.pyc,,
639
+ pip/_vendor/rich/__pycache__/highlighter.cpython-312.pyc,,
640
+ pip/_vendor/rich/__pycache__/json.cpython-312.pyc,,
641
+ pip/_vendor/rich/__pycache__/jupyter.cpython-312.pyc,,
642
+ pip/_vendor/rich/__pycache__/layout.cpython-312.pyc,,
643
+ pip/_vendor/rich/__pycache__/live.cpython-312.pyc,,
644
+ pip/_vendor/rich/__pycache__/live_render.cpython-312.pyc,,
645
+ pip/_vendor/rich/__pycache__/logging.cpython-312.pyc,,
646
+ pip/_vendor/rich/__pycache__/markup.cpython-312.pyc,,
647
+ pip/_vendor/rich/__pycache__/measure.cpython-312.pyc,,
648
+ pip/_vendor/rich/__pycache__/padding.cpython-312.pyc,,
649
+ pip/_vendor/rich/__pycache__/pager.cpython-312.pyc,,
650
+ pip/_vendor/rich/__pycache__/palette.cpython-312.pyc,,
651
+ pip/_vendor/rich/__pycache__/panel.cpython-312.pyc,,
652
+ pip/_vendor/rich/__pycache__/pretty.cpython-312.pyc,,
653
+ pip/_vendor/rich/__pycache__/progress.cpython-312.pyc,,
654
+ pip/_vendor/rich/__pycache__/progress_bar.cpython-312.pyc,,
655
+ pip/_vendor/rich/__pycache__/prompt.cpython-312.pyc,,
656
+ pip/_vendor/rich/__pycache__/protocol.cpython-312.pyc,,
657
+ pip/_vendor/rich/__pycache__/region.cpython-312.pyc,,
658
+ pip/_vendor/rich/__pycache__/repr.cpython-312.pyc,,
659
+ pip/_vendor/rich/__pycache__/rule.cpython-312.pyc,,
660
+ pip/_vendor/rich/__pycache__/scope.cpython-312.pyc,,
661
+ pip/_vendor/rich/__pycache__/screen.cpython-312.pyc,,
662
+ pip/_vendor/rich/__pycache__/segment.cpython-312.pyc,,
663
+ pip/_vendor/rich/__pycache__/spinner.cpython-312.pyc,,
664
+ pip/_vendor/rich/__pycache__/status.cpython-312.pyc,,
665
+ pip/_vendor/rich/__pycache__/style.cpython-312.pyc,,
666
+ pip/_vendor/rich/__pycache__/styled.cpython-312.pyc,,
667
+ pip/_vendor/rich/__pycache__/syntax.cpython-312.pyc,,
668
+ pip/_vendor/rich/__pycache__/table.cpython-312.pyc,,
669
+ pip/_vendor/rich/__pycache__/terminal_theme.cpython-312.pyc,,
670
+ pip/_vendor/rich/__pycache__/text.cpython-312.pyc,,
671
+ pip/_vendor/rich/__pycache__/theme.cpython-312.pyc,,
672
+ pip/_vendor/rich/__pycache__/themes.cpython-312.pyc,,
673
+ pip/_vendor/rich/__pycache__/traceback.cpython-312.pyc,,
674
+ pip/_vendor/rich/__pycache__/tree.cpython-312.pyc,,
675
+ pip/_vendor/rich/_cell_widths.py,sha256=fbmeyetEdHjzE_Vx2l1uK7tnPOhMs2X1lJfO3vsKDpA,10209
676
+ pip/_vendor/rich/_emoji_codes.py,sha256=hu1VL9nbVdppJrVoijVshRlcRRe_v3dju3Mmd2sKZdY,140235
677
+ pip/_vendor/rich/_emoji_replace.py,sha256=n-kcetsEUx2ZUmhQrfeMNc-teeGhpuSQ5F8VPBsyvDo,1064
678
+ pip/_vendor/rich/_export_format.py,sha256=RI08pSrm5tBSzPMvnbTqbD9WIalaOoN5d4M1RTmLq1Y,2128
679
+ pip/_vendor/rich/_extension.py,sha256=Xt47QacCKwYruzjDi-gOBq724JReDj9Cm9xUi5fr-34,265
680
+ pip/_vendor/rich/_fileno.py,sha256=HWZxP5C2ajMbHryvAQZseflVfQoGzsKOHzKGsLD8ynQ,799
681
+ pip/_vendor/rich/_inspect.py,sha256=oZJGw31e64dwXSCmrDnvZbwVb1ZKhWfU8wI3VWohjJk,9695
682
+ pip/_vendor/rich/_log_render.py,sha256=1ByI0PA1ZpxZY3CGJOK54hjlq4X-Bz_boIjIqCd8Kns,3225
683
+ pip/_vendor/rich/_loop.py,sha256=hV_6CLdoPm0va22Wpw4zKqM0RYsz3TZxXj0PoS-9eDQ,1236
684
+ pip/_vendor/rich/_null_file.py,sha256=tGSXk_v-IZmbj1GAzHit8A3kYIQMiCpVsCFfsC-_KJ4,1387
685
+ pip/_vendor/rich/_palettes.py,sha256=cdev1JQKZ0JvlguV9ipHgznTdnvlIzUFDBb0It2PzjI,7063
686
+ pip/_vendor/rich/_pick.py,sha256=evDt8QN4lF5CiwrUIXlOJCntitBCOsI3ZLPEIAVRLJU,423
687
+ pip/_vendor/rich/_ratio.py,sha256=Zt58apszI6hAAcXPpgdWKpu3c31UBWebOeR4mbyptvU,5471
688
+ pip/_vendor/rich/_spinners.py,sha256=U2r1_g_1zSjsjiUdAESc2iAMc3i4ri_S8PYP6kQ5z1I,19919
689
+ pip/_vendor/rich/_stack.py,sha256=-C8OK7rxn3sIUdVwxZBBpeHhIzX0eI-VM3MemYfaXm0,351
690
+ pip/_vendor/rich/_timer.py,sha256=zelxbT6oPFZnNrwWPpc1ktUeAT-Vc4fuFcRZLQGLtMI,417
691
+ pip/_vendor/rich/_win32_console.py,sha256=P0vxI2fcndym1UU1S37XAzQzQnkyY7YqAKmxm24_gug,22820
692
+ pip/_vendor/rich/_windows.py,sha256=aBwaD_S56SbgopIvayVmpk0Y28uwY2C5Bab1wl3Bp-I,1925
693
+ pip/_vendor/rich/_windows_renderer.py,sha256=t74ZL3xuDCP3nmTp9pH1L5LiI2cakJuQRQleHCJerlk,2783
694
+ pip/_vendor/rich/_wrap.py,sha256=FlSsom5EX0LVkA3KWy34yHnCfLtqX-ZIepXKh-70rpc,3404
695
+ pip/_vendor/rich/abc.py,sha256=ON-E-ZqSSheZ88VrKX2M3PXpFbGEUUZPMa_Af0l-4f0,890
696
+ pip/_vendor/rich/align.py,sha256=sCUkisXkQfoq-IQPyBELfJ8l7LihZJX3HbH8K7Cie-M,10368
697
+ pip/_vendor/rich/ansi.py,sha256=iD6532QYqnBm6hADulKjrV8l8kFJ-9fEVooHJHH3hMg,6906
698
+ pip/_vendor/rich/bar.py,sha256=ldbVHOzKJOnflVNuv1xS7g6dLX2E3wMnXkdPbpzJTcs,3263
699
+ pip/_vendor/rich/box.py,sha256=nr5fYIUghB_iUCEq6y0Z3LlCT8gFPDrzN9u2kn7tJl4,10831
700
+ pip/_vendor/rich/cells.py,sha256=aMmGK4BjXhgE6_JF1ZEGmW3O7mKkE8g84vUnj4Et4To,4780
701
+ pip/_vendor/rich/color.py,sha256=bCRATVdRe5IClJ6Hl62de2PKQ_U4i2MZ4ugjUEg7Tao,18223
702
+ pip/_vendor/rich/color_triplet.py,sha256=3lhQkdJbvWPoLDO-AnYImAWmJvV5dlgYNCVZ97ORaN4,1054
703
+ pip/_vendor/rich/columns.py,sha256=HUX0KcMm9dsKNi11fTbiM_h2iDtl8ySCaVcxlalEzq8,7131
704
+ pip/_vendor/rich/console.py,sha256=deFZIubq2M9A2MCsKFAsFQlWDvcOMsGuUA07QkOaHIw,99173
705
+ pip/_vendor/rich/constrain.py,sha256=1VIPuC8AgtKWrcncQrjBdYqA3JVWysu6jZo1rrh7c7Q,1288
706
+ pip/_vendor/rich/containers.py,sha256=c_56TxcedGYqDepHBMTuZdUIijitAQgnox-Qde0Z1qo,5502
707
+ pip/_vendor/rich/control.py,sha256=DSkHTUQLorfSERAKE_oTAEUFefZnZp4bQb4q8rHbKws,6630
708
+ pip/_vendor/rich/default_styles.py,sha256=-Fe318kMVI_IwciK5POpThcO0-9DYJ67TZAN6DlmlmM,8082
709
+ pip/_vendor/rich/diagnose.py,sha256=an6uouwhKPAlvQhYpNNpGq9EJysfMIOvvCbO3oSoR24,972
710
+ pip/_vendor/rich/emoji.py,sha256=omTF9asaAnsM4yLY94eR_9dgRRSm1lHUszX20D1yYCQ,2501
711
+ pip/_vendor/rich/errors.py,sha256=5pP3Kc5d4QJ_c0KFsxrfyhjiPVe7J1zOqSFbFAzcV-Y,642
712
+ pip/_vendor/rich/file_proxy.py,sha256=Tl9THMDZ-Pk5Wm8sI1gGg_U5DhusmxD-FZ0fUbcU0W0,1683
713
+ pip/_vendor/rich/filesize.py,sha256=9fTLAPCAwHmBXdRv7KZU194jSgNrRb6Wx7RIoBgqeKY,2508
714
+ pip/_vendor/rich/highlighter.py,sha256=6ZAjUcNhBRajBCo9umFUclyi2xL0-55JL7S0vYGUJu4,9585
715
+ pip/_vendor/rich/json.py,sha256=vVEoKdawoJRjAFayPwXkMBPLy7RSTs-f44wSQDR2nJ0,5031
716
+ pip/_vendor/rich/jupyter.py,sha256=QyoKoE_8IdCbrtiSHp9TsTSNyTHY0FO5whE7jOTd9UE,3252
717
+ pip/_vendor/rich/layout.py,sha256=ajkSFAtEVv9EFTcFs-w4uZfft7nEXhNzL7ZVdgrT5rI,14004
718
+ pip/_vendor/rich/live.py,sha256=vUcnJV2LMSK3sQNaILbm0-_B8BpAeiHfcQMAMLfpRe0,14271
719
+ pip/_vendor/rich/live_render.py,sha256=zJtB471jGziBtEwxc54x12wEQtH4BuQr1SA8v9kU82w,3666
720
+ pip/_vendor/rich/logging.py,sha256=uB-cB-3Q4bmXDLLpbOWkmFviw-Fde39zyMV6tKJ2WHQ,11903
721
+ pip/_vendor/rich/markup.py,sha256=3euGKP5s41NCQwaSjTnJxus5iZMHjxpIM0W6fCxra38,8451
722
+ pip/_vendor/rich/measure.py,sha256=HmrIJX8sWRTHbgh8MxEay_83VkqNW_70s8aKP5ZcYI8,5305
723
+ pip/_vendor/rich/padding.py,sha256=kTFGsdGe0os7tXLnHKpwTI90CXEvrceeZGCshmJy5zw,4970
724
+ pip/_vendor/rich/pager.py,sha256=SO_ETBFKbg3n_AgOzXm41Sv36YxXAyI3_R-KOY2_uSc,828
725
+ pip/_vendor/rich/palette.py,sha256=lInvR1ODDT2f3UZMfL1grq7dY_pDdKHw4bdUgOGaM4Y,3396
726
+ pip/_vendor/rich/panel.py,sha256=2Fd1V7e1kHxlPFIusoHY5T7-Cs0RpkrihgVG9ZVqJ4g,10705
727
+ pip/_vendor/rich/pretty.py,sha256=5oIHP_CGWnHEnD0zMdW5qfGC5kHqIKn7zH_eC4crULE,35848
728
+ pip/_vendor/rich/progress.py,sha256=P02xi7T2Ua3qq17o83bkshe4c0v_45cg8VyTj6US6Vg,59715
729
+ pip/_vendor/rich/progress_bar.py,sha256=L4jw8E6Qb_x-jhOrLVhkuMaPmiAhFIl8jHQbWFrKuR8,8164
730
+ pip/_vendor/rich/prompt.py,sha256=wdOn2X8XTJKnLnlw6PoMY7xG4iUPp3ezt4O5gqvpV-E,11304
731
+ pip/_vendor/rich/protocol.py,sha256=5hHHDDNHckdk8iWH5zEbi-zuIVSF5hbU2jIo47R7lTE,1391
732
+ pip/_vendor/rich/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
733
+ pip/_vendor/rich/region.py,sha256=rNT9xZrVZTYIXZC0NYn41CJQwYNbR-KecPOxTgQvB8Y,166
734
+ pip/_vendor/rich/repr.py,sha256=5MZJZmONgC6kud-QW-_m1okXwL2aR6u6y-pUcUCJz28,4431
735
+ pip/_vendor/rich/rule.py,sha256=0fNaS_aERa3UMRc3T5WMpN_sumtDxfaor2y3of1ftBk,4602
736
+ pip/_vendor/rich/scope.py,sha256=TMUU8qo17thyqQCPqjDLYpg_UU1k5qVd-WwiJvnJVas,2843
737
+ pip/_vendor/rich/screen.py,sha256=YoeReESUhx74grqb0mSSb9lghhysWmFHYhsbMVQjXO8,1591
738
+ pip/_vendor/rich/segment.py,sha256=hU1ueeXqI6YeFa08K9DAjlF2QLxcJY9pwZx7RsXavlk,24246
739
+ pip/_vendor/rich/spinner.py,sha256=15koCmF0DQeD8-k28Lpt6X_zJQUlzEhgo_6A6uy47lc,4339
740
+ pip/_vendor/rich/status.py,sha256=kkPph3YeAZBo-X-4wPp8gTqZyU466NLwZBA4PZTTewo,4424
741
+ pip/_vendor/rich/style.py,sha256=3hiocH_4N8vwRm3-8yFWzM7tSwjjEven69XqWasSQwM,27073
742
+ pip/_vendor/rich/styled.py,sha256=eZNnzGrI4ki_54pgY3Oj0T-x3lxdXTYh4_ryDB24wBU,1258
743
+ pip/_vendor/rich/syntax.py,sha256=TnZDuOD4DeHFbkaVEAji1gf8qgAlMU9Boe_GksMGCkk,35475
744
+ pip/_vendor/rich/table.py,sha256=nGEvAZHF4dy1vT9h9Gj9O5qhSQO3ODAxJv0RY1vnIB8,39680
745
+ pip/_vendor/rich/terminal_theme.py,sha256=1j5-ufJfnvlAo5Qsi_ACZiXDmwMXzqgmFByObT9-yJY,3370
746
+ pip/_vendor/rich/text.py,sha256=5rQ3zvNrg5UZKNLecbh7fiw9v3HeFulNVtRY_CBDjjE,47312
747
+ pip/_vendor/rich/theme.py,sha256=belFJogzA0W0HysQabKaHOc3RWH2ko3fQAJhoN-AFdo,3777
748
+ pip/_vendor/rich/themes.py,sha256=0xgTLozfabebYtcJtDdC5QkX5IVUEaviqDUJJh4YVFk,102
749
+ pip/_vendor/rich/traceback.py,sha256=CUpxYLjQWIb6vQQ6O72X0hvDV6caryGqU6UweHgOyCY,29601
750
+ pip/_vendor/rich/tree.py,sha256=meAOUU6sYnoBEOX2ILrPLY9k5bWrWNQKkaiEFvHinXM,9167
751
+ pip/_vendor/tomli/__init__.py,sha256=JhUwV66DB1g4Hvt1UQCVMdfCu-IgAV8FXmvDU9onxd4,396
752
+ pip/_vendor/tomli/__pycache__/__init__.cpython-312.pyc,,
753
+ pip/_vendor/tomli/__pycache__/_parser.cpython-312.pyc,,
754
+ pip/_vendor/tomli/__pycache__/_re.cpython-312.pyc,,
755
+ pip/_vendor/tomli/__pycache__/_types.cpython-312.pyc,,
756
+ pip/_vendor/tomli/_parser.py,sha256=g9-ENaALS-B8dokYpCuzUFalWlog7T-SIYMjLZSWrtM,22633
757
+ pip/_vendor/tomli/_re.py,sha256=dbjg5ChZT23Ka9z9DHOXfdtSpPwUfdgMXnj8NOoly-w,2943
758
+ pip/_vendor/tomli/_types.py,sha256=-GTG2VUqkpxwMqzmVO4F7ybKddIbAnuAHXfmWQcTi3Q,254
759
+ pip/_vendor/tomli/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26
760
+ pip/_vendor/truststore/__init__.py,sha256=WIDeyzWm7EVX44g354M25vpRXbeY1lsPH6EmUJUcq4o,1264
761
+ pip/_vendor/truststore/__pycache__/__init__.cpython-312.pyc,,
762
+ pip/_vendor/truststore/__pycache__/_api.cpython-312.pyc,,
763
+ pip/_vendor/truststore/__pycache__/_macos.cpython-312.pyc,,
764
+ pip/_vendor/truststore/__pycache__/_openssl.cpython-312.pyc,,
765
+ pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-312.pyc,,
766
+ pip/_vendor/truststore/__pycache__/_windows.cpython-312.pyc,,
767
+ pip/_vendor/truststore/_api.py,sha256=GeXRNTlxPZ3kif4kNoh6JY0oE4QRzTGcgXr6l_X_Gk0,10555
768
+ pip/_vendor/truststore/_macos.py,sha256=nZlLkOmszUE0g6ryRwBVGY5COzPyudcsiJtDWarM5LQ,20503
769
+ pip/_vendor/truststore/_openssl.py,sha256=LLUZ7ZGaio-i5dpKKjKCSeSufmn6T8pi9lDcFnvSyq0,2324
770
+ pip/_vendor/truststore/_ssl_constants.py,sha256=NUD4fVKdSD02ri7-db0tnO0VqLP9aHuzmStcW7tAl08,1130
771
+ pip/_vendor/truststore/_windows.py,sha256=rAHyKYD8M7t-bXfG8VgOVa3TpfhVhbt4rZQlO45YuP8,17993
772
+ pip/_vendor/truststore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
773
+ pip/_vendor/typing_extensions.py,sha256=78hFl0HpDY-ylHUVCnWdU5nTHxUP2-S-3wEZk6CQmLk,134499
774
+ pip/_vendor/urllib3/__init__.py,sha256=iXLcYiJySn0GNbWOOZDDApgBL1JgP44EZ8i1760S8Mc,3333
775
+ pip/_vendor/urllib3/__pycache__/__init__.cpython-312.pyc,,
776
+ pip/_vendor/urllib3/__pycache__/_collections.cpython-312.pyc,,
777
+ pip/_vendor/urllib3/__pycache__/_version.cpython-312.pyc,,
778
+ pip/_vendor/urllib3/__pycache__/connection.cpython-312.pyc,,
779
+ pip/_vendor/urllib3/__pycache__/connectionpool.cpython-312.pyc,,
780
+ pip/_vendor/urllib3/__pycache__/exceptions.cpython-312.pyc,,
781
+ pip/_vendor/urllib3/__pycache__/fields.cpython-312.pyc,,
782
+ pip/_vendor/urllib3/__pycache__/filepost.cpython-312.pyc,,
783
+ pip/_vendor/urllib3/__pycache__/poolmanager.cpython-312.pyc,,
784
+ pip/_vendor/urllib3/__pycache__/request.cpython-312.pyc,,
785
+ pip/_vendor/urllib3/__pycache__/response.cpython-312.pyc,,
786
+ pip/_vendor/urllib3/_collections.py,sha256=pyASJJhW7wdOpqJj9QJA8FyGRfr8E8uUUhqUvhF0728,11372
787
+ pip/_vendor/urllib3/_version.py,sha256=t9wGB6ooOTXXgiY66K1m6BZS1CJyXHAU8EoWDTe6Shk,64
788
+ pip/_vendor/urllib3/connection.py,sha256=ttIA909BrbTUzwkqEe_TzZVh4JOOj7g61Ysei2mrwGg,20314
789
+ pip/_vendor/urllib3/connectionpool.py,sha256=e2eiAwNbFNCKxj4bwDKNK-w7HIdSz3OmMxU_TIt-evQ,40408
790
+ pip/_vendor/urllib3/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
791
+ pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-312.pyc,,
792
+ pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-312.pyc,,
793
+ pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-312.pyc,,
794
+ pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-312.pyc,,
795
+ pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc,,
796
+ pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-312.pyc,,
797
+ pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-312.pyc,,
798
+ pip/_vendor/urllib3/contrib/_appengine_environ.py,sha256=bDbyOEhW2CKLJcQqAKAyrEHN-aklsyHFKq6vF8ZFsmk,957
799
+ pip/_vendor/urllib3/contrib/_securetransport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
800
+ pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-312.pyc,,
801
+ pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-312.pyc,,
802
+ pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-312.pyc,,
803
+ pip/_vendor/urllib3/contrib/_securetransport/bindings.py,sha256=4Xk64qIkPBt09A5q-RIFUuDhNc9mXilVapm7WnYnzRw,17632
804
+ pip/_vendor/urllib3/contrib/_securetransport/low_level.py,sha256=B2JBB2_NRP02xK6DCa1Pa9IuxrPwxzDzZbixQkb7U9M,13922
805
+ pip/_vendor/urllib3/contrib/appengine.py,sha256=VR68eAVE137lxTgjBDwCna5UiBZTOKa01Aj_-5BaCz4,11036
806
+ pip/_vendor/urllib3/contrib/ntlmpool.py,sha256=NlfkW7WMdW8ziqudopjHoW299og1BTWi0IeIibquFwk,4528
807
+ pip/_vendor/urllib3/contrib/pyopenssl.py,sha256=hDJh4MhyY_p-oKlFcYcQaVQRDv6GMmBGuW9yjxyeejM,17081
808
+ pip/_vendor/urllib3/contrib/securetransport.py,sha256=Fef1IIUUFHqpevzXiDPbIGkDKchY2FVKeVeLGR1Qq3g,34446
809
+ pip/_vendor/urllib3/contrib/socks.py,sha256=aRi9eWXo9ZEb95XUxef4Z21CFlnnjbEiAo9HOseoMt4,7097
810
+ pip/_vendor/urllib3/exceptions.py,sha256=0Mnno3KHTNfXRfY7638NufOPkUb6mXOm-Lqj-4x2w8A,8217
811
+ pip/_vendor/urllib3/fields.py,sha256=kvLDCg_JmH1lLjUUEY_FLS8UhY7hBvDPuVETbY8mdrM,8579
812
+ pip/_vendor/urllib3/filepost.py,sha256=5b_qqgRHVlL7uLtdAYBzBh-GHmU5AfJVt_2N0XS3PeY,2440
813
+ pip/_vendor/urllib3/packages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
814
+ pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-312.pyc,,
815
+ pip/_vendor/urllib3/packages/__pycache__/six.cpython-312.pyc,,
816
+ pip/_vendor/urllib3/packages/backports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
817
+ pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-312.pyc,,
818
+ pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-312.pyc,,
819
+ pip/_vendor/urllib3/packages/backports/__pycache__/weakref_finalize.cpython-312.pyc,,
820
+ pip/_vendor/urllib3/packages/backports/makefile.py,sha256=nbzt3i0agPVP07jqqgjhaYjMmuAi_W5E0EywZivVO8E,1417
821
+ pip/_vendor/urllib3/packages/backports/weakref_finalize.py,sha256=tRCal5OAhNSRyb0DhHp-38AtIlCsRP8BxF3NX-6rqIA,5343
822
+ pip/_vendor/urllib3/packages/six.py,sha256=b9LM0wBXv7E7SrbCjAm4wwN-hrH-iNxv18LgWNMMKPo,34665
823
+ pip/_vendor/urllib3/poolmanager.py,sha256=aWyhXRtNO4JUnCSVVqKTKQd8EXTvUm1VN9pgs2bcONo,19990
824
+ pip/_vendor/urllib3/request.py,sha256=YTWFNr7QIwh7E1W9dde9LM77v2VWTJ5V78XuTTw7D1A,6691
825
+ pip/_vendor/urllib3/response.py,sha256=fmDJAFkG71uFTn-sVSTh2Iw0WmcXQYqkbRjihvwBjU8,30641
826
+ pip/_vendor/urllib3/util/__init__.py,sha256=JEmSmmqqLyaw8P51gUImZh8Gwg9i1zSe-DoqAitn2nc,1155
827
+ pip/_vendor/urllib3/util/__pycache__/__init__.cpython-312.pyc,,
828
+ pip/_vendor/urllib3/util/__pycache__/connection.cpython-312.pyc,,
829
+ pip/_vendor/urllib3/util/__pycache__/proxy.cpython-312.pyc,,
830
+ pip/_vendor/urllib3/util/__pycache__/queue.cpython-312.pyc,,
831
+ pip/_vendor/urllib3/util/__pycache__/request.cpython-312.pyc,,
832
+ pip/_vendor/urllib3/util/__pycache__/response.cpython-312.pyc,,
833
+ pip/_vendor/urllib3/util/__pycache__/retry.cpython-312.pyc,,
834
+ pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-312.pyc,,
835
+ pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc,,
836
+ pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-312.pyc,,
837
+ pip/_vendor/urllib3/util/__pycache__/timeout.cpython-312.pyc,,
838
+ pip/_vendor/urllib3/util/__pycache__/url.cpython-312.pyc,,
839
+ pip/_vendor/urllib3/util/__pycache__/wait.cpython-312.pyc,,
840
+ pip/_vendor/urllib3/util/connection.py,sha256=5Lx2B1PW29KxBn2T0xkN1CBgRBa3gGVJBKoQoRogEVk,4901
841
+ pip/_vendor/urllib3/util/proxy.py,sha256=zUvPPCJrp6dOF0N4GAVbOcl6o-4uXKSrGiTkkr5vUS4,1605
842
+ pip/_vendor/urllib3/util/queue.py,sha256=nRgX8_eX-_VkvxoX096QWoz8Ps0QHUAExILCY_7PncM,498
843
+ pip/_vendor/urllib3/util/request.py,sha256=C0OUt2tcU6LRiQJ7YYNP9GvPrSvl7ziIBekQ-5nlBZk,3997
844
+ pip/_vendor/urllib3/util/response.py,sha256=GJpg3Egi9qaJXRwBh5wv-MNuRWan5BIu40oReoxWP28,3510
845
+ pip/_vendor/urllib3/util/retry.py,sha256=6ENvOZ8PBDzh8kgixpql9lIrb2dxH-k7ZmBanJF2Ng4,22050
846
+ pip/_vendor/urllib3/util/ssl_.py,sha256=QDuuTxPSCj1rYtZ4xpD7Ux-r20TD50aHyqKyhQ7Bq4A,17460
847
+ pip/_vendor/urllib3/util/ssl_match_hostname.py,sha256=Ir4cZVEjmAk8gUAIHWSi7wtOO83UCYABY2xFD1Ql_WA,5758
848
+ pip/_vendor/urllib3/util/ssltransport.py,sha256=NA-u5rMTrDFDFC8QzRKUEKMG0561hOD4qBTr3Z4pv6E,6895
849
+ pip/_vendor/urllib3/util/timeout.py,sha256=cwq4dMk87mJHSBktK1miYJ-85G-3T3RmT20v7SFCpno,10168
850
+ pip/_vendor/urllib3/util/url.py,sha256=lCAE7M5myA8EDdW0sJuyyZhVB9K_j38ljWhHAnFaWoE,14296
851
+ pip/_vendor/urllib3/util/wait.py,sha256=fOX0_faozG2P7iVojQoE1mbydweNyTcm-hXEfFrTtLI,5403
852
+ pip/_vendor/vendor.txt,sha256=43152uDtpsunEE29vmLqqKZUosdrbvzIFkzscLB55Cg,332
853
+ pip/py.typed,sha256=EBVvvPRTn_eIpz5e5QztSCdrMX7Qwd7VP93RSoIlZ2I,286
venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/REQUESTED ADDED
File without changes
venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/WHEEL ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.2.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/entry_points.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ [console_scripts]
2
+ pip = pip._internal.cli.main:main
3
+ pip3 = pip._internal.cli.main:main
venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
venv/lib/python3.12/site-packages/pip/__init__.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+
3
+ __version__ = "24.3.1"
4
+
5
+
6
+ def main(args: Optional[List[str]] = None) -> int:
7
+ """This is an internal API only meant for use by pip's own console scripts.
8
+
9
+ For additional details, see https://github.com/pypa/pip/issues/7498.
10
+ """
11
+ from pip._internal.utils.entrypoints import _wrapper
12
+
13
+ return _wrapper(args)