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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ jobs:
test -x "$smoke_root/wheel-venv/bin/dyro"
test ! -e "$smoke_root/wheel-venv/bin/dyro-bridge"
test ! -e "$smoke_root/wheel-venv/bin/dyro-mcp"
"$smoke_root/wheel-venv/bin/python" -I -c "import importlib.util; assert importlib.util.find_spec('dyro.bridge') is None"
DYRO_LOCAL_AGENT_DISPATCH_HOME="$smoke_root/wheel-dispatch-home" "$smoke_root/wheel-venv/bin/dyro" dispatch doctor >"$smoke_root/dispatch-doctor.json"
"$smoke_root/wheel-venv/bin/python" "$GITHUB_WORKSPACE/tools/verify_bundle_stranger.py" "$smoke_root/wheel-venv/bin/dyro"
uv run python -m venv "$smoke_root/sdist-venv"
Expand All @@ -99,6 +100,7 @@ jobs:
test -x "$smoke_root/sdist-venv/bin/dyro"
test ! -e "$smoke_root/sdist-venv/bin/dyro-bridge"
test ! -e "$smoke_root/sdist-venv/bin/dyro-mcp"
"$smoke_root/sdist-venv/bin/python" -I -c "import importlib.util; assert importlib.util.find_spec('dyro.bridge') is None"
"$smoke_root/sdist-venv/bin/python" "$GITHUB_WORKSPACE/tools/verify_bundle_stranger.py" "$smoke_root/sdist-venv/bin/dyro"

windows-dispatch-import:
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

## Unreleased

## 0.7.3 - 2026-08-18

- Console home no longer treats empty attention as idle. The overview
says unread, workspace cards say "摘要未列出关注项", and zero-count
attention chips are not marked success. Ready work still can say
there is nothing that needs you on the spot.
- Bare `dyro` home prints the workspace alias, not the local root.
Unhealthy `next` repair findings and commands stay path-free and
use `--workspace`. Isolated Console no longer allowlists `task next`.
- CI wheel/sdist smoke now asserts `dyro.bridge` is absent, matching
the publish gate.

## 0.7.2 - 2026-08-17

- `objective explain` and a ready `next` now lead with a three-line
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0006-delivery-physics-and-capability-plane.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@

- 产品叙事从「启动 agent」转为「核验完成」。
- `0.7.0` 落地 Proof、Capability Card、Host Compiler 与 `verify-bundle`。`trusted_usage` 默认 `false`;生产 `BudgetUsage` / `BudgetRequest` 从 Card 读取该字段。`objective tick` 对 automatic Objective 做预算预览(`automatic=True`),受监督 apply 仍 `automatic=False`。未信任用量只在存在 `workspace.max_provider_usage` 时硬停。Proof 仍不接入 `no_progress`。Console summary 保持 `proof_inspection=not_inspected`,不探 Git / Proof;`dyro objective attention` 走完整快照,可报 `PROOF_DECAYED`。两套入口不得写成同一套 Proof 展示。
- Console 独立 inspect、`trigger_observation`、陌生人核验与叙事锁已在 `0.7.1` 落地。`0.7.2` 收口换工具 briefing 与 Console「现在需要你」。后续功能与产品面收口继续开发和上线,版本号保持 `0.7.x`。
- Console 独立 inspect、`trigger_observation`、陌生人核验与叙事锁已在 `0.7.1` 落地。`0.7.2` 收口换工具 briefing 与 Console「现在需要你」。`0.7.3` 把空关注收回未读,并去掉首页与 `next` repair 的路径泄漏。后续功能与产品面收口继续开发和上线,版本号保持 `0.7.x`。
- 可携带核验的对外承诺仍是:陌生人拿着 Proof Bundle 和自己提供的 git 对象,能得到与源机**相同的完整性结论**(字节仍在、钉死 SHA 可解析)。这不是身份证明,也不是「现在工作区还能 merge」。`schema_version = 1` 的合同在 `0.7.x` 锁住语义;冻结成 `1.0.0` 身份号须另做产品决定。
- 实施成本是新的投影层与兼容层,而不是第二套调度器。

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,6 @@ Dyro 的交付拓扑与之**实质相近**:TaskGraph(`depends_on` / conflict

未来的 adapter、通知、签名规则、发布平台与审批系统应使用 Python entry point 或独立 Profile 扩展包接入;不要把某个组织的策略加入 core 默认行为。

`0.7.0` 把已有证据物理学抽成可复验的 Proof,并把 argv adapter 升级为 Capability Card,再把定律编译为只收缩权威的宿主投影。衰减与现有 merge / 下游检查同真值;`proof verify` 看当前工作区,`verify-bundle` 只核完整性,两套结论不得混称。Console summary 与 `dyro objective attention` 不是同一套 Proof 展示。Console 独立 inspect、`trigger_observation` 与可携带核验门禁已在 `0.7.1` 落地。`0.7.2` 把换工具后的开场白收成事项加一条只读下一步,不另开会话层。后续功能继续在 `0.7.x` 开发和上线,不另开 `0.8.0` / `0.9.0` / `1.0.0` 功能号。可携带核验是 Proof Bundle 加调用方提供的 git 对象,核验完整性而不是身份,也不承诺与当前 merge 同一套 `live`。这不另造 TaskGraph 或完成状态机;见 [`交付物理学`](designs/delivery-physics.md) 与 [`ADR-0006`](adr/0006-delivery-physics-and-capability-plane.md)。
`0.7.0` 把已有证据物理学抽成可复验的 Proof,并把 argv adapter 升级为 Capability Card,再把定律编译为只收缩权威的宿主投影。衰减与现有 merge / 下游检查同真值;`proof verify` 看当前工作区,`verify-bundle` 只核完整性,两套结论不得混称。Console summary 与 `dyro objective attention` 不是同一套 Proof 展示。Console 独立 inspect、`trigger_observation` 与可携带核验门禁已在 `0.7.1` 落地。`0.7.2` 把换工具后的开场白收成事项加一条只读下一步,不另开会话层。`0.7.3` 把 Console 空关注收回未读,并不再在首页和下一条修复命令里漏本地路径。后续功能继续在 `0.7.x` 开发和上线,不另开 `0.8.0` / `0.9.0` / `1.0.0` 功能号。可携带核验是 Proof Bundle 加调用方提供的 git 对象,核验完整性而不是身份,也不承诺与当前 merge 同一套 `live`。这不另造 TaskGraph 或完成状态机;见 [`交付物理学`](designs/delivery-physics.md) 与 [`ADR-0006`](adr/0006-delivery-physics-and-capability-plane.md)。

开发者侧的可选本地多 Agent 派发(五段式任务契约、注入前机密守卫、locator 核验、隔离 patch)与上述控制面分层并列,随 `dyro` 安装包分发(`dyro dispatch` / `import experiments.local_agent_dispatch`),但**不**替代 gates/合并。同时写多块走 Core Peer Wave(task worktree + `conflict_group`),见 [`peer-wave-execution.md`](designs/peer-wave-execution.md)、[`ADR-0002`](adr/0002-optional-local-agent-dispatch.md)、[`多智能体编排纪律`](agent-orchestration-discipline.md) 与 [`可选本地 Agent 派发设计`](designs/optional-local-agent-dispatch.md)。
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.2"
version = "0.7.3"
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.2"},
"client": {"name": "dyro-agent-bridge-skill", "version": "0.7.3"},
"operation": "bridge.capabilities.compact",
"input": {}
}
Expand Down
17 changes: 10 additions & 7 deletions src/dyro/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2433,29 +2433,32 @@ def cmd_next(args: argparse.Namespace) -> None:
bool(absent_bootstrap_ids) and set(failures) == expected_bootstrap_failures
)
repair_commands = (
[_scoped_command(args, config, "bootstrap", "--yes")]
[_briefing_command(args, config, "bootstrap", "--yes")]
if bootstrap_applicable
else []
)
findings = [
_doctor_finding_payload(item, include_paths=False) for item in failures
]
if args.format == "json":
_print_control_plane_json(
"next_step",
state="needs_repair",
summary="工作区还不能开始任务。",
commands=repair_commands,
diagnostic_commands=[_scoped_command(args, config, "doctor")],
diagnostic_commands=[_briefing_command(args, config, "doctor")],
mutation_available=bootstrap_applicable,
findings=[_finding_payload(item) for item in failures],
findings=findings,
)
return
print("工作区还不能开始任务:")
for finding in failures:
print(" " + finding)
print(f"修复后运行:{_scoped_command(args, config, 'doctor')}")
for finding in findings:
print(f" {finding['status']} {finding['message']}")
print(f"修复后运行:{_briefing_command(args, config, 'doctor')}")
if bootstrap_applicable:
print(
"缺失仓库均已配置 remote,可运行:"
+ _scoped_command(args, config, "bootstrap", "--yes")
+ _briefing_command(args, config, "bootstrap", "--yes")
)
return
lines = list_lines(config, read_budget=budget)
Expand Down
4 changes: 2 additions & 2 deletions src/dyro/console/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ class ConsoleAsset:
),
"app.js": (
"text/javascript; charset=utf-8",
"28320497905d3c560e52f2ebd1bd3a807c1d5ae31da07ba3a9a534eabe3e081d",
32622,
"beaa5e872359895d9af8aa0ae17c0ebbf60e369b4df4d46fd1e022164fe427af",
33027,
),
"styles.css": (
"text/css; charset=utf-8",
Expand Down
21 changes: 16 additions & 5 deletions src/dyro/console/assets/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@ function overviewState(attention, workspaces) {
if (count(attention && attention.ready)) return "有工作可推进";
if (count(attention && attention.waiting)) return "等待外部条件";
if (count(attention && attention.paused)) return "存在已暂停工作";
return "全部正常";
if (readableWorkspaceCount(workspaces)) return "关注项未知";
return "状态不完整";
}

function workspaceMatter(summary) {
Expand All @@ -308,7 +309,7 @@ function workspaceMatter(summary) {
if (count(attention.ready)) return "有工作可以继续推进";
if (count(attention.waiting)) return "在等外部条件";
if (count(attention.paused)) return "有工作已暂停";
return "可以先观察";
return "摘要未列出关注项";
}

function needsYouWorkspaces(workspaces) {
Expand Down Expand Up @@ -336,9 +337,20 @@ function renderNeedsYou(workspaces, total) {
const items = needsYouWorkspaces(workspaces);
if (!items.length) {
const readable = readableWorkspaceCount(workspaces);
root.append(element("p", readable
if (!readable) {
root.append(element("p", "项目状态还不完整,关注项未知。"));
return;
}
const listed = workspaces.some((summary) => {
if (text(summary.availability) !== "available") return false;
const attention = summary.attention_counts || {};
return Boolean(
count(attention.ready) || count(attention.waiting) || count(attention.paused)
);
});
root.append(element("p", listed
? "现在没有需要你当场处理的项目。"
: "项目状态还不完整,关注项未知。"));
: "摘要未列出关注项。"));
return;
}
for (const summary of items) {
Expand Down Expand Up @@ -456,7 +468,6 @@ function renderCounts(attention) {
card.className = "count";
if (key === "repair_required" && count(attention && attention[key])) card.dataset.level = "danger";
if (key === "needs_user" && count(attention && attention[key])) card.dataset.level = "warning";
if (!count(attention && attention[key])) card.dataset.level = "success";
card.append(element("strong", String(count(attention && attention[key]))), element("span", label));
root.append(card);
}
Expand Down
2 changes: 1 addition & 1 deletion src/dyro/console/inspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ def _safe_command(command: object, alias: object) -> bool:
escaped_alias = re.escape(alias)
return bool(
re.fullmatch(
rf"dyro --workspace {escaped_alias}(?: (?:doctor|task next|objective (?:explain|tick|attention) [A-Za-z0-9][A-Za-z0-9._-]{{0,79}}))?",
rf"dyro --workspace {escaped_alias}(?: (?:doctor|objective (?:explain|tick|attention) [A-Za-z0-9][A-Za-z0-9._-]{{0,79}}))?",
command,
)
)
Expand Down
2 changes: 1 addition & 1 deletion src/dyro/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -2165,7 +2165,7 @@ def _run_config_home(
config: Config, record: WorkspaceRecord | None, dry_run: bool
) -> None:
print("\n" + title(f"━━ {config.name} ━━"))
print(f"工作区 {value(config.root)}")
print(f"工作区 {value(config.name)}")
failures = [finding for finding in doctor(config) if finding.startswith("FAIL")]
if failures:
print(
Expand Down
7 changes: 5 additions & 2 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1202,8 +1202,9 @@ def test_control_plane_next_only_offers_applicable_bootstrap(self) -> None:
self.assertEqual(unavailable["commands"], [])
self.assertEqual(
unavailable["diagnostic_commands"],
[f"dyro --root {self.root.resolve()} doctor"],
["dyro --workspace test-workspace doctor"],
)
self.assertNotIn(str(self.root.resolve()), json.dumps(unavailable))

config_path = self.root / "dyro.toml"
config_path.write_text(
Expand All @@ -1217,8 +1218,9 @@ def test_control_plane_next_only_offers_applicable_bootstrap(self) -> None:
self.assertTrue(applicable["mutation_available"])
self.assertEqual(
applicable["commands"],
[f"dyro --root {self.root.resolve()} bootstrap --yes"],
["dyro --workspace test-workspace bootstrap --yes"],
)
self.assertNotIn(str(self.root.resolve()), json.dumps(applicable))

def test_control_plane_next_never_offers_bootstrap_through_symlink_parent(
self,
Expand Down Expand Up @@ -1667,6 +1669,7 @@ def test_bare_dyro_prints_the_same_follow_up_before_the_home_menu(self) -> None:
self.assertIn("今天做什么", text)
self.assertIn("做下一步,不打开编码工具", text)
self.assertNotIn("objective apply", text)
self.assertNotIn(str(self.root.resolve()), text)

def test_bare_dyro_without_objectives_does_not_invent_a_briefing(self) -> None:
output = StringIO()
Expand Down
2 changes: 2 additions & 0 deletions tests/test_console_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ def test_shell_exposes_a_semantic_command_center(self) -> None:
self.assertIn("摘要未列出关注项".encode(), script.body)
self.assertIn("关注项未知".encode(), script.body)
self.assertNotIn("没有需要关注的事项".encode(), script.body)
self.assertNotIn("全部正常".encode(), script.body)
self.assertNotIn("可以先观察".encode(), script.body)
self.assertIn("function renderTaskStatusCounts".encode(), script.body)
self.assertIn("待签核".encode(), script.body)
self.assertIn("尚未核验是否已合入".encode(), script.body)
Expand Down
15 changes: 15 additions & 0 deletions tests/test_console_inspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,21 @@ def test_parent_rejects_system_tool_probe_leak(self) -> None:
self.assertNotIn("git", str(error.exception))
self.assertNotIn("argv", str(error.exception))

def test_isolated_command_allowlist_rejects_task_next(self) -> None:
self.assertTrue(
IsolatedOverviewService._safe_command(
"dyro --workspace demo objective tick release", "demo"
)
)
self.assertTrue(
IsolatedOverviewService._safe_command("dyro --workspace demo doctor", "demo")
)
self.assertFalse(
IsolatedOverviewService._safe_command(
"dyro --workspace demo task next", "demo"
)
)


if __name__ == "__main__":
unittest.main()
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.2"])
code = main(["--root", str(ROOT), "--release-tag", "v0.7.3"])
self.assertEqual(code, 0)
self.assertIn("0.7 gates present", stdout.getvalue())
self.assertNotIn("1.0 gates present", stdout.getvalue())
Expand Down
3 changes: 3 additions & 0 deletions tests/test_release_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ def test_ci_no_longer_ships_agent_bridge_zero_effect_gate(self) -> None:
self.assertIn("dyro-dispatch','SKILL.md", workflow)
self.assertIn("dyro-dispatch','agents','openai.yaml", workflow)
self.assertGreaterEqual(workflow.count("verify_bundle_stranger.py"), 2)
self.assertGreaterEqual(
workflow.count("find_spec('dyro.bridge') is None"), 2
)

def test_default_wheel_stays_bridge_free(self) -> None:
metadata = (ROOT / "pyproject.toml").read_text(encoding="utf-8")
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading