Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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==<version>` 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
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/dyro/bridge/skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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": {}
}
Expand Down
5 changes: 3 additions & 2 deletions src/dyro/integrations/assets/dyro-control-plane/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ When the request already supplies a workspace alias, skip global discovery and u
- Git state: `dyro --workspace <alias> status --format json`
- Health: `dyro --workspace <alias> doctor --format json`
- One safe next step: `dyro --workspace <alias> 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 <alias> 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 <alias> 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 <alias> line inbox --unacked --format json`. Report Observed as returned. User action must not invent `line post`, `line ack`, or merge.
- Change Sets: `dyro --workspace <alias> changeset list --format json` or `dyro --workspace <alias> changeset verify <id> --format json`
- Installed control-plane Skill health: `dyro integration status skill --format json`
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 7 additions & 3 deletions src/dyro/integrations/assets/dyro-executor/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
57 changes: 41 additions & 16 deletions src/dyro/integrations/assets/dyro-line-family/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <parent> <child> | merge <child> --into <parent> | sync <child>"
metadata:
short-description: "预检子线派生 / 合入父线 / 从父线同步;不要执行"
short-description: "预检后、在用户请求突变时执行子线派生 / 合入父线 / 从父线同步"
---

# Dyro 开发线家族

只做预检。这不是第一方自动座位,也不是 `task merge`。
先预检,再按用户是否请求突变决定是否执行。这不是第一方自动座位,也不是 `task merge`。

`line spawn` / `line merge` / `line sync` 只处理 **一层父线**:
- `spawn`:从已有父线派生子开发线(不是任务)
Expand All @@ -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`.
Expand Down Expand Up @@ -72,8 +81,7 @@ dyro --workspace <alias> --dry-run line merge <child> --into <parent>
dyro --workspace <alias> --dry-run line sync <child>
```

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
Expand All @@ -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 <alias> line spawn <parent> <child> --yes
dyro --workspace <alias> line merge <child> --into <parent> --yes
dyro --workspace <alias> line sync <child> --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.
Expand All @@ -99,13 +127,10 @@ dyro --workspace <alias> 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 <alias> line spawn <parent> <child> --yes
dyro --workspace <alias> line merge <child> --into <parent> --yes
dyro --workspace <alias> line sync <child> --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`.
Original file line number Diff line number Diff line change
@@ -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."
2 changes: 1 addition & 1 deletion src/dyro/integrations/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:"
Expand Down
3 changes: 2 additions & 1 deletion src/dyro/updates.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,8 @@ def build_update_plan(
(
uv,
"tool",
"upgrade",
"install",
"--force",
"--default-index",
PYPI_SIMPLE_URL,
"--no-config",
Expand Down
4 changes: 2 additions & 2 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
4 changes: 2 additions & 2 deletions tests/test_console_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
41 changes: 36 additions & 5 deletions tests/test_integrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]",
Expand Down Expand Up @@ -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"
Expand All @@ -236,15 +239,28 @@ 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 <parent> <child> --yes", content)
self.assertIn("line merge <child> --into <parent> --yes", content)
self.assertIn("line sync <child> --yes", content)
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)
Expand All @@ -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('"'))

Expand All @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion tests/test_release_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
Loading