docker-devkit 0.2.0.dev37277620738__tar.gz → 0.2.1.dev37485576216__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/.github/actions/setup-release-devkit/action.yml +1 -1
  2. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/.github/workflows/release.yml +7 -3
  3. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/AGENTS.md +1 -1
  4. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/PKG-INFO +2 -2
  5. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/pyproject.toml +2 -2
  6. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/src/docker_devkit/build_docker.py +56 -22
  7. docker_devkit-0.2.1.dev37485576216/tests/test_build_docker.py +99 -0
  8. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/uv.lock +4 -4
  9. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/.gitattributes +0 -0
  10. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/.github/workflows/integrate.yml +0 -0
  11. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/.github/workflows/merge-gate.yml +0 -0
  12. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/.gitignore +0 -0
  13. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/.python-version +0 -0
  14. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/CLAUDE.md +0 -0
  15. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/LICENSE +0 -0
  16. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/NOTICE +0 -0
  17. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/README.md +0 -0
  18. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/release-devkit.yaml +0 -0
  19. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/ruff.base.toml +0 -0
  20. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/ruff.toml +0 -0
  21. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/src/docker_devkit/__init__.py +0 -0
  22. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/src/docker_devkit/config.py +0 -0
  23. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/src/docker_devkit/context_sha.py +0 -0
  24. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/src/docker_devkit/detect_gpu.py +0 -0
  25. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/src/docker_devkit/documents.py +0 -0
  26. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/src/docker_devkit/down.py +0 -0
  27. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/src/docker_devkit/image_refs.py +0 -0
  28. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/src/docker_devkit/lifecycle.py +0 -0
  29. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/src/docker_devkit/matrix.py +0 -0
  30. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/src/docker_devkit/mirror.py +0 -0
  31. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/src/docker_devkit/modes.py +0 -0
  32. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/src/docker_devkit/py.typed +0 -0
  33. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/src/docker_devkit/score_generate.py +0 -0
  34. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/src/docker_devkit/up.py +0 -0
  35. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/tests/test_config.py +0 -0
  36. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/tests/test_context_sha.py +0 -0
  37. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/tests/test_default_targets.py +0 -0
  38. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/tests/test_documents.py +0 -0
  39. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/tests/test_image_refs.py +0 -0
  40. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/tests/test_lifecycle.py +0 -0
  41. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/tests/test_matrix.py +0 -0
  42. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/tests/test_mirror.py +0 -0
  43. {docker_devkit-0.2.0.dev37277620738 → docker_devkit-0.2.1.dev37485576216}/tests/test_score_generate.py +0 -0
@@ -5,7 +5,7 @@ runs:
5
5
  steps:
6
6
  - shell: bash
7
7
  env:
8
- RELEASE_DEVKIT_COMMIT: 1130b982aeb956a42b7bf9000ad231eaade593bf
8
+ RELEASE_DEVKIT_COMMIT: ed406a0d88445dd601b7cbe47decbb6371694e3a
9
9
  run: |
10
10
  git clone https://github.com/outernet-foundation/release-devkit.git "$RUNNER_TEMP/release-devkit"
11
11
  git -C "$RUNNER_TEMP/release-devkit" checkout "$RELEASE_DEVKIT_COMMIT"
@@ -11,12 +11,14 @@ permissions:
11
11
  contents: read
12
12
 
13
13
  jobs:
14
- publish-prerelease:
14
+ prerelease:
15
15
  if: github.ref == 'refs/heads/dev'
16
16
  runs-on: ubuntu-latest
17
17
  permissions:
18
- contents: read
18
+ contents: write
19
19
  id-token: write
20
+ packages: read
21
+ actions: read
20
22
  steps:
21
23
  - uses: actions/checkout@v5
22
24
  with:
@@ -31,7 +33,9 @@ jobs:
31
33
  save-cache: "false"
32
34
  - &setup-release-devkit
33
35
  uses: ./.github/actions/setup-release-devkit
34
- - run: uv run --project "$RUNNER_TEMP/release-devkit" --locked --no-dev publish-prerelease
36
+ - run: uv run --project "$RUNNER_TEMP/release-devkit" --locked --no-dev prerelease
37
+ env:
38
+ GITHUB_TOKEN: ${{ github.token }}
35
39
 
36
40
  release:
37
41
  if: github.ref == 'refs/heads/main'
@@ -65,7 +65,7 @@ The mirror is declared, not inferred: the `mirror:` section with `prefix` in the
65
65
  - The `mirror` verb (crane install from a pinned release, per-target `crane copy`, count footer) uses bashrun + plain prints, deliberately not ci-devkit's `ci_step` — it needs nothing from the floor.
66
66
  - Non-mirror lock entries (e.g. `placeframe/*` publishes) are not probed and keep failing naturally at pull. There is no config-level ban on the flag: an air-gap fails closed anyway (no route to upstream — the pull dies with or without it).
67
67
 
68
- The ci-devkit edge exists for the builds shelf: a CI `build` run pushes the resolved `workloads/images.lock` to the repo's builds registry (`--builds-registry` + `--lock-project` + `--run-number`) through ci-devkit's `push_build` — one `images-lock`-platform artifact tagged `run-{N}` per run, the exact lock the run baked with, consumed by release-devkit's release stapler. ORAS CLI knowledge lives only in ci-devkit (org law); local builds (`--mode local`, no `--builds-registry`) never touch it.
68
+ The ci-devkit edge exists for the builds shelf: a CI `build` run distills bake's `--metadata-file` into a digest manifest (`target → {ref, digest, tags[]}` JSON) and pushes it to the repo's builds registry at the conventional `images-digests/all` project/platform (`--builds-registry` + `--run-number`) through ci-devkit's `push_build` — one `images-digests-all` artifact tagged `run-{N}` per run, consumed by release-devkit's release surfaces for the built-image digest table. `workloads/images.lock` stays in source control (the stapler that consumed the shelf copy is gone). ORAS CLI knowledge lives only in ci-devkit (org law); local builds (`--mode local`, no `--builds-registry`) never touch it.
69
69
 
70
70
  Lifecycle stays a separate package from codegen (client generation, datamodel generation, workspace locking) because their dependency surfaces are disjoint — bundling them would force a consumer that only brings a stack up to install `sqlacodegen`, `psycopg`, `datamodel-code-generator`.
71
71
 
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: docker-devkit
3
- Version: 0.2.0.dev37277620738
3
+ Version: 0.2.1.dev37485576216
4
4
  Summary: Docker-stack lifecycle commands (up/down/build) driven by a pyproject-declared compose assembly
5
5
  License-File: LICENSE
6
6
  License-File: NOTICE
7
7
  Requires-Python: >=3.13
8
8
  Requires-Dist: bashrun>=0.1.0
9
- Requires-Dist: ci-devkit==0.1.15.dev36756330648
9
+ Requires-Dist: ci-devkit>=0.1.15
10
10
  Requires-Dist: packaging>=24.1
11
11
  Requires-Dist: pathspec>=1.0.4
12
12
  Requires-Dist: pydantic-settings>=2.9.1
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "docker-devkit"
3
- version = "0.2.0.dev37277620738"
3
+ version = "0.2.1.dev37485576216"
4
4
  description = "Docker-stack lifecycle commands (up/down/build) driven by a pyproject-declared compose assembly"
5
5
  requires-python = ">=3.13"
6
6
  dependencies = [
7
7
  "bashrun>=0.1.0",
8
- "ci-devkit==0.1.15.dev36756330648",
8
+ "ci-devkit>=0.1.15",
9
9
  "packaging>=24.1",
10
10
  "pathspec>=1.0.4",
11
11
  "pydantic>=2.11.7",
@@ -6,7 +6,8 @@ import platform
6
6
  import re
7
7
  from pathlib import Path
8
8
  from subprocess import CalledProcessError
9
- from typing import Annotated, Any, Literal
9
+ from tempfile import TemporaryDirectory
10
+ from typing import Annotated, Any, Literal, TypedDict
10
11
 
11
12
  import typer
12
13
  from bashrun.bash import bash, bash_output
@@ -32,7 +33,16 @@ METADATA_PATH = Path("metadata.json")
32
33
 
33
34
  Mode = Literal["local", "ci"]
34
35
 
35
- IMAGES_LOCK_PLATFORM = "images-lock"
36
+ DIGEST_PROJECT = "images-digests"
37
+ DIGEST_PLATFORM = "all"
38
+ DIGEST_FILE_NAME = "images-digests.json"
39
+
40
+
41
+ class DigestEntry(TypedDict):
42
+ ref: str
43
+ digest: str
44
+ tags: list[str]
45
+
36
46
 
37
47
  app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
38
48
 
@@ -55,14 +65,12 @@ def build(
55
65
  ] = None,
56
66
  builds_registry: Annotated[
57
67
  str | None,
58
- typer.Option("--builds-registry", help="OCI registry path to push the resolved lock to (CI build artifacts)."),
59
- ] = None,
60
- lock_project: Annotated[
61
- str | None,
62
- typer.Option("--lock-project", help="Project component of the lock's builds-shelf address (CI only)."),
68
+ typer.Option(
69
+ "--builds-registry", help="OCI registry path to push the digest manifest to (CI build artifacts)."
70
+ ),
63
71
  ] = None,
64
72
  run_number: Annotated[
65
- int, typer.Option("--run-number", help="CI run number baked into the lock's builds-shelf tag.")
73
+ int, typer.Option("--run-number", help="CI run number baked into the digest manifest's builds-shelf tag.")
66
74
  ] = 0,
67
75
  ) -> None:
68
76
  run_build(
@@ -74,7 +82,6 @@ def build(
74
82
  no_cache=no_cache,
75
83
  targets_opt=targets_opt,
76
84
  builds_registry=builds_registry,
77
- lock_project=lock_project,
78
85
  run_number=run_number,
79
86
  )
80
87
 
@@ -90,7 +97,6 @@ def run_build(
90
97
  targets_opt: list[str] | None = None,
91
98
  env_overlay: dict[str, str] | None = None,
92
99
  builds_registry: str | None = None,
93
- lock_project: str | None = None,
94
100
  run_number: int = 0,
95
101
  ) -> None:
96
102
  root = Path.cwd()
@@ -223,22 +229,50 @@ def run_build(
223
229
  if not set(targets) <= baked_images.keys():
224
230
  raise RuntimeError("Bake output does not cover the requested targets")
225
231
 
226
- push_images_lock(builds_registry, lock_project, run_number, lock_file)
232
+ push_image_digests(builds_registry, run_number, baked_images, targets)
227
233
 
228
234
 
229
- def push_images_lock(builds_registry: str | None, lock_project: str | None, run_number: int, lock_file: Path) -> None:
235
+ def push_image_digests(
236
+ builds_registry: str | None,
237
+ run_number: int,
238
+ baked_images: dict[str, Any],
239
+ targets: list[str],
240
+ ) -> None:
230
241
  if builds_registry is None:
231
242
  return
232
- if not lock_project or run_number <= 0:
233
- raise typer.BadParameter("--builds-registry requires --lock-project and a positive --run-number")
234
- push_build(
235
- builds_registry,
236
- lock_project,
237
- IMAGES_LOCK_PLATFORM,
238
- f"run-{run_number}",
239
- lock_file.parent,
240
- [lock_file.name],
241
- )
243
+ if run_number <= 0:
244
+ raise typer.BadParameter("--builds-registry requires a positive --run-number")
245
+ manifest = distill_digest_manifest(baked_images, targets)
246
+ with TemporaryDirectory(prefix="digest-manifest-") as staging:
247
+ manifest_path = Path(staging) / DIGEST_FILE_NAME
248
+ manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8")
249
+ push_build(
250
+ builds_registry,
251
+ DIGEST_PROJECT,
252
+ DIGEST_PLATFORM,
253
+ f"run-{run_number}",
254
+ Path(staging),
255
+ [DIGEST_FILE_NAME],
256
+ )
257
+
258
+
259
+ def distill_digest_manifest(baked_images: dict[str, Any], targets: list[str]) -> dict[str, DigestEntry]:
260
+ manifest: dict[str, DigestEntry] = {}
261
+ for target in targets:
262
+ entry = baked_images[target]
263
+ image_name = entry["image.name"]
264
+ digest = entry["containerimage.digest"]
265
+ refs = [reference.strip() for reference in image_name.split(",") if reference.strip()]
266
+ image_ref = ""
267
+ tags: list[str] = []
268
+ for reference in refs:
269
+ reference = reference.split("@", 1)[0]
270
+ repository, separator, tag = reference.rpartition(":")
271
+ if separator:
272
+ image_ref = repository
273
+ tags.append(tag)
274
+ manifest[target] = {"ref": image_ref, "digest": digest, "tags": tags}
275
+ return manifest
242
276
 
243
277
 
244
278
  # Vibe code - Gemini 3
@@ -0,0 +1,99 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Any
4
+
5
+ import pytest
6
+ import typer
7
+
8
+ from docker_devkit import build_docker
9
+ from docker_devkit.build_docker import DIGEST_FILE_NAME, distill_digest_manifest, push_image_digests
10
+
11
+ DIGEST_A = "sha256:" + "a" * 64
12
+ DIGEST_B = "sha256:" + "b" * 64
13
+ REPO_ZED = "ghcr.io/outernet-foundation/placeframe-capture-tool/zed-capture"
14
+ REPO_AOA = "ghcr.io/outernet-foundation/placeframe-capture-tool/aoa-bridge"
15
+
16
+
17
+ def _baked(target: str, *, refs: str, digest: str) -> dict[str, dict[str, str]]:
18
+ return {target: {"image.name": refs, "containerimage.digest": digest}}
19
+
20
+
21
+ class PushBuildRecorder:
22
+ def __init__(self) -> None:
23
+ self.calls: list[tuple[str, str, str, str, Path, list[str]]] = []
24
+ self.manifests: list[dict[str, Any]] = []
25
+
26
+ def __call__(
27
+ self,
28
+ registry: str,
29
+ project: str,
30
+ platform: str,
31
+ tag: str,
32
+ source_directory: Path,
33
+ paths: list[str],
34
+ ) -> None:
35
+ self.calls.append((registry, project, platform, tag, source_directory, paths))
36
+ manifest_path = Path(source_directory) / paths[0]
37
+ self.manifests.append(json.loads(manifest_path.read_text(encoding="utf-8")))
38
+
39
+
40
+ def test_distills_single_target_single_tag() -> None:
41
+ baked = _baked("zed-capture", refs=f"{REPO_ZED}:tree-abc", digest=DIGEST_A)
42
+ assert distill_digest_manifest(baked, ["zed-capture"]) == {
43
+ "zed-capture": {"ref": REPO_ZED, "digest": DIGEST_A, "tags": ["tree-abc"]}
44
+ }
45
+
46
+
47
+ def test_distills_multiple_tags_from_comma_separated_refs() -> None:
48
+ baked = _baked("zed-capture", refs=f"{REPO_ZED}:tree-abc,{REPO_ZED}:run-42", digest=DIGEST_A)
49
+ assert distill_digest_manifest(baked, ["zed-capture"]) == {
50
+ "zed-capture": {"ref": REPO_ZED, "digest": DIGEST_A, "tags": ["tree-abc", "run-42"]}
51
+ }
52
+
53
+
54
+ def test_distills_multiple_targets() -> None:
55
+ baked = {
56
+ "zed-capture": {"image.name": f"{REPO_ZED}:tree-abc", "containerimage.digest": DIGEST_A},
57
+ "aoa-bridge": {"image.name": f"{REPO_AOA}:tree-def", "containerimage.digest": DIGEST_B},
58
+ }
59
+ assert distill_digest_manifest(baked, ["zed-capture", "aoa-bridge"]) == {
60
+ "zed-capture": {"ref": REPO_ZED, "digest": DIGEST_A, "tags": ["tree-abc"]},
61
+ "aoa-bridge": {"ref": REPO_AOA, "digest": DIGEST_B, "tags": ["tree-def"]},
62
+ }
63
+
64
+
65
+ def test_strips_digest_suffix_from_refs() -> None:
66
+ baked = _baked("zed-capture", refs=f"{REPO_ZED}:tree-abc@{DIGEST_A}", digest=DIGEST_A)
67
+ entry = distill_digest_manifest(baked, ["zed-capture"])["zed-capture"]
68
+ assert entry["ref"] == REPO_ZED
69
+ assert entry["tags"] == ["tree-abc"]
70
+
71
+
72
+ def test_push_image_digests_is_noop_without_builds_registry(monkeypatch: pytest.MonkeyPatch) -> None:
73
+ recorder = PushBuildRecorder()
74
+ monkeypatch.setattr(build_docker, "push_build", recorder)
75
+ baked = _baked("zed-capture", refs=f"{REPO_ZED}:tree-abc", digest=DIGEST_A)
76
+ push_image_digests(None, 42, baked, ["zed-capture"])
77
+ assert recorder.calls == []
78
+
79
+
80
+ def test_push_image_digests_rejects_zero_run_number() -> None:
81
+ with pytest.raises(typer.BadParameter):
82
+ push_image_digests("ghcr.io/owner/builds", 0, {}, [])
83
+
84
+
85
+ def test_push_image_digests_pushes_manifest_to_images_digests_all(monkeypatch: pytest.MonkeyPatch) -> None:
86
+ recorder = PushBuildRecorder()
87
+ monkeypatch.setattr(build_docker, "push_build", recorder)
88
+ baked = _baked("zed-capture", refs=f"{REPO_ZED}:tree-abc,{REPO_ZED}:run-42", digest=DIGEST_A)
89
+ push_image_digests("ghcr.io/owner/builds", 42, baked, ["zed-capture"])
90
+ assert len(recorder.calls) == 1
91
+ registry, project, platform, tag, _source_directory, paths = recorder.calls[0]
92
+ assert registry == "ghcr.io/owner/builds"
93
+ assert project == "images-digests"
94
+ assert platform == "all"
95
+ assert tag == "run-42"
96
+ assert paths == [DIGEST_FILE_NAME]
97
+ assert recorder.manifests == [
98
+ {"zed-capture": {"ref": REPO_ZED, "digest": DIGEST_A, "tags": ["tree-abc", "run-42"]}}
99
+ ]
@@ -43,16 +43,16 @@ wheels = [
43
43
 
44
44
  [[package]]
45
45
  name = "ci-devkit"
46
- version = "0.1.15.dev36756330648"
46
+ version = "0.2.0"
47
47
  source = { registry = "https://pypi.org/simple" }
48
48
  dependencies = [
49
49
  { name = "bashrun" },
50
50
  { name = "pydantic" },
51
51
  { name = "pydantic-settings" },
52
52
  ]
53
- sdist = { url = "https://files.pythonhosted.org/packages/5c/1e/fdad4f4fd539491f8ddeaf45e349801b274e9f7a8b00f768c6e83df1b97d/ci_devkit-0.1.15.dev36756330648.tar.gz", hash = "sha256:55b497b377a5e10e85c773b57897e4d1f883129b45567d1ecb581c02a18eabe4", size = 53675, upload-time = "2026-09-30T18:10:54.868Z" }
53
+ sdist = { url = "https://files.pythonhosted.org/packages/fd/c6/94c2a4a406b593ca3a1140151c0cb2c1367b30bf295f32a4bf9c92c100ac/ci_devkit-0.2.0.tar.gz", hash = "sha256:a16e0a91771a5b50205646577e8d56198c7c3262a063d7ebcf9a82fc26a2e3e0", size = 53288, upload-time = "2026-10-05T15:02:35.926Z" }
54
54
  wheels = [
55
- { url = "https://files.pythonhosted.org/packages/2f/1e/9f81e475c3cc65febb1e9013caf13dd20b393275c785713a7a85498dfcbc/ci_devkit-0.1.15.dev36756330648-py3-none-any.whl", hash = "sha256:4189adc38d182591cabef7e06321ae1105cdb9fb915f38d90d352a3f277630aa", size = 28208, upload-time = "2026-09-30T18:10:54.044Z" },
55
+ { url = "https://files.pythonhosted.org/packages/ce/d7/f7b91b7e62ed5cd7864f4ddbf84851ad5a31938342cb00e28bf15a7064c2/ci_devkit-0.2.0-py3-none-any.whl", hash = "sha256:d86403effbe36563b0a471d860e5e1e46ae7793d7d203a2dcc5561e91d1163f6", size = 28022, upload-time = "2026-10-05T15:02:34.654Z" },
56
56
  ]
57
57
 
58
58
  [[package]]
@@ -93,7 +93,7 @@ dev = [
93
93
  [package.metadata]
94
94
  requires-dist = [
95
95
  { name = "bashrun", specifier = ">=0.1.0" },
96
- { name = "ci-devkit", specifier = "==0.1.15.dev36756330648" },
96
+ { name = "ci-devkit", specifier = ">=0.1.15" },
97
97
  { name = "packaging", specifier = ">=24.1" },
98
98
  { name = "pathspec", specifier = ">=1.0.4" },
99
99
  { name = "pydantic", specifier = ">=2.11.7" },