agentic-dev-loop 0.10.4__tar.gz → 0.11.0__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.
- {agentic_dev_loop-0.10.4/src/agentic_dev_loop.egg-info → agentic_dev_loop-0.11.0}/PKG-INFO +5 -3
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/README.md +4 -2
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/pyproject.toml +1 -1
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/core/profiles/__init__.py +4 -6
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/cli.py +3 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/graph.py +4 -2
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/onboarding/checks.py +4 -3
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/onboarding/templates/trigger-agentic-dev-loop.sh +6 -14
- agentic_dev_loop-0.11.0/src/agentic_dev_loop/engine/onboarding/upgrade.py +76 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/viz/__init__.py +2 -2
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/viz/__main__.py +5 -3
- agentic_dev_loop-0.11.0/src/agentic_dev_loop/viz/layout.py +233 -0
- agentic_dev_loop-0.11.0/src/agentic_dev_loop/viz/page.py +9 -0
- agentic_dev_loop-0.11.0/src/agentic_dev_loop/viz/server.py +470 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0/src/agentic_dev_loop.egg-info}/PKG-INFO +5 -3
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop.egg-info/SOURCES.txt +8 -1
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_029_merge_on_conflict.py +0 -5
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_033_brief_step.py +0 -2
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_035_finalize_nudge.py +6 -5
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_036_banned_commands_and_lane_boundary.py +0 -11
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_041_brief_coverage.py +1 -40
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_046_brief_prompt.py +1 -15
- agentic_dev_loop-0.11.0/tests/test_055_cli.py +23 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_055_doctor.py +1 -1
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_055_profiles.py +1 -1
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_055_templates.py +2 -2
- agentic_dev_loop-0.11.0/tests/test_055_upgrade.py +125 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_actions.py +0 -6
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_agents.py +0 -22
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_aidlc.py +0 -15
- agentic_dev_loop-0.11.0/tests/test_build_viz_page.py +68 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_cli.py +4 -61
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_config.py +1 -90
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_containers.py +1 -25
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_deps.py +2 -3
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_dry_run.py +0 -8
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_e2e_fixtures.py +0 -28
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_graph.py +0 -31
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_isolation.py +1 -65
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_isolation_pbt.py +3 -50
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_llm.py +0 -1
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_loaders.py +0 -3
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_proc.py +4 -32
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_publish.py +137 -4
- agentic_dev_loop-0.11.0/tests/test_qa_prompt.py +30 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_registry.py +0 -5
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_results.py +0 -22
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_secrets.py +2 -11
- agentic_dev_loop-0.11.0/tests/test_source_resolution.py +62 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_stages.py +0 -84
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_sync_back.py +0 -9
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_trigger_script.py +32 -47
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_unit5_pbt.py +10 -32
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_viz.py +335 -320
- agentic_dev_loop-0.11.0/tests/test_viz_demo.py +126 -0
- agentic_dev_loop-0.11.0/tests/test_viz_layout.py +275 -0
- agentic_dev_loop-0.10.4/src/agentic_dev_loop/viz/server.py +0 -1273
- agentic_dev_loop-0.10.4/tests/test_055_cli.py +0 -51
- agentic_dev_loop-0.10.4/tests/test_qa_prompt.py +0 -53
- agentic_dev_loop-0.10.4/tests/test_source_resolution.py +0 -21
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/LICENSE +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/setup.cfg +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/core/config.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/core/paths.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/core/profiles/data/dotnet-node.yaml +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/core/profiles/data/python.yaml +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/agent_error_guide.md +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/agent_error_guide.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/agents.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/allowlist.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/brief_coverage.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/brief_prompt.md +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/brief_prompt.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/containers.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/deps.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/dryrun.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/forbidden.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/isolation.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/llm.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/mounts.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/onboarding/__init__.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/onboarding/doctor.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/onboarding/init.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/onboarding/recipes/dotnet-node.yaml +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/onboarding/recipes/python.yaml +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/onboarding/recipes.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/onboarding/templates/dev.agent.md +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/onboarding/templates/env.example +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/onboarding/templates/qa.agent.md +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/onboarding/templates/review.agent.md +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/onboarding/templates.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/presets.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/secrets.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/stages.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/sync_back.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_055_init.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_055_profile_loading.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_055_recipes.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_055_schema_lock.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_agent_error_guide.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_egress_proxy.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_hostboundary.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_hostboundary_integration.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_module_contract.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_repo_ascii.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_secrets_integration.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_secrets_pipeline.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_step_workspace.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_unit5_config.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_unit5_pure.py +0 -0
- {agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/tests/test_verdict.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-dev-loop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Summary: Turn an LLM into a gated, sandboxed, resumable construction loop over your codebase.
|
|
5
5
|
Author: Luke Wilson
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -492,8 +492,10 @@ before.
|
|
|
492
492
|
`agentic-dev-loop doctor --fast` is the per-run gate the trigger runs first.
|
|
493
493
|
Hard failures abort the run (engine pin mismatch, engine runtime availability,
|
|
494
494
|
API version, git repo, `.env*` files); warnings print and continue. `doctor
|
|
495
|
-
--json` emits the same results mechanically.
|
|
496
|
-
|
|
495
|
+
--json` emits the same results mechanically. On a hard failure the trigger
|
|
496
|
+
runs `agentic-dev-loop upgrade` exactly once: it installs the latest release
|
|
497
|
+
with pipx and rewrites `.engine-version` to match, then the run fails hard if
|
|
498
|
+
doctor still fails.
|
|
497
499
|
|
|
498
500
|
A consumer repo needs a pipeline file and the agent files it names, both
|
|
499
501
|
committed before the first run (the host-boundary check refuses dirty tooling,
|
|
@@ -448,8 +448,10 @@ before.
|
|
|
448
448
|
`agentic-dev-loop doctor --fast` is the per-run gate the trigger runs first.
|
|
449
449
|
Hard failures abort the run (engine pin mismatch, engine runtime availability,
|
|
450
450
|
API version, git repo, `.env*` files); warnings print and continue. `doctor
|
|
451
|
-
--json` emits the same results mechanically.
|
|
452
|
-
|
|
451
|
+
--json` emits the same results mechanically. On a hard failure the trigger
|
|
452
|
+
runs `agentic-dev-loop upgrade` exactly once: it installs the latest release
|
|
453
|
+
with pipx and rewrites `.engine-version` to match, then the run fails hard if
|
|
454
|
+
doctor still fails.
|
|
453
455
|
|
|
454
456
|
A consumer repo needs a pipeline file and the agent files it names, both
|
|
455
457
|
committed before the first run (the host-boundary check refuses dirty tooling,
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentic-dev-loop"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.11.0"
|
|
8
8
|
description = "Turn an LLM into a gated, sandboxed, resumable construction loop over your codebase."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
{agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/core/profiles/__init__.py
RENAMED
|
@@ -81,18 +81,16 @@ def parse_version(text: str) -> tuple[int, int, int, int, int]:
|
|
|
81
81
|
return (int(major), int(minor), int(patch), _RANK[pre], int(num))
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
def check_engine_min(engine_min: str, installed: str
|
|
85
|
-
latest: str | None = None) -> None:
|
|
84
|
+
def check_engine_min(engine_min: str, installed: str) -> None:
|
|
86
85
|
"""Raise unless ``installed`` satisfies ``engine_min``.
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
The fix line names the upgrade command: it installs the latest release,
|
|
88
|
+
which always satisfies the floor.
|
|
90
89
|
"""
|
|
91
90
|
if parse_version(installed) < parse_version(engine_min):
|
|
92
|
-
target = latest or engine_min
|
|
93
91
|
raise ProfileError(
|
|
94
92
|
f"profile requires engine >= {engine_min}; installed {installed}. "
|
|
95
|
-
|
|
93
|
+
"Upgrade: run agentic-dev-loop upgrade")
|
|
96
94
|
|
|
97
95
|
|
|
98
96
|
def _data_dir():
|
|
@@ -171,6 +171,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
171
171
|
if argv and argv[0] == "doctor":
|
|
172
172
|
from .onboarding.doctor import run_doctor
|
|
173
173
|
return run_doctor(argv[1:])
|
|
174
|
+
if argv and argv[0] == "upgrade":
|
|
175
|
+
from .onboarding.upgrade import run_upgrade
|
|
176
|
+
return run_upgrade(argv[1:])
|
|
174
177
|
probe = argparse.ArgumentParser(add_help=False)
|
|
175
178
|
probe.add_argument("pipeline", type=Path, nargs="?")
|
|
176
179
|
_add_version_flag(probe)
|
|
@@ -275,13 +275,15 @@ def _run_pipeline(cfg, base, registry, state_dir, max_attempts, args, viz_bus,
|
|
|
275
275
|
if bus is None and not getattr(args, "no_viz", True):
|
|
276
276
|
start_state = initial if initial is not None \
|
|
277
277
|
else graph.get_state(config).values
|
|
278
|
-
topology
|
|
278
|
+
topology = viz.topology_graph(cfg)
|
|
279
279
|
bus = viz.VizBus()
|
|
280
280
|
if args.viz_port:
|
|
281
281
|
kill_listeners_on_port(args.viz_port, match="agentic-dev-loop",
|
|
282
282
|
cwd=runtime.workspace)
|
|
283
283
|
httpd, _thread = viz.serve_viz(
|
|
284
|
-
bus, topology, start_state["subject"],
|
|
284
|
+
bus, topology["mermaid"], start_state["subject"],
|
|
285
|
+
args.viz_port, topology["nodes"], topology["flow"],
|
|
286
|
+
topology["edges"])
|
|
285
287
|
bound = httpd.server_address[1]
|
|
286
288
|
url = f"http://127.0.0.1:{bound}"
|
|
287
289
|
if args.viz_port and bound != args.viz_port:
|
{agentic_dev_loop-0.10.4 → agentic_dev_loop-0.11.0}/src/agentic_dev_loop/engine/onboarding/checks.py
RENAMED
|
@@ -25,6 +25,7 @@ __all__ = ["Check", "HostProbes", "collect_checks"]
|
|
|
25
25
|
ENV_WHITELIST = frozenset({".env.example", ".env.template", ".env.sample"})
|
|
26
26
|
MIN_API = (1, 43)
|
|
27
27
|
PYPI_URL = "https://pypi.org/pypi/agentic-dev-loop/json"
|
|
28
|
+
FIX_UPGRADE = "run agentic-dev-loop upgrade"
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
@dataclass(frozen=True)
|
|
@@ -171,12 +172,12 @@ def collect_checks(profile: Profile | None, pipeline, installed: str,
|
|
|
171
172
|
if pin is None:
|
|
172
173
|
checks.append(_hard(
|
|
173
174
|
"pin", False, f"no engine pin file at {pin_path}",
|
|
174
|
-
|
|
175
|
+
FIX_UPGRADE))
|
|
175
176
|
else:
|
|
176
177
|
checks.append(_hard(
|
|
177
178
|
"pin", pin == installed,
|
|
178
179
|
f"installed {installed}, pin {pin}",
|
|
179
|
-
|
|
180
|
+
FIX_UPGRADE))
|
|
180
181
|
latest = probes.latest_release()
|
|
181
182
|
if latest is None:
|
|
182
183
|
checks.append(_hard("pin-latest", True,
|
|
@@ -189,7 +190,7 @@ def collect_checks(profile: Profile | None, pipeline, installed: str,
|
|
|
189
190
|
checks.append(_hard(
|
|
190
191
|
"pin-latest", not behind,
|
|
191
192
|
f"pin {pin} is behind latest release {latest}",
|
|
192
|
-
|
|
193
|
+
FIX_UPGRADE))
|
|
193
194
|
|
|
194
195
|
present = probes.docker_present()
|
|
195
196
|
checks.append(_hard(
|
|
@@ -5,7 +5,6 @@ ROOT="$(cd "$(dirname "$0")" && pwd)"
|
|
|
5
5
|
cd "$ROOT"
|
|
6
6
|
|
|
7
7
|
PIPELINE="$ROOT/agentic-dev-loop/pipeline.yaml"
|
|
8
|
-
PIN_FILE="$ROOT/agentic-dev-loop/.engine-version"
|
|
9
8
|
|
|
10
9
|
# Optional host environment file. The default lives outside the workspace so
|
|
11
10
|
# secrets never enter the repo; set AGENTIC_DEV_LOOP_ENV_FILE to override.
|
|
@@ -25,24 +24,17 @@ if ! command -v agentic-dev-loop >/dev/null 2>&1; then
|
|
|
25
24
|
exit 1
|
|
26
25
|
fi
|
|
27
26
|
|
|
28
|
-
if [[ ! -f "$PIN_FILE" ]]; then
|
|
29
|
-
echo "error: missing engine pin file: $PIN_FILE" >&2
|
|
30
|
-
echo " run 'agentic-dev-loop init' in this repo to write it" >&2
|
|
31
|
-
exit 1
|
|
32
|
-
fi
|
|
33
|
-
PIN="$(cat "$PIN_FILE")"
|
|
34
|
-
|
|
35
27
|
# doctor --fast is the per-run gate: hard failures abort, warnings continue.
|
|
36
|
-
# A
|
|
28
|
+
# A failing gate is repaired exactly once by upgrading to the latest engine
|
|
29
|
+
# release (which also rewrites the project pin), then the run fails hard.
|
|
37
30
|
if ! agentic-dev-loop doctor --fast; then
|
|
38
|
-
echo "
|
|
39
|
-
if !
|
|
40
|
-
echo "error:
|
|
41
|
-
echo " the install may not be pipx-managed, or the index is unreachable" >&2
|
|
31
|
+
echo "doctor --fast failed - upgrading to the latest engine release" >&2
|
|
32
|
+
if ! agentic-dev-loop upgrade; then
|
|
33
|
+
echo "error: agentic-dev-loop upgrade failed" >&2
|
|
42
34
|
exit 1
|
|
43
35
|
fi
|
|
44
36
|
if ! agentic-dev-loop doctor --fast; then
|
|
45
|
-
echo "error: doctor --fast still fails after
|
|
37
|
+
echo "error: doctor --fast still fails after upgrading the engine" >&2
|
|
46
38
|
exit 1
|
|
47
39
|
fi
|
|
48
40
|
fi
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""``agentic-dev-loop upgrade``: install the latest engine and sync the pin.
|
|
2
|
+
|
|
3
|
+
Installs the latest release with pipx and rewrites
|
|
4
|
+
``agentic-dev-loop/.engine-version`` to the installed version, so the host
|
|
5
|
+
install and the committed pin converge in one action.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import shutil
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from agentic_dev_loop.core.profiles import ProfileError, parse_version
|
|
17
|
+
|
|
18
|
+
__all__ = ["run_upgrade"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _resolve(name: str, env: dict | None) -> str | None:
|
|
22
|
+
if env is not None:
|
|
23
|
+
return shutil.which(name, path=env.get("PATH", ""))
|
|
24
|
+
return shutil.which(name)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def run_upgrade(argv: list[str], *, workspace: Path | None = None,
|
|
28
|
+
env: dict | None = None) -> int:
|
|
29
|
+
parser = argparse.ArgumentParser(
|
|
30
|
+
prog="agentic-dev-loop upgrade",
|
|
31
|
+
description="Install the latest engine release and rewrite the "
|
|
32
|
+
"project pin file to match.")
|
|
33
|
+
parser.parse_args(argv)
|
|
34
|
+
|
|
35
|
+
root = Path(workspace or Path.cwd()).resolve()
|
|
36
|
+
pipx = _resolve("pipx", env)
|
|
37
|
+
if pipx is None:
|
|
38
|
+
print("error: pipx is not on PATH; the engine install must be "
|
|
39
|
+
"pipx-managed", file=sys.stderr)
|
|
40
|
+
return 1
|
|
41
|
+
|
|
42
|
+
result = subprocess.run(
|
|
43
|
+
[pipx, "install", "--force", "agentic-dev-loop"],
|
|
44
|
+
capture_output=True, text=True, check=False, env=env)
|
|
45
|
+
if result.returncode != 0:
|
|
46
|
+
detail = (result.stderr or "unknown error").strip()
|
|
47
|
+
print(f"error: pipx install failed: {detail}", file=sys.stderr)
|
|
48
|
+
return 1
|
|
49
|
+
|
|
50
|
+
cli = _resolve("agentic-dev-loop", env)
|
|
51
|
+
if cli is None:
|
|
52
|
+
print("error: agentic-dev-loop is not on PATH", file=sys.stderr)
|
|
53
|
+
return 1
|
|
54
|
+
version_result = subprocess.run(
|
|
55
|
+
[cli, "--version"], capture_output=True, text=True,
|
|
56
|
+
check=False, env=env)
|
|
57
|
+
if version_result.returncode != 0:
|
|
58
|
+
detail = (version_result.stderr or "unknown error").strip()
|
|
59
|
+
print(f"error: could not read the installed version: {detail}",
|
|
60
|
+
file=sys.stderr)
|
|
61
|
+
return 1
|
|
62
|
+
|
|
63
|
+
tokens = version_result.stdout.split()
|
|
64
|
+
version = tokens[-1] if tokens else ""
|
|
65
|
+
try:
|
|
66
|
+
parse_version(version)
|
|
67
|
+
except ProfileError:
|
|
68
|
+
print("error: unreadable version from --version output: "
|
|
69
|
+
f"{version_result.stdout.strip()!r}", file=sys.stderr)
|
|
70
|
+
return 1
|
|
71
|
+
|
|
72
|
+
out = root / "agentic-dev-loop"
|
|
73
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
(out / ".engine-version").write_text(version + "\n", encoding="utf-8")
|
|
75
|
+
print(f"upgraded agentic-dev-loop to {version}; .engine-version updated")
|
|
76
|
+
return 0
|
|
@@ -4,10 +4,10 @@ import time # noqa: F401 - re-exported so tests can pin NodeWatch's clock
|
|
|
4
4
|
|
|
5
5
|
from .server import (MAX_LINE, PAGE, ConsoleCapture, HeartbeatThread,
|
|
6
6
|
NodeWatch, VizBus, _ReuseSafeServer, _Tee,
|
|
7
|
-
_handler_class, serve_viz, topology_mermaid, translate)
|
|
7
|
+
_handler_class, serve_viz, topology_graph, topology_mermaid, translate)
|
|
8
8
|
|
|
9
9
|
__all__ = [
|
|
10
10
|
"MAX_LINE", "PAGE", "ConsoleCapture", "HeartbeatThread", "NodeWatch",
|
|
11
11
|
"VizBus", "_ReuseSafeServer", "_Tee", "_handler_class", "serve_viz",
|
|
12
|
-
"topology_mermaid", "translate",
|
|
12
|
+
"topology_graph", "topology_mermaid", "translate",
|
|
13
13
|
]
|
|
@@ -8,7 +8,7 @@ import sys
|
|
|
8
8
|
|
|
9
9
|
from agentic_dev_loop.core.config import load_config
|
|
10
10
|
|
|
11
|
-
from . import VizBus, serve_viz,
|
|
11
|
+
from . import VizBus, serve_viz, topology_graph
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def main(argv: list[str] | None = None) -> int:
|
|
@@ -23,9 +23,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
23
23
|
args = parser.parse_args(argv)
|
|
24
24
|
|
|
25
25
|
cfg = load_config(args.pipeline)
|
|
26
|
-
topology
|
|
26
|
+
topology = topology_graph(cfg)
|
|
27
27
|
bus = VizBus()
|
|
28
|
-
httpd, _thread = serve_viz(bus, topology, cfg.name, args.port,
|
|
28
|
+
httpd, _thread = serve_viz(bus, topology["mermaid"], cfg.name, args.port,
|
|
29
|
+
topology["nodes"], topology["flow"],
|
|
30
|
+
topology["edges"])
|
|
29
31
|
bound = httpd.server_address[1]
|
|
30
32
|
url = f"http://127.0.0.1:{bound}"
|
|
31
33
|
print(f"Live graph viz: {url}", flush=True)
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""Pure-Python port of the viz pipeline layout algorithm.
|
|
2
|
+
|
|
3
|
+
Mirrors ``planning-docs/design/pipeline-layout.js`` one-to-one (contract in
|
|
4
|
+
its header and in the unit-9 business-logic model): no DOM, no I/O, same
|
|
5
|
+
input/output shapes and the same crossing-free ordering rules. The reference
|
|
6
|
+
ES5 implementation remains the browser-side copy; this port is the tested
|
|
7
|
+
source of truth (decision Q3=A).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
NW = 112
|
|
15
|
+
NH = 76
|
|
16
|
+
GAP = 24
|
|
17
|
+
STEP = NW + GAP
|
|
18
|
+
LANE = 18
|
|
19
|
+
LANE_TOP = 14
|
|
20
|
+
LANE_PAD = 16
|
|
21
|
+
CH = 12
|
|
22
|
+
|
|
23
|
+
METRICS = {"NW": NW, "NH": NH, "GAP": GAP, "STEP": STEP, "LANE": LANE}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _Edge:
|
|
27
|
+
"""Mutable routing record, mirroring the JS object the reference mutates."""
|
|
28
|
+
|
|
29
|
+
__slots__ = ("src", "dst", "label", "kind", "si", "ti", "rs", "rt",
|
|
30
|
+
"lane_s", "lane_t", "j", "k", "xe")
|
|
31
|
+
|
|
32
|
+
def __init__(self, src: str, dst: str, label: str | None, kind: str):
|
|
33
|
+
self.src = src
|
|
34
|
+
self.dst = dst
|
|
35
|
+
self.label = label
|
|
36
|
+
self.kind = kind
|
|
37
|
+
self.si = 0
|
|
38
|
+
self.ti = 0
|
|
39
|
+
self.rs = 0
|
|
40
|
+
self.rt = 0
|
|
41
|
+
self.lane_s = 0
|
|
42
|
+
self.lane_t = 0
|
|
43
|
+
self.j = 0
|
|
44
|
+
self.k = 0
|
|
45
|
+
self.xe = 0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _plan(names: list[str], routed: list[_Edge], idx: dict[str, int], r: int) -> dict[str, Any]:
|
|
49
|
+
n = len(names)
|
|
50
|
+
per = max(1, (n + r - 1) // r)
|
|
51
|
+
rows = max(1, (n + per - 1) // per)
|
|
52
|
+
pos = [{"r": i // per, "c": i % per} for i in range(n)]
|
|
53
|
+
edges = []
|
|
54
|
+
for e in routed:
|
|
55
|
+
rec = _Edge(e.src, e.dst, e.label, e.kind)
|
|
56
|
+
rec.si = idx[e.src]
|
|
57
|
+
rec.ti = idx[e.dst]
|
|
58
|
+
rec.rs = pos[rec.si]["r"]
|
|
59
|
+
rec.rt = pos[rec.ti]["r"]
|
|
60
|
+
edges.append(rec)
|
|
61
|
+
same = sorted((e for e in edges if e.rs == e.rt),
|
|
62
|
+
key=lambda e: abs(e.si - e.ti))
|
|
63
|
+
back = sorted((e for e in edges if e.rs > e.rt),
|
|
64
|
+
key=lambda e: (e.rs, e.si))
|
|
65
|
+
forward = sorted((e for e in edges if e.rs < e.rt),
|
|
66
|
+
key=lambda e: (e.rt, -e.si))
|
|
67
|
+
for j, e in enumerate(back):
|
|
68
|
+
e.j = j
|
|
69
|
+
for k, e in enumerate(forward):
|
|
70
|
+
e.k = k
|
|
71
|
+
multi = rows > 1
|
|
72
|
+
left = CH + CH * len(back) + 8 if multi else 0
|
|
73
|
+
core = per * STEP - GAP
|
|
74
|
+
right = (14 if multi else 0) + (8 + CH * len(forward) if forward else 0)
|
|
75
|
+
return {"per": per, "rows": rows, "pos": pos, "U": same, "C": back,
|
|
76
|
+
"F": forward, "O": left, "core": core, "width": left + core + right}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def layout_pipeline(opts: dict[str, Any]) -> dict[str, Any]:
|
|
80
|
+
"""Lay out stations and routes for ``opts`` (contract: FD section 1.1)."""
|
|
81
|
+
names = list(opts.get("nodes") or [])
|
|
82
|
+
n = len(names)
|
|
83
|
+
status = opts.get("status") or {}
|
|
84
|
+
avail = max(0, opts.get("availWidth") or 0)
|
|
85
|
+
flow = opts.get("flow") or []
|
|
86
|
+
cond_edges = opts.get("edges") or []
|
|
87
|
+
idx = {name: i for i, name in enumerate(names)}
|
|
88
|
+
|
|
89
|
+
def reached(name: str) -> bool:
|
|
90
|
+
return (status.get(name) or "pending") != "pending"
|
|
91
|
+
|
|
92
|
+
def failed(name: str) -> bool:
|
|
93
|
+
state = status.get(name) or "pending"
|
|
94
|
+
return state == "fail" or state == "fatal"
|
|
95
|
+
|
|
96
|
+
rails: list[dict[str, Any]] = []
|
|
97
|
+
extra_flow: list[_Edge] = []
|
|
98
|
+
for f in flow:
|
|
99
|
+
if f["src"] not in idx or f["dst"] not in idx:
|
|
100
|
+
continue
|
|
101
|
+
if idx[f["dst"]] == idx[f["src"]] + 1:
|
|
102
|
+
rails.append(f)
|
|
103
|
+
else:
|
|
104
|
+
extra_flow.append(_Edge(f["src"], f["dst"], None, "flow"))
|
|
105
|
+
routed = [
|
|
106
|
+
_Edge(e["src"], e["dst"], e.get("label") or None, "back")
|
|
107
|
+
for e in cond_edges
|
|
108
|
+
if e["src"] in idx and e["dst"] in idx
|
|
109
|
+
] + extra_flow
|
|
110
|
+
|
|
111
|
+
layout = _plan(names, routed, idx, 1)
|
|
112
|
+
r = 2
|
|
113
|
+
while layout["width"] > avail and layout["per"] > 2:
|
|
114
|
+
layout = _plan(names, routed, idx, r)
|
|
115
|
+
r += 1
|
|
116
|
+
offset = layout["O"]
|
|
117
|
+
|
|
118
|
+
gutters: list[dict[str, int]] = []
|
|
119
|
+
for g in range(layout["rows"]):
|
|
120
|
+
items = 0
|
|
121
|
+
|
|
122
|
+
def take() -> int:
|
|
123
|
+
nonlocal items
|
|
124
|
+
value = items
|
|
125
|
+
items += 1
|
|
126
|
+
return value
|
|
127
|
+
|
|
128
|
+
for e in layout["U"]:
|
|
129
|
+
if e.rt == g:
|
|
130
|
+
e.lane_s = take()
|
|
131
|
+
for e in sorted((e for e in layout["C"] if e.rt == g),
|
|
132
|
+
key=lambda e: -e.j):
|
|
133
|
+
e.lane_t = take()
|
|
134
|
+
for e in layout["F"]:
|
|
135
|
+
if e.rt == g:
|
|
136
|
+
e.lane_t = take()
|
|
137
|
+
for e in sorted((e for e in layout["C"] if e.rs == g),
|
|
138
|
+
key=lambda e: e.si):
|
|
139
|
+
e.lane_s = take()
|
|
140
|
+
for e in layout["F"]:
|
|
141
|
+
if e.rs == g:
|
|
142
|
+
e.lane_s = take()
|
|
143
|
+
wrap = take() if g < layout["rows"] - 1 else -1
|
|
144
|
+
gutters.append({"n": items, "wrapLane": wrap})
|
|
145
|
+
|
|
146
|
+
row_y = [0]
|
|
147
|
+
for g, gutter in enumerate(gutters):
|
|
148
|
+
gutter["h"] = (LANE_TOP + (gutter["n"] - 1) * LANE + LANE_PAD
|
|
149
|
+
if gutter["n"] else 4)
|
|
150
|
+
row_y.append(row_y[g] + NH + gutter["h"])
|
|
151
|
+
|
|
152
|
+
def lane_y(g: int, lane: int) -> int:
|
|
153
|
+
return row_y[g] + NH + LANE_TOP + lane * LANE
|
|
154
|
+
|
|
155
|
+
def node_x(i: int) -> int:
|
|
156
|
+
return offset + layout["pos"][i]["c"] * STEP
|
|
157
|
+
|
|
158
|
+
def node_y(i: int) -> int:
|
|
159
|
+
return row_y[layout["pos"][i]["r"]]
|
|
160
|
+
|
|
161
|
+
entry_count: dict[int, int] = {}
|
|
162
|
+
for e in layout["U"] + layout["C"] + layout["F"]:
|
|
163
|
+
m = entry_count.get(e.ti, 0)
|
|
164
|
+
entry_count[e.ti] = m + 1
|
|
165
|
+
e.xe = max(node_x(e.ti) + 8, node_x(e.ti) + NW - 14 - m * 14)
|
|
166
|
+
|
|
167
|
+
lines: list[dict[str, Any]] = []
|
|
168
|
+
arrows: list[dict[str, Any]] = []
|
|
169
|
+
labels: list[dict[str, Any]] = []
|
|
170
|
+
|
|
171
|
+
def horizontal(x1: int, x2: int, y: int, kind: str, on: bool = False) -> None:
|
|
172
|
+
lines.append({"x": min(x1, x2), "y": y, "w": abs(x2 - x1), "h": 0,
|
|
173
|
+
"dir": "h", "kind": kind, "on": bool(on)})
|
|
174
|
+
|
|
175
|
+
def vertical(x: int, y1: int, y2: int, kind: str, on: bool = False) -> None:
|
|
176
|
+
lines.append({"x": x, "y": min(y1, y2), "w": 0, "h": abs(y2 - y1),
|
|
177
|
+
"dir": "v", "kind": kind, "on": bool(on)})
|
|
178
|
+
|
|
179
|
+
for f in rails:
|
|
180
|
+
i = idx[f["src"]]
|
|
181
|
+
on = reached(f["dst"])
|
|
182
|
+
if layout["pos"][i + 1]["r"] == layout["pos"][i]["r"]:
|
|
183
|
+
horizontal(node_x(i) + NW, node_x(i + 1), node_y(i) + 37, "flow", on)
|
|
184
|
+
arrows.append({"x": node_x(i + 1) - 6, "y": node_y(i) + 33,
|
|
185
|
+
"dir": "right", "kind": "flow", "on": on})
|
|
186
|
+
else:
|
|
187
|
+
r = layout["pos"][i]["r"]
|
|
188
|
+
xr = node_x(i) + NW + 12
|
|
189
|
+
yw = lane_y(r, gutters[r]["wrapLane"])
|
|
190
|
+
yn = node_y(i + 1) + 37
|
|
191
|
+
horizontal(node_x(i) + NW, xr + 2, node_y(i) + 37, "flow", on)
|
|
192
|
+
vertical(xr, node_y(i) + 37, yw, "flow", on)
|
|
193
|
+
horizontal(offset - 12, xr + 2, yw - 1, "flow", on)
|
|
194
|
+
vertical(offset - 12, yw - 1, yn + 2, "flow", on)
|
|
195
|
+
horizontal(offset - 12, offset, yn, "flow", on)
|
|
196
|
+
arrows.append({"x": offset - 6, "y": yn - 4, "dir": "right",
|
|
197
|
+
"kind": "flow", "on": on})
|
|
198
|
+
|
|
199
|
+
for e in layout["U"] + layout["C"] + layout["F"]:
|
|
200
|
+
xs = node_x(e.si) + NW // 2
|
|
201
|
+
top = node_y(e.ti) + NH
|
|
202
|
+
y1 = lane_y(e.rs, e.lane_s)
|
|
203
|
+
on = e.kind == "back" and failed(e.src)
|
|
204
|
+
vertical(xs, node_y(e.si) + NH, y1, e.kind, on)
|
|
205
|
+
if e.rs == e.rt:
|
|
206
|
+
horizontal(e.xe, xs, y1, e.kind, on)
|
|
207
|
+
vertical(e.xe, top + 6, y1, e.kind, on)
|
|
208
|
+
else:
|
|
209
|
+
if e.rs > e.rt:
|
|
210
|
+
xc = offset - CH - CH * (e.j + 1)
|
|
211
|
+
else:
|
|
212
|
+
xc = offset + layout["core"] + 14 + 8 + CH * e.k
|
|
213
|
+
y2 = lane_y(e.rt, e.lane_t)
|
|
214
|
+
horizontal(xc, xs, y1, e.kind, on)
|
|
215
|
+
vertical(xc, y2, y1, e.kind, on)
|
|
216
|
+
horizontal(xc, e.xe, y2, e.kind, on)
|
|
217
|
+
vertical(e.xe, top + 6, y2, e.kind, on)
|
|
218
|
+
arrows.append({"x": e.xe - 5, "y": top, "dir": "up", "kind": e.kind,
|
|
219
|
+
"on": on})
|
|
220
|
+
if e.label:
|
|
221
|
+
labels.append({"x": xs - 6, "y": y1 - 9, "text": e.label})
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
"width": layout["width"],
|
|
225
|
+
"height": row_y[layout["rows"]],
|
|
226
|
+
"rows": layout["rows"],
|
|
227
|
+
"perRow": layout["per"],
|
|
228
|
+
"nodes": [{"id": name, "index": i, "x": node_x(i), "y": node_y(i),
|
|
229
|
+
"w": NW, "h": NH} for i, name in enumerate(names)],
|
|
230
|
+
"lines": lines,
|
|
231
|
+
"arrows": arrows,
|
|
232
|
+
"labels": labels,
|
|
233
|
+
}
|