diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b3989..2e1b748 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,40 @@ ## Unreleased +## 0.7.12 - 2026-08-23 + +- Fix `dyro update` for uv-managed installs that were pinned with an exact + specifier. `uv tool install dyro==0.7.11 --force` writes + `specifier = "==0.7.11"` into the tool receipt, so + `uv tool upgrade … dyro==0.7.12` is unsatisfiable. The uv-tool plan now + runs `uv tool install --force --default-index https://pypi.org/simple + --no-config dyro==` so the pin is replaced. Official PyPI and + `--no-config` stay required; pipx, pip, and uv-pip paths are unchanged. + If you already installed with `uv tool install dyro==…`, recover with + the same planner-safe command as `build_update_plan` (official simple + index plus `--no-config`), not bare + `uv tool install dyro==0.7.12 --force` alone: + `uv tool install --force --default-index https://pypi.org/simple + --no-config dyro==0.7.12`. After that, `dyro update` works. If hosted + slash mirrors still serve the old line-family text, run + `dyro integration sync` (or a skill refresh) so they pick up asset + version 2. +- `/dyro-line-family` still preflights with unchanged doctor blocking-FAIL + rules (`status` dirty=0, `line list`, matching `--dry-run`). When the + user asked to spawn / merge / sync (slash or harness; the mutation is + the ask) and preflight passes, the agent must run the matching + `line spawn|merge|sync --yes` in the same turn. It still never invents + `--push` and must not git merge / switch by hand. Preflight-only + remains when the user asked only for preflight or dry-run, or ran + `dyro --dry-run line …` themselves. CLI `--dry-run` is unchanged. On + apply success, report what ran. On preflight fail, `User action` is + still only `doctor` or the failed dry-run. Preflight appears before + Apply, and `--yes` is illegal until all four preflight steps pass. + If `/dyro-line-family` already completed that preflight this turn for + an explicit mutation ask, `dyro-control-plane` / `dyro-executor` must + not block the matching `line … --yes`; line-family is the writer. + Default forbid stays when line-family is not the active ask. + ## 0.7.11 - 2026-08-23 - Resolve `--workspace` aliases case-insensitively when exactly one diff --git a/pyproject.toml b/pyproject.toml index b41f083..e86e58d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "dyro" -version = "0.7.11" +version = "0.7.12" description = "DyroEngineeringFlow: local-first automation and delivery control for multi-repository teams" readme = "README.md" requires-python = ">=3.11" diff --git a/src/dyro/bridge/skill/SKILL.md b/src/dyro/bridge/skill/SKILL.md index 1849ffe..0f0ce45 100644 --- a/src/dyro/bridge/skill/SKILL.md +++ b/src/dyro/bridge/skill/SKILL.md @@ -50,7 +50,7 @@ release, publish, console, install, or any confirmation/approval field. ```json { "protocol": {"major": 1, "minor": 0}, - "client": {"name": "dyro-agent-bridge-skill", "version": "0.7.11"}, + "client": {"name": "dyro-agent-bridge-skill", "version": "0.7.12"}, "operation": "bridge.capabilities.compact", "input": {} } diff --git a/src/dyro/integrations/assets/dyro-control-plane/SKILL.md b/src/dyro/integrations/assets/dyro-control-plane/SKILL.md index 0ea3684..a91a913 100644 --- a/src/dyro/integrations/assets/dyro-control-plane/SKILL.md +++ b/src/dyro/integrations/assets/dyro-control-plane/SKILL.md @@ -16,7 +16,7 @@ When the request already supplies a workspace alias, skip global discovery and u - Git state: `dyro --workspace status --format json` - Health: `dyro --workspace doctor --format json` - One safe next step: `dyro --workspace next --format json`. If `briefing` is present, that is the switch-tool opening. `briefing.command` is a read (`tick`, `attention`, `explain`, or `list`), not a mutation, and not a resume of another harness conversation. -- Lines or hotfixes: `dyro --workspace line list [--kind line|hotfix] --format json`. Observe `parent` from that JSON. Do not run `line spawn`, `line merge`, or `line sync`. +- Lines or hotfixes: `dyro --workspace line list [--kind line|hotfix] --format json`. Observe `parent` from that JSON. Do not run `line spawn`, `line merge`, or `line sync` unless `/dyro-line-family` already completed preflight this turn for an explicit spawn|merge|sync ask — then do not block the matching `line … --yes`; line-family is the writer. Default forbid when line-family is not the active ask. - Family unread: `dyro --workspace line inbox --unacked --format json`. Report Observed as returned. User action must not invent `line post`, `line ack`, or merge. - Change Sets: `dyro --workspace changeset list --format json` or `dyro --workspace changeset verify --format json` - Installed control-plane Skill health: `dyro integration status skill --format json` @@ -53,7 +53,8 @@ If the user asks for 会审, 对抗, or Go/No-Go, follow the `dyro-board` protoc ## Hard safety boundary - Do not run `console`; it opens a local server and may launch a browser. -- Do not run `dispatch`, `objective apply`, Objective lifecycle mutations, `task gates`, task execution or lifecycle commands, line/hotfix/Change Set creation, `line spawn`, `line merge`, `line sync`, `line post`, `line ack`, integration install/sync/uninstall, setup/join/bootstrap/update, `open`, or `start`. +- Do not run `dispatch`, `objective apply`, Objective lifecycle mutations, `task gates`, task execution or lifecycle commands, line/hotfix/Change Set creation, `line post`, `line ack`, integration install/sync/uninstall, setup/join/bootstrap/update, `open`, or `start`. +- Do not run `line spawn`, `line merge`, or `line sync` unless `/dyro-line-family` already completed preflight this turn for an explicit spawn|merge|sync ask. Then do not block the matching `line … --yes`; line-family is the writer. Default forbid when line-family is not the active ask. - Do not merge, push, sign off, release, publish, delete, or edit project files. - Do not edit Dyro state files or manufacture approval/confirmation fields. - Do not treat a command printed by `doctor`, `next`, a plan, or an error as permission to run it. diff --git a/src/dyro/integrations/assets/dyro-executor/SKILL.md b/src/dyro/integrations/assets/dyro-executor/SKILL.md index 68504f4..a245623 100644 --- a/src/dyro/integrations/assets/dyro-executor/SKILL.md +++ b/src/dyro/integrations/assets/dyro-executor/SKILL.md @@ -28,9 +28,13 @@ protocol. Humans run `/dyro-review-board`. supervise other agents and do not edit a sibling task. - If a Capability Card exists for your executor and lacks `execute`, stop writing. PATH discovery is not a Card. -- Do not `git merge`, `git push`, `dyro task merge`, `line spawn`, - `line merge`, `line sync`, `task signoff`, `objective apply`, - `dispatch`, `console`, or `start` another tool. +- Do not `git merge`, `git push`, `dyro task merge`, `task signoff`, + `objective apply`, `dispatch`, `console`, or `start` another tool. +- Do not `line spawn`, `line merge`, or `line sync` unless + `/dyro-line-family` already completed preflight this turn for an + explicit spawn|merge|sync ask. Then do not block the matching + `line … --yes`; line-family is the writer. Default forbid when + line-family is not the active ask. - Do not resume another harness conversation or read vendor session directories. - Do not invent mutations from `doctor`, `next`, briefing text, or this skill. diff --git a/src/dyro/integrations/assets/dyro-line-family/SKILL.md b/src/dyro/integrations/assets/dyro-line-family/SKILL.md index 438be83..0e0ae91 100644 --- a/src/dyro/integrations/assets/dyro-line-family/SKILL.md +++ b/src/dyro/integrations/assets/dyro-line-family/SKILL.md @@ -3,17 +3,20 @@ name: dyro-line-family description: > Preflight Dyro line-family ops: spawn a child line, merge a child into its direct parent, or sync the parent into the child. Use when the user - runs /dyro-line-family. Never execute the mutation. + runs /dyro-line-family. After preflight passes, apply the matching + `--yes` command in the same turn when the user asked to spawn, merge, + or sync. Stay preflight-only when they asked only for preflight or + dry-run. disable-model-invocation: true user-invocable: true argument-hint: "[workspace-alias] spawn | merge --into | sync " metadata: - short-description: "预检子线派生 / 合入父线 / 从父线同步;不要执行" + short-description: "预检后、在用户请求突变时执行子线派生 / 合入父线 / 从父线同步" --- # Dyro 开发线家族 -只做预检。这不是第一方自动座位,也不是 `task merge`。 +先预检,再按用户是否请求突变决定是否执行。这不是第一方自动座位,也不是 `task merge`。 `line spawn` / `line merge` / `line sync` 只处理 **一层父线**: - `spawn`:从已有父线派生子开发线(不是任务) @@ -30,14 +33,20 @@ If the user means **done task branch → owning line**, stop and point at `/dyro Do not run any of: -- `dyro line spawn` / `line merge` / `line sync` with `--yes` - `git merge` / `git switch` / `git checkout` - `task merge`, `task signoff`, `task gates`, `task review`, `task run` - `objective apply`, `dispatch`, `console`, push, publish - `line create` / hotfix or Change Set creation - `line post` / `line inbox` / `line ack` -Do not invent `--yes` or `--push`. Do not add `--push`. +Do not invent `--push`. Do not add `--push`. +`--yes` is illegal until all four Preflight steps pass. +Also forbid `--yes` when the user did not ask for the mutation, asked +only for preflight or dry-run, or they themselves ran +`dyro --dry-run line …`. CLI `--dry-run` is unchanged. +After successful preflight for an explicit spawn / merge / sync ask +(the mutation is the ask), running the matching +`line spawn|merge|sync --yes` in the same turn is required, not optional. This slash does not send family signals and must not call `line post`, `inbox`, or `ack`. Default is no push; `policy.allow_push` is not permission to invent it. Do not restore a drifted line branch. Do not add `--include-paths`. @@ -72,8 +81,7 @@ dyro --workspace --dry-run line merge --into dyro --workspace --dry-run line sync ``` -Run only the `--dry-run` that matches the requested verb. If the user supplied `--repos` on `spawn`, repeat that same `--repos` on the dry-run. -Never pass `--yes` to `line spawn`, `line merge`, or `line sync`. +Run only the `--dry-run` that matches the requested verb. If the user supplied `--repos` on `spawn`, repeat that same `--repos` on the dry-run and, when applying, on the live command. Prefer JSON when the command accepts `--format json`. One JSON document only; `kind=error` is blocked evidence. ## Preflight @@ -87,6 +95,26 @@ Prefer JSON when the command accepts `--format json`. One JSON document only; `k 3. `line list --format json`. Confirm the named ids exist. For `merge`, `child.parent` must equal `--into`. For `sync`, the child must have a `parent`. One-level parent only. 4. Run the matching `--dry-run line spawn|merge|sync`. This is the real gate. Non-zero exit or `kind=error` → stop and quote the CLI error. Do not proceed. +`--yes` is illegal until all four preflight steps pass. + +## Apply + +When this slash or harness turn is an explicit spawn / merge / sync ask +and every preflight step passed, run exactly one matching command in +the same turn: + +```bash +dyro --workspace line spawn --yes +dyro --workspace line merge --into --yes +dyro --workspace line sync --yes +``` + +Run only the matching verb. Repeat user-supplied `--repos` on `spawn` if present. +Do not add `--push`. Do not run git merge / switch / checkout by hand. + +Stay preflight-only when the user asked only for preflight or dry-run, +or when they ran `dyro --dry-run line …` themselves. + ## Report Use Observed / Inferred / Unknown / Plan / User action. @@ -99,13 +127,10 @@ dyro --workspace doctor or the `--dry-run line spawn|merge|sync` that failed. Do not print a live command. -If every preflight step passed, say clearly that `next.commands` did not emit this, then show **one** command for the user to run personally: - -```bash -dyro --workspace line spawn --yes -dyro --workspace line merge --into --yes -dyro --workspace line sync --yes -``` +If the user asked only for preflight or dry-run and every preflight step +passed, say clearly that `next.commands` did not emit this, then show +**one** command for the user to run personally. Do not run it. -Print only the matching verb. Repeat user-supplied `--repos` on `spawn` if present. -Do not add `--push`. Do not run that command. +If the user asked to spawn / merge / sync and every preflight step +passed, run the matching `--yes` command in this turn, then report what +ran. Do not add `--push`. diff --git a/src/dyro/integrations/assets/dyro-line-family/agents/openai.yaml b/src/dyro/integrations/assets/dyro-line-family/agents/openai.yaml index d15c5be..e79220a 100644 --- a/src/dyro/integrations/assets/dyro-line-family/agents/openai.yaml +++ b/src/dyro/integrations/assets/dyro-line-family/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "Dyro 开发线家族" - short_description: "预检子线派生 / 合入父线 / 从父线同步;不要执行" - default_prompt: "Use $dyro-line-family. Preflight with doctor/status/line list and --dry-run line spawn|merge|sync only. Never run --yes, invent --push, or git merge. A printed command is not next.commands." + short_description: "预检后、在用户请求突变时执行子线派生 / 合入父线 / 从父线同步" + default_prompt: "Use $dyro-line-family. Preflight before Apply. --yes is illegal until all four preflight steps pass. If the user asked to spawn|merge|sync and preflight passed, run the matching --yes in this turn. Never invent --push or git merge. Stay preflight-only for a dry-run/preflight ask." diff --git a/src/dyro/integrations/manager.py b/src/dyro/integrations/manager.py index 2839d2e..d31fa0b 100644 --- a/src/dyro/integrations/manager.py +++ b/src/dyro/integrations/manager.py @@ -55,7 +55,7 @@ TASK_MERGE_ASSET_VERSION = 1 LINE_FAMILY_INTEGRATION_ID = "line-family" LINE_FAMILY_SKILL_NAME = "dyro-line-family" -LINE_FAMILY_ASSET_VERSION = 1 +LINE_FAMILY_ASSET_VERSION = 2 MANIFEST_SCHEMA_VERSION = 2 LEGACY_MANIFEST_SCHEMA_VERSION = 1 _SHA256_PREFIX = "sha256:" diff --git a/src/dyro/updates.py b/src/dyro/updates.py index 4dcfceb..9cb5fa4 100644 --- a/src/dyro/updates.py +++ b/src/dyro/updates.py @@ -337,7 +337,8 @@ def build_update_plan( ( uv, "tool", - "upgrade", + "install", + "--force", "--default-index", PYPI_SIMPLE_URL, "--no-config", diff --git a/tests/test_cli.py b/tests/test_cli.py index 8f98b39..aeb6734 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2256,6 +2256,6 @@ def test_package_version_matches_pyproject(self) -> None: from dyro import __version__ metadata = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8")) - self.assertEqual(metadata["project"]["version"], "0.7.11") - self.assertEqual(__version__, "0.7.11") + self.assertEqual(metadata["project"]["version"], "0.7.12") + self.assertEqual(__version__, "0.7.12") self.assertEqual(__version__, metadata["project"]["version"]) diff --git a/tests/test_console_artifacts.py b/tests/test_console_artifacts.py index 1a488d3..c345860 100644 --- a/tests/test_console_artifacts.py +++ b/tests/test_console_artifacts.py @@ -225,12 +225,12 @@ def test_page_and_manifest_keep_p3_fail_closed_pins(self) -> None: self.assertNotEqual(refresh_at, -1) self.assertNotIn(b"/artifacts", script.body[refresh_at:next_fn]) - def test_package_version_is_0_7_11(self) -> None: + def test_package_version_is_0_7_12(self) -> None: import tomllib from pathlib import Path metadata = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8")) - self.assertEqual(metadata["project"]["version"], "0.7.11") + self.assertEqual(metadata["project"]["version"], "0.7.12") class ConsoleArtifactServiceTests(WorkspaceCase): diff --git a/tests/test_integrations.py b/tests/test_integrations.py index 7b41e3e..8e81cde 100644 --- a/tests/test_integrations.py +++ b/tests/test_integrations.py @@ -178,6 +178,9 @@ def test_packaged_skill_is_concise_and_has_required_metadata(self) -> None: self.assertNotIn("dyro image", content) self.assertIn("skip global discovery", content) self.assertIn("Never add `--include-paths`", content) + self.assertIn("already completed preflight this turn", content) + self.assertIn("line-family is the writer", content) + self.assertIn("Default forbid when line-family is not the active ask", content) for private_pattern in ( r"/Users/[^<\s]", r"/home/[^<\s]", @@ -225,7 +228,7 @@ def test_packaged_dispatch_skill_is_concise_and_has_required_metadata( if ": " in line: self.assertTrue(line.split(": ", 1)[1].startswith('"')) - def test_packaged_line_family_skill_is_preflight_only(self) -> None: + def test_packaged_line_family_skill_applies_when_user_asked(self) -> None: root = manager._asset_root("line-family") skill = root / "SKILL.md" metadata = root / "agents" / "openai.yaml" @@ -236,8 +239,20 @@ def test_packaged_line_family_skill_is_preflight_only(self) -> None: self.assertIn("name: dyro-line-family", content) self.assertIn("disable-model-invocation: true", content) self.assertIn("user-invocable: true", content) - self.assertIn("Never pass `--yes`", content) - self.assertIn("Do not invent `--yes` or `--push`", content) + self.assertNotIn("不要执行", content) + self.assertNotIn("Never execute the mutation", content) + self.assertNotIn("Print only the matching verb", content) + self.assertNotIn("Do not run that command", content) + self.assertIn("Run only the matching verb", content) + self.assertIn("illegal until all four preflight", content) + self.assertLess( + content.index("## Preflight"), + content.index("## Apply"), + ) + self.assertIn("required, not optional", content) + self.assertIn("same turn", content) + self.assertIn("Stay preflight-only", content) + self.assertIn("Do not invent `--push`", content) self.assertIn("Do not add `--push`", content) self.assertIn("line spawn --yes", content) self.assertIn("line merge --into --yes", content) @@ -245,6 +260,7 @@ def test_packaged_line_family_skill_is_preflight_only(self) -> None: self.assertIn("--dry-run line spawn", content) self.assertIn("--dry-run line merge", content) self.assertIn("--dry-run line sync", content) + self.assertIn("report what", content) self.assertIn("git `main`", content) self.assertIn("/dyro-task-merge", content) self.assertNotIn("Any `FAIL` → stop", content) @@ -259,8 +275,16 @@ def test_packaged_line_family_skill_is_preflight_only(self) -> None: self.assertIn("`line inbox`", content) self.assertIn("`line ack`", content) self.assertIn("must not call `line post`", content) - self.assertIn("$dyro-line-family", metadata.read_text(encoding="utf-8")) - for line in metadata.read_text(encoding="utf-8").splitlines(): + yaml_text = metadata.read_text(encoding="utf-8") + self.assertIn("$dyro-line-family", yaml_text) + self.assertNotIn("不要执行", yaml_text) + self.assertNotIn("Print only the matching verb", yaml_text) + self.assertNotIn("Do not run that command", yaml_text) + self.assertNotIn("Never execute the mutation", yaml_text) + self.assertIn("Preflight before Apply", yaml_text) + self.assertIn("illegal until all four preflight", yaml_text) + self.assertIn("matching --yes", yaml_text) + for line in yaml_text.splitlines(): if ": " in line: self.assertTrue(line.split(": ", 1)[1].startswith('"')) @@ -271,9 +295,16 @@ def test_packaged_line_family_skill_is_preflight_only(self) -> None: executor = (manager._asset_root("executor") / "SKILL.md").read_text( encoding="utf-8" ) + executor_plain = " ".join(executor.split()) self.assertIn("`line spawn`", executor) self.assertIn("`line merge`", executor) self.assertIn("`line sync`", executor) + self.assertIn("already completed preflight this turn", executor) + self.assertIn("line-family is the writer", executor) + self.assertIn( + "Default forbid when line-family is not the active ask", + executor_plain, + ) def test_dispatch_skill_installs_independently_from_control_plane(self) -> None: self.assertEqual( diff --git a/tests/test_release_gates.py b/tests/test_release_gates.py index 7b887fb..1b35fd3 100644 --- a/tests/test_release_gates.py +++ b/tests/test_release_gates.py @@ -32,7 +32,7 @@ def test_physics_train_refuses_published_0_6_9_tag(self) -> None: def test_0_7_release_runs_gates_without_claiming_1_0(self) -> None: stdout = StringIO() with redirect_stdout(stdout): - code = main(["--root", str(ROOT), "--release-tag", "v0.7.11"]) + code = main(["--root", str(ROOT), "--release-tag", "v0.7.12"]) self.assertEqual(code, 0) self.assertIn("0.7 gates present", stdout.getvalue()) self.assertNotIn("1.0 gates present", stdout.getvalue()) diff --git a/tests/test_updates.py b/tests/test_updates.py index e09d67b..706de0e 100644 --- a/tests/test_updates.py +++ b/tests/test_updates.py @@ -245,18 +245,23 @@ def which(name: str) -> str | None: pip_available=True, ) + self.assertEqual(uv.manager, "uv tool") + self.assertEqual(uv.scope, "当前 uv tool 隔离环境") self.assertEqual( uv.argv, ( "/bin/uv", "tool", - "upgrade", + "install", + "--force", "--default-index", "https://pypi.org/simple", "--no-config", "dyro==0.5.6", ), ) + self.assertNotIn("upgrade", uv.argv) + self.assertEqual(uv.constraint, "") self.assertEqual( pipx.argv, ( @@ -283,6 +288,119 @@ def which(name: str) -> str | None: ), ) self.assertNotIn("sh", uv.argv) + self.assertEqual(pipx.manager, "pipx") + self.assertEqual(pip.manager, "pip") + + def test_uv_tool_plan_replaces_exact_pin_via_install_force(self) -> None: + commands = {"uv": "/bin/uv", "pipx": "/bin/pipx"} + + def which(name: str) -> str | None: + return commands.get(name) + + prefixes = ( + "/home/me/.local/share/uv/tools/dyro", + "/home/me/.local/share/uv/tools/dyro/extra", + r"C:\Users\me\AppData\Roaming\uv\tools\dyro", + ) + for prefix in prefixes: + with self.subTest(prefix=prefix): + plan = build_update_plan( + "0.7.12", + prefix=prefix, + executable="/tool/bin/python", + which=which, + editable=False, + pip_available=True, + ) + self.assertEqual(plan.manager, "uv tool") + self.assertEqual(plan.scope, "当前 uv tool 隔离环境") + self.assertEqual(plan.argv[0], "/bin/uv") + self.assertEqual(plan.argv[1:4], ("tool", "install", "--force")) + self.assertNotIn("upgrade", plan.argv) + self.assertEqual( + plan.argv[4:], + ( + "--default-index", + "https://pypi.org/simple", + "--no-config", + "dyro==0.7.12", + ), + ) + self.assertEqual(plan.constraint, "") + + pipx = build_update_plan( + "0.7.12", + prefix="/home/me/.local/pipx/venvs/dyro", + executable="/tool/bin/python", + which=which, + editable=False, + ) + self.assertEqual(pipx.manager, "pipx") + self.assertEqual( + pipx.argv, + ( + "/bin/pipx", + "upgrade", + "--index-url", + "https://pypi.org/simple", + "dyro", + ), + ) + self.assertEqual(pipx.constraint, "dyro==0.7.12") + + pip = build_update_plan( + "0.7.12", + prefix="/home/me/venv", + executable="/home/me/venv/bin/python", + which=lambda _: None, + editable=False, + pip_available=True, + ) + self.assertEqual(pip.manager, "pip") + self.assertIn("--upgrade", pip.argv) + self.assertNotIn("--force", pip.argv) + + uv_pip = build_update_plan( + "0.7.12", + prefix="/home/me/venv", + base_prefix="/usr/local", + executable="/home/me/venv/bin/python", + which=lambda name: "/bin/uv" if name == "uv" else None, + editable=False, + pip_available=False, + ) + self.assertEqual(uv_pip.manager, "uv pip") + self.assertEqual(uv_pip.argv[1:3], ("pip", "install")) + self.assertIn("--upgrade", uv_pip.argv) + self.assertNotIn("--force", uv_pip.argv) + + with self.assertRaisesRegex(DyroError, "找不到 uv"): + build_update_plan( + "0.7.12", + prefix="/home/me/.local/share/uv/tools/dyro", + which=lambda _: None, + editable=False, + ) + + def test_changelog_0_7_12_recovery_matches_planner_safe_uv_tool_install( + self, + ) -> None: + root = Path(__file__).resolve().parents[1] + changelog = (root / "CHANGELOG.md").read_text(encoding="utf-8") + section = changelog.split("## 0.7.12", 1)[1].split("## 0.7.11", 1)[0] + normalized = " ".join(section.split()) + self.assertIn("build_update_plan", section) + self.assertIn("https://pypi.org/simple", section) + self.assertIn("--no-config", section) + self.assertIn( + "uv tool install --force --default-index https://pypi.org/simple " + "--no-config dyro==0.7.12", + normalized, + ) + self.assertIn("not bare", normalized) + self.assertIn("uv tool install dyro==0.7.12 --force", normalized) + self.assertIn("integration sync", section) + self.assertIn("asset version 2", normalized) def test_refuses_to_replace_an_editable_source_checkout(self) -> None: with self.assertRaisesRegex(DyroError, "editable"): diff --git a/uv.lock b/uv.lock index 5e4aa09..bfd3e6e 100644 --- a/uv.lock +++ b/uv.lock @@ -286,7 +286,7 @@ wheels = [ [[package]] name = "dyro" -version = "0.7.11" +version = "0.7.12" source = { editable = "." } dependencies = [ { name = "cryptography" },