Skip to content

Commit 117e963

Browse files
committed
feat(开发线): apply line-family spawn/merge/sync after preflight
When the user asked to spawn, merge, or sync, /dyro-line-family must run the matching --yes command after preflight. Stay preflight-only for a dry-run ask. Never invent --push.
1 parent a75229b commit 117e963

5 files changed

Lines changed: 64 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@
1313
`--no-config` stay required; pipx, pip, and uv-pip paths are unchanged.
1414
If you already installed with `uv tool install dyro==…`, run one last
1515
`uv tool install dyro==0.7.12 --force`. After that, `dyro update` works.
16+
- `/dyro-line-family` still preflights with unchanged doctor blocking-FAIL
17+
rules (`status` dirty=0, `line list`, matching `--dry-run`). When the
18+
user asked to spawn / merge / sync (slash or harness; the mutation is
19+
the ask) and preflight passes, the agent must run the matching
20+
`line spawn|merge|sync --yes` in the same turn. It still never invents
21+
`--push` and must not git merge / switch by hand. Preflight-only
22+
remains when the user asked only for preflight or dry-run, or ran
23+
`dyro --dry-run line …` themselves. CLI `--dry-run` is unchanged. On
24+
apply success, report what ran. On preflight fail, `User action` is
25+
still only `doctor` or the failed dry-run.
1626

1727
## 0.7.11 - 2026-08-23
1828

src/dyro/integrations/assets/dyro-line-family/SKILL.md

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@ name: dyro-line-family
33
description: >
44
Preflight Dyro line-family ops: spawn a child line, merge a child into
55
its direct parent, or sync the parent into the child. Use when the user
6-
runs /dyro-line-family. Never execute the mutation.
6+
runs /dyro-line-family. After preflight passes, apply the matching
7+
`--yes` command in the same turn when the user asked to spawn, merge,
8+
or sync. Stay preflight-only when they asked only for preflight or
9+
dry-run.
710
disable-model-invocation: true
811
user-invocable: true
912
argument-hint: "[workspace-alias] spawn <parent> <child> | merge <child> --into <parent> | sync <child>"
1013
metadata:
11-
short-description: "预检子线派生 / 合入父线 / 从父线同步;不要执行"
14+
short-description: "预检后、在用户请求突变时执行子线派生 / 合入父线 / 从父线同步"
1215
---
1316

1417
# Dyro 开发线家族
1518

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

1821
`line spawn` / `line merge` / `line sync` 只处理 **一层父线**
1922
- `spawn`:从已有父线派生子开发线(不是任务)
@@ -30,14 +33,19 @@ If the user means **done task branch → owning line**, stop and point at `/dyro
3033

3134
Do not run any of:
3235

33-
- `dyro line spawn` / `line merge` / `line sync` with `--yes`
3436
- `git merge` / `git switch` / `git checkout`
3537
- `task merge`, `task signoff`, `task gates`, `task review`, `task run`
3638
- `objective apply`, `dispatch`, `console`, push, publish
3739
- `line create` / hotfix or Change Set creation
3840
- `line post` / `line inbox` / `line ack`
3941

40-
Do not invent `--yes` or `--push`. Do not add `--push`.
42+
Do not invent `--push`. Do not add `--push`.
43+
Forbid `--yes` only when the user did not ask for the mutation, asked
44+
only for preflight or dry-run, or they themselves ran
45+
`dyro --dry-run line …`. CLI `--dry-run` is unchanged.
46+
After successful preflight for an explicit spawn / merge / sync ask
47+
(the mutation is the ask), running the matching
48+
`line spawn|merge|sync --yes` in the same turn is required, not optional.
4149
This slash does not send family signals and must not call `line post`, `inbox`, or `ack`.
4250
Default is no push; `policy.allow_push` is not permission to invent it.
4351
Do not restore a drifted line branch. Do not add `--include-paths`.
@@ -72,10 +80,27 @@ dyro --workspace <alias> --dry-run line merge <child> --into <parent>
7280
dyro --workspace <alias> --dry-run line sync <child>
7381
```
7482

75-
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.
76-
Never pass `--yes` to `line spawn`, `line merge`, or `line sync`.
83+
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.
7784
Prefer JSON when the command accepts `--format json`. One JSON document only; `kind=error` is blocked evidence.
7885

86+
## Apply
87+
88+
When this slash or harness turn is an explicit spawn / merge / sync ask
89+
and every preflight step passed, run exactly one matching command in
90+
the same turn:
91+
92+
```bash
93+
dyro --workspace <alias> line spawn <parent> <child> --yes
94+
dyro --workspace <alias> line merge <child> --into <parent> --yes
95+
dyro --workspace <alias> line sync <child> --yes
96+
```
97+
98+
Print only the matching verb. Repeat user-supplied `--repos` on `spawn` if present.
99+
Do not add `--push`. Do not run git merge / switch / checkout by hand.
100+
101+
Stay preflight-only when the user asked only for preflight or dry-run,
102+
or when they ran `dyro --dry-run line …` themselves.
103+
79104
## Preflight
80105

81106
1. `doctor` is observational. Do not treat every `FAIL` as a stop.
@@ -99,13 +124,10 @@ dyro --workspace <alias> doctor
99124

100125
or the `--dry-run line spawn|merge|sync` that failed. Do not print a live command.
101126

102-
If every preflight step passed, say clearly that `next.commands` did not emit this, then show **one** command for the user to run personally:
127+
If the user asked only for preflight or dry-run and every preflight step
128+
passed, say clearly that `next.commands` did not emit this, then show
129+
**one** command for the user to run personally. Do not run it.
103130

104-
```bash
105-
dyro --workspace <alias> line spawn <parent> <child> --yes
106-
dyro --workspace <alias> line merge <child> --into <parent> --yes
107-
dyro --workspace <alias> line sync <child> --yes
108-
```
109-
110-
Print only the matching verb. Repeat user-supplied `--repos` on `spawn` if present.
111-
Do not add `--push`. Do not run that command.
131+
If the user asked to spawn / merge / sync and every preflight step
132+
passed, run the matching `--yes` command in this turn, then report what
133+
ran. Do not add `--push`.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
interface:
22
display_name: "Dyro 开发线家族"
3-
short_description: "预检子线派生 / 合入父线 / 从父线同步;不要执行"
4-
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."
3+
short_description: "预检后、在用户请求突变时执行子线派生 / 合入父线 / 从父线同步"
4+
default_prompt: "Use $dyro-line-family. Preflight with doctor/status/line list and matching --dry-run. 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."

src/dyro/integrations/manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
TASK_MERGE_ASSET_VERSION = 1
5656
LINE_FAMILY_INTEGRATION_ID = "line-family"
5757
LINE_FAMILY_SKILL_NAME = "dyro-line-family"
58-
LINE_FAMILY_ASSET_VERSION = 1
58+
LINE_FAMILY_ASSET_VERSION = 2
5959
MANIFEST_SCHEMA_VERSION = 2
6060
LEGACY_MANIFEST_SCHEMA_VERSION = 1
6161
_SHA256_PREFIX = "sha256:"

tests/test_integrations.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ def test_packaged_dispatch_skill_is_concise_and_has_required_metadata(
225225
if ": " in line:
226226
self.assertTrue(line.split(": ", 1)[1].startswith('"'))
227227

228-
def test_packaged_line_family_skill_is_preflight_only(self) -> None:
228+
def test_packaged_line_family_skill_applies_when_user_asked(self) -> None:
229229
root = manager._asset_root("line-family")
230230
skill = root / "SKILL.md"
231231
metadata = root / "agents" / "openai.yaml"
@@ -236,15 +236,20 @@ def test_packaged_line_family_skill_is_preflight_only(self) -> None:
236236
self.assertIn("name: dyro-line-family", content)
237237
self.assertIn("disable-model-invocation: true", content)
238238
self.assertIn("user-invocable: true", content)
239-
self.assertIn("Never pass `--yes`", content)
240-
self.assertIn("Do not invent `--yes` or `--push`", content)
239+
self.assertNotIn("不要执行", content)
240+
self.assertNotIn("Never execute the mutation", content)
241+
self.assertIn("required, not optional", content)
242+
self.assertIn("same turn", content)
243+
self.assertIn("Stay preflight-only", content)
244+
self.assertIn("Do not invent `--push`", content)
241245
self.assertIn("Do not add `--push`", content)
242246
self.assertIn("line spawn <parent> <child> --yes", content)
243247
self.assertIn("line merge <child> --into <parent> --yes", content)
244248
self.assertIn("line sync <child> --yes", content)
245249
self.assertIn("--dry-run line spawn", content)
246250
self.assertIn("--dry-run line merge", content)
247251
self.assertIn("--dry-run line sync", content)
252+
self.assertIn("report what", content)
248253
self.assertIn("git `main`", content)
249254
self.assertIn("/dyro-task-merge", content)
250255
self.assertNotIn("Any `FAIL` → stop", content)
@@ -259,8 +264,11 @@ def test_packaged_line_family_skill_is_preflight_only(self) -> None:
259264
self.assertIn("`line inbox`", content)
260265
self.assertIn("`line ack`", content)
261266
self.assertIn("must not call `line post`", content)
262-
self.assertIn("$dyro-line-family", metadata.read_text(encoding="utf-8"))
263-
for line in metadata.read_text(encoding="utf-8").splitlines():
267+
yaml_text = metadata.read_text(encoding="utf-8")
268+
self.assertIn("$dyro-line-family", yaml_text)
269+
self.assertNotIn("不要执行", yaml_text)
270+
self.assertIn("matching --yes", yaml_text)
271+
for line in yaml_text.splitlines():
264272
if ": " in line:
265273
self.assertTrue(line.split(": ", 1)[1].startswith('"'))
266274

0 commit comments

Comments
 (0)