arctx-cli 0.3.1b2__tar.gz → 0.3.1b4__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.
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/PKG-INFO +10 -7
- arctx_cli-0.3.1b4/README.md +26 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/pyproject.toml +2 -2
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/append_batch.py +37 -0
- arctx_cli-0.3.1b4/src/arctx_cli/commands/_lane_context.py +91 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/add.py +59 -5
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/asset.py +4 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/attach.py +11 -2
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/cut.py +3 -1
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/dump.py +10 -1
- arctx_cli-0.3.1b4/src/arctx_cli/commands/guide.py +208 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/lane.py +189 -76
- arctx_cli-0.3.1b4/src/arctx_cli/commands/log.py +331 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/reparent.py +5 -2
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/uncut.py +3 -1
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/ext/git/__init__.py +4 -0
- arctx_cli-0.3.1b4/src/arctx_cli/lane_gate.py +25 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/main.py +37 -4
- arctx_cli-0.3.1b4/src/arctx_cli/post_write_check.py +72 -0
- arctx_cli-0.3.1b4/tests/cli/test_add_frontier.py +278 -0
- arctx_cli-0.3.1b4/tests/cli/test_guide_cli.py +149 -0
- arctx_cli-0.3.1b4/tests/cli/test_lane_lifecycle.py +279 -0
- arctx_cli-0.3.1b4/tests/cli/test_log_chronological.py +233 -0
- arctx_cli-0.3.1b4/tests/cli/test_main_guide_hint.py +85 -0
- arctx_cli-0.3.1b4/tests/cli/test_post_write_check.py +163 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_serve_api.py +63 -0
- arctx_cli-0.3.1b2/README.md +0 -23
- arctx_cli-0.3.1b2/src/arctx_cli/commands/guide.py +0 -135
- arctx_cli-0.3.1b2/src/arctx_cli/commands/log.py +0 -76
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/.gitignore +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/__init__.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/alias.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/__init__.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/_targets.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/alias_cmd.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/current.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/export.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/ext.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/graph.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/init.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/list.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/migrate.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/node.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/outcomes.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/payload.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/pr.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/reachable.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/serve.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/show.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/step.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/sync_cmd.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/trace.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/use.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/commands/web.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/context.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/ext/__init__.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/ext/command/__init__.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/ext/git/branch.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/ext/git/cherry_pick.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/ext/git/commit.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/ext/git/hook.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/ext/git/merge.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/ext/git/repo.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/ext/git/reset.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/ext/git/revert.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/ext/git/verify.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/ext/git/worktree.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/ext_registry.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/paths.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/payload_builder.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/src/arctx_cli/workspace.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/__init__.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/__init__.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_alias_cli.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_alias_resolve.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_asset_extension_cli.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_basic.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_branch.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_cherry_pick.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_command_extension.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_commit.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_commit_guard.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_dag_core_cli.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_ext_cli.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_git_namespace_cli.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_git_repo.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_hook_install.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_hook_post_merge.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_hook_post_rewrite.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_init_extension.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_init_hooks.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_init_stag_id.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_lane.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_merge.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_migrate.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_paths.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_pr.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_reset.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_revert.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_show_history.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_sync.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/cli/test_verify.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/fixtures/__init__.py +0 -0
- {arctx_cli-0.3.1b2 → arctx_cli-0.3.1b4}/tests/fixtures/dummy_ext.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arctx-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1b4
|
|
4
4
|
Summary: CLI for ARCTX — append-only DAG editing and refactoring
|
|
5
5
|
Project-URL: Homepage, https://github.com/takumiecd/arctx
|
|
6
6
|
Project-URL: Repository, https://github.com/takumiecd/arctx
|
|
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
18
|
Classifier: Topic :: Utilities
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
|
-
Requires-Dist: arctx
|
|
20
|
+
Requires-Dist: arctx==0.3.1b4
|
|
21
21
|
Provides-Extra: dev
|
|
22
22
|
Requires-Dist: black>=23.0; extra == 'dev'
|
|
23
23
|
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
@@ -26,9 +26,11 @@ Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
|
26
26
|
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
|
|
29
|
-
#
|
|
29
|
+
# ARCTX CLI
|
|
30
30
|
|
|
31
|
-
Command-line interface
|
|
31
|
+
Command-line interface for ARCTX experiment graphs. Use it to initialize runs,
|
|
32
|
+
record steps, attach results, cut abandoned branches, export reports, and serve
|
|
33
|
+
the Web GUI.
|
|
32
34
|
|
|
33
35
|
## Installation
|
|
34
36
|
|
|
@@ -41,11 +43,12 @@ This also installs `arctx` as a dependency.
|
|
|
41
43
|
## Usage
|
|
42
44
|
|
|
43
45
|
```bash
|
|
44
|
-
arctx init
|
|
45
|
-
arctx
|
|
46
|
+
arctx init optimize --extension git --run-id demo
|
|
47
|
+
arctx add --from <node_id> --type experiment --field variant=A
|
|
46
48
|
arctx dump
|
|
47
49
|
```
|
|
48
50
|
|
|
49
51
|
## Package layout
|
|
50
52
|
|
|
51
|
-
This package provides the `arctx` CLI command
|
|
53
|
+
This package provides the `arctx` CLI command. The core API is in the separate
|
|
54
|
+
`arctx` package; the experimental TUI is shipped separately as `arctx-tui`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# ARCTX CLI
|
|
2
|
+
|
|
3
|
+
Command-line interface for ARCTX experiment graphs. Use it to initialize runs,
|
|
4
|
+
record steps, attach results, cut abandoned branches, export reports, and serve
|
|
5
|
+
the Web GUI.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install arctx-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This also installs `arctx` as a dependency.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
arctx init optimize --extension git --run-id demo
|
|
19
|
+
arctx add --from <node_id> --type experiment --field variant=A
|
|
20
|
+
arctx dump
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Package layout
|
|
24
|
+
|
|
25
|
+
This package provides the `arctx` CLI command. The core API is in the separate
|
|
26
|
+
`arctx` package; the experimental TUI is shipped separately as `arctx-tui`.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "arctx-cli"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.1b4"
|
|
8
8
|
description = "CLI for ARCTX — append-only DAG editing and refactoring"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -25,7 +25,7 @@ classifiers = [
|
|
|
25
25
|
"Topic :: Utilities",
|
|
26
26
|
]
|
|
27
27
|
dependencies = [
|
|
28
|
-
"arctx
|
|
28
|
+
"arctx==0.3.1b4",
|
|
29
29
|
]
|
|
30
30
|
|
|
31
31
|
[project.optional-dependencies]
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from arctx.core.append import AppendBatch, GraphRecordEnvelope
|
|
6
|
+
from arctx.core.lanes import format_lane_validation_errors, lane_validation_errors
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
def graph_counts(handle) -> dict[str, set[str]]:
|
|
@@ -12,6 +13,7 @@ def graph_counts(handle) -> dict[str, set[str]]:
|
|
|
12
13
|
"steps": set(handle.run_graph.steps),
|
|
13
14
|
"payloads": set(handle.run_graph.payloads),
|
|
14
15
|
"work_events": {event.event_id for event in handle.run_graph.work_events},
|
|
16
|
+
"lane_error_count": {str(_lane_error_count(handle))},
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
|
|
@@ -24,6 +26,8 @@ def maybe_append_or_save(
|
|
|
24
26
|
before: dict[str, set[str]],
|
|
25
27
|
) -> None:
|
|
26
28
|
"""Use append_batch for capable stores, otherwise fall back to save_run."""
|
|
29
|
+
if _has_new_work_events(handle, before):
|
|
30
|
+
_ensure_no_new_lane_errors(handle, baseline=_baseline_lane_error_count(before))
|
|
27
31
|
if user_id is None or lane_id is None or not hasattr(store, "append_batch"):
|
|
28
32
|
store.save_run(handle)
|
|
29
33
|
return
|
|
@@ -80,3 +84,36 @@ def build_append_batch(
|
|
|
80
84
|
|
|
81
85
|
def _new_ids(current: dict[str, object], before: dict[str, set[str]], key: str) -> list[str]:
|
|
82
86
|
return [record_id for record_id in current if record_id not in before.get(key, set())]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _has_new_work_events(handle, before: dict[str, set[str]]) -> bool:
|
|
90
|
+
return any(
|
|
91
|
+
event.event_id not in before.get("work_events", set())
|
|
92
|
+
for event in handle.run_graph.work_events
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _lane_error_count(handle) -> int:
|
|
97
|
+
return len(
|
|
98
|
+
lane_validation_errors(
|
|
99
|
+
handle.run_graph,
|
|
100
|
+
root_node_id=handle.root_node_id,
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _baseline_lane_error_count(before: dict[str, set[str]]) -> int:
|
|
106
|
+
values = before.get("lane_error_count", {"0"})
|
|
107
|
+
try:
|
|
108
|
+
return int(next(iter(values)))
|
|
109
|
+
except (StopIteration, ValueError):
|
|
110
|
+
return 0
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _ensure_no_new_lane_errors(handle, *, baseline: int) -> None:
|
|
114
|
+
current = lane_validation_errors(
|
|
115
|
+
handle.run_graph,
|
|
116
|
+
root_node_id=handle.root_node_id,
|
|
117
|
+
)
|
|
118
|
+
if len(current) > baseline:
|
|
119
|
+
raise ValueError(format_lane_validation_errors(current[baseline:]))
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Shared helpers for resolving the current lane and its active frontiers.
|
|
2
|
+
|
|
3
|
+
Both ``arctx add`` (defaulting ``--from`` to the current lane's frontier) and
|
|
4
|
+
``arctx guide`` (reporting "Active Frontiers in Lane") need the same three
|
|
5
|
+
steps: resolve the active lane id (env / --lane / repo pointer), look up the
|
|
6
|
+
``Lane`` object by id or name, and compute its active frontier nodes. Keeping
|
|
7
|
+
this in one place means both commands agree on lane resolution order and on
|
|
8
|
+
what counts as a frontier.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def find_lane(handle, name_or_id: str):
|
|
17
|
+
"""Resolve a Lane object by lane_id or by name. Returns None if absent."""
|
|
18
|
+
lane = handle.run_graph.lanes.get(name_or_id)
|
|
19
|
+
if lane is not None:
|
|
20
|
+
return lane
|
|
21
|
+
for candidate in handle.run_graph.lanes.values():
|
|
22
|
+
if candidate.name == name_or_id:
|
|
23
|
+
return candidate
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class LaneFrontierContext:
|
|
29
|
+
"""Resolution result: the lane id/name that was requested, the Lane object
|
|
30
|
+
found for it (if any), and its active frontier node ids."""
|
|
31
|
+
|
|
32
|
+
lane_id: str
|
|
33
|
+
lane_name: str | None
|
|
34
|
+
frontier_node_ids: tuple[str, ...]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def resolve_lane_frontiers(handle, lane_id: str) -> LaneFrontierContext:
|
|
38
|
+
"""Resolve ``lane_id`` (id or name) to its active frontier nodes.
|
|
39
|
+
|
|
40
|
+
``lane_id`` should already be resolved via the canonical chain (explicit
|
|
41
|
+
flag / ``ARCTX_LANE_ID`` / repo pointer) — see
|
|
42
|
+
``arctx_cli.context.resolve_lane_id_from_args``. This helper only does the
|
|
43
|
+
graph-level lookup: Lane object + frontier computation.
|
|
44
|
+
"""
|
|
45
|
+
from arctx.core.lanes import lane_active_frontiers
|
|
46
|
+
|
|
47
|
+
lane = find_lane(handle, lane_id)
|
|
48
|
+
if lane is None:
|
|
49
|
+
return LaneFrontierContext(lane_id=lane_id, lane_name=None, frontier_node_ids=())
|
|
50
|
+
frontiers = lane_active_frontiers(handle.run_graph, lane.lane_id)
|
|
51
|
+
return LaneFrontierContext(
|
|
52
|
+
lane_id=lane.lane_id, lane_name=lane.name, frontier_node_ids=frontiers
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def root_frontier_fallback(handle) -> str | None:
|
|
57
|
+
"""Return the run root node id if it is usable as a default ``--from``.
|
|
58
|
+
|
|
59
|
+
The run root is deliberately excluded from lane membership (it is run
|
|
60
|
+
metadata, not a lane-owned record — see ``lane_membership``), so
|
|
61
|
+
``lane_active_frontiers`` never includes it and a brand-new run always
|
|
62
|
+
reports zero frontiers for every lane. That makes the very first
|
|
63
|
+
``arctx add`` on a fresh run fail without ``--from`` even though the root
|
|
64
|
+
is the only possible input.
|
|
65
|
+
|
|
66
|
+
Returns the root node id when it is both active (not cut) and itself a
|
|
67
|
+
frontier (no outgoing steps yet, from *any* lane) — i.e. nothing has been
|
|
68
|
+
recorded against it yet. Returns ``None`` otherwise, so callers fall back
|
|
69
|
+
to the existing "no active frontiers" error.
|
|
70
|
+
"""
|
|
71
|
+
from arctx.core.cuts import is_active_node
|
|
72
|
+
|
|
73
|
+
root_node_id = handle.root_node_id
|
|
74
|
+
if not root_node_id or root_node_id not in handle.run_graph.nodes:
|
|
75
|
+
return None
|
|
76
|
+
if not is_active_node(handle.run_graph, root_node_id):
|
|
77
|
+
return None
|
|
78
|
+
if handle.run_graph.steps_from_node(root_node_id):
|
|
79
|
+
return None
|
|
80
|
+
return root_node_id
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def describe_frontier_candidates(handle, node_ids) -> list[str]:
|
|
84
|
+
"""Format frontier node ids as ``n_xxx (type_a, type_b)`` lines, like guide."""
|
|
85
|
+
lines = []
|
|
86
|
+
for node_id in node_ids:
|
|
87
|
+
payloads = handle.run_graph.payloads_for_node(node_id)
|
|
88
|
+
types = [p.payload_type for p in payloads]
|
|
89
|
+
type_str = f" ({', '.join(types)})" if types else ""
|
|
90
|
+
lines.append(f"{node_id}{type_str}")
|
|
91
|
+
return lines
|
|
@@ -7,6 +7,11 @@ import json
|
|
|
7
7
|
import sys
|
|
8
8
|
|
|
9
9
|
from arctx_cli.append_batch import graph_counts, maybe_append_or_save
|
|
10
|
+
from arctx_cli.commands._lane_context import (
|
|
11
|
+
describe_frontier_candidates,
|
|
12
|
+
resolve_lane_frontiers,
|
|
13
|
+
root_frontier_fallback,
|
|
14
|
+
)
|
|
10
15
|
from arctx_cli.commands._targets import step_view
|
|
11
16
|
from arctx_cli.context import (
|
|
12
17
|
resolve_run_id_from_args,
|
|
@@ -14,7 +19,9 @@ from arctx_cli.context import (
|
|
|
14
19
|
resolve_user_id_from_args,
|
|
15
20
|
resolve_lane_id_from_args,
|
|
16
21
|
)
|
|
22
|
+
from arctx_cli.lane_gate import ensure_lane_open
|
|
17
23
|
from arctx_cli.payload_builder import build_payload, parse_field_args, parse_json_object
|
|
24
|
+
from arctx_cli.post_write_check import warn_if_invalid
|
|
18
25
|
|
|
19
26
|
|
|
20
27
|
def add_parser(subparsers) -> argparse.ArgumentParser:
|
|
@@ -22,10 +29,14 @@ def add_parser(subparsers) -> argparse.ArgumentParser:
|
|
|
22
29
|
parser.add_argument(
|
|
23
30
|
"--from",
|
|
24
31
|
action="append",
|
|
25
|
-
required=
|
|
32
|
+
required=False,
|
|
33
|
+
default=None,
|
|
26
34
|
dest="input_nodes",
|
|
27
35
|
metavar="NODE_ID",
|
|
28
|
-
help=
|
|
36
|
+
help=(
|
|
37
|
+
"Input node (repeatable for multi-input steps). If omitted, "
|
|
38
|
+
"defaults to the current lane's single active frontier node."
|
|
39
|
+
),
|
|
29
40
|
)
|
|
30
41
|
parser.add_argument("--title", default=None)
|
|
31
42
|
parser.add_argument("--type", dest="payload_kind", default=None)
|
|
@@ -36,14 +47,50 @@ def add_parser(subparsers) -> argparse.ArgumentParser:
|
|
|
36
47
|
parser.add_argument("--store-dir", default=None)
|
|
37
48
|
parser.add_argument("--user", default=None)
|
|
38
49
|
parser.add_argument("--lane", default=None)
|
|
50
|
+
parser.add_argument("--force", action="store_true",
|
|
51
|
+
help="Write even if the target lane is closed")
|
|
39
52
|
|
|
40
53
|
return parser
|
|
41
54
|
|
|
42
55
|
|
|
56
|
+
def _default_input_node_ids(handle, lane_id: str) -> list[str]:
|
|
57
|
+
"""Default ``--from`` to the current lane's single active frontier node.
|
|
58
|
+
|
|
59
|
+
Raises ``ValueError`` (with a corrective, ready-to-copy command) when the
|
|
60
|
+
lane has zero or more than one active frontier, since there is no single
|
|
61
|
+
unambiguous node to continue from.
|
|
62
|
+
"""
|
|
63
|
+
context = resolve_lane_frontiers(handle, lane_id)
|
|
64
|
+
frontiers = context.frontier_node_ids
|
|
65
|
+
lane_label = context.lane_name or context.lane_id
|
|
66
|
+
|
|
67
|
+
if len(frontiers) == 1:
|
|
68
|
+
return [frontiers[0]]
|
|
69
|
+
|
|
70
|
+
if not frontiers:
|
|
71
|
+
root_node_id = root_frontier_fallback(handle)
|
|
72
|
+
if root_node_id is not None:
|
|
73
|
+
return [root_node_id]
|
|
74
|
+
raise ValueError(
|
|
75
|
+
f"--from is required here: lane {lane_label!r} has no active frontiers. "
|
|
76
|
+
f"Pass --from NODE_ID explicitly. Use `arctx guide --context` or "
|
|
77
|
+
f"`arctx dump` to find a node, e.g.: arctx add --from n_xxx ..."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
candidates = describe_frontier_candidates(handle, frontiers)
|
|
81
|
+
listing = "\n".join(f" - {line}" for line in candidates)
|
|
82
|
+
example = frontiers[0]
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"--from is required here: lane {lane_label!r} has {len(frontiers)} active "
|
|
85
|
+
f"frontiers. Pick one:\n{listing}\n"
|
|
86
|
+
f"arctx add --from {example} ..."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
43
90
|
def run_add_step_command(
|
|
44
91
|
*,
|
|
45
92
|
run_id: str,
|
|
46
|
-
input_node_ids: list[str],
|
|
93
|
+
input_node_ids: list[str] | None,
|
|
47
94
|
title: str | None,
|
|
48
95
|
payload_kind: str | None,
|
|
49
96
|
payload_type: str,
|
|
@@ -52,11 +99,15 @@ def run_add_step_command(
|
|
|
52
99
|
store_dir: str,
|
|
53
100
|
user_id: str | None = None,
|
|
54
101
|
lane_id: str | None = None,
|
|
102
|
+
force: bool = False,
|
|
55
103
|
) -> dict:
|
|
56
104
|
store = resolve_store(store_dir)
|
|
57
105
|
if not store.run_path(run_id).exists():
|
|
58
106
|
raise KeyError(f"unknown run_id: {run_id}")
|
|
59
107
|
handle = store.load_run(run_id)
|
|
108
|
+
ensure_lane_open(handle, lane_id, force=force)
|
|
109
|
+
if not input_node_ids:
|
|
110
|
+
input_node_ids = _default_input_node_ids(handle, lane_id or "default")
|
|
60
111
|
data = dict(json_data or {})
|
|
61
112
|
data.update(field_data or {})
|
|
62
113
|
if title is not None:
|
|
@@ -94,8 +145,9 @@ def run_add_step_command(
|
|
|
94
145
|
|
|
95
146
|
def cli_add(args) -> int:
|
|
96
147
|
try:
|
|
148
|
+
run_id = resolve_run_id_from_args(args)
|
|
97
149
|
result = run_add_step_command(
|
|
98
|
-
run_id=
|
|
150
|
+
run_id=run_id,
|
|
99
151
|
input_node_ids=args.input_nodes,
|
|
100
152
|
title=args.title,
|
|
101
153
|
payload_kind=args.payload_kind,
|
|
@@ -105,9 +157,11 @@ def cli_add(args) -> int:
|
|
|
105
157
|
store_dir=args.store_dir,
|
|
106
158
|
user_id=resolve_user_id_from_args(args),
|
|
107
159
|
lane_id=resolve_lane_id_from_args(args),
|
|
160
|
+
force=args.force,
|
|
108
161
|
)
|
|
109
162
|
print(json.dumps(result["step"], ensure_ascii=False, indent=2))
|
|
110
|
-
|
|
163
|
+
strict_rc = warn_if_invalid(run_id, args.store_dir, command_name="add")
|
|
164
|
+
return strict_rc or 0
|
|
111
165
|
except (KeyError, ValueError, json.JSONDecodeError) as exc:
|
|
112
166
|
print(f"error: {exc}", file=sys.stderr)
|
|
113
167
|
return 1
|
|
@@ -13,6 +13,7 @@ from arctx_cli.context import (
|
|
|
13
13
|
resolve_user_id_from_args,
|
|
14
14
|
resolve_lane_id_from_args,
|
|
15
15
|
)
|
|
16
|
+
from arctx_cli.lane_gate import ensure_lane_open
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def add_parser(subparsers) -> argparse.ArgumentParser:
|
|
@@ -27,6 +28,8 @@ def add_parser(subparsers) -> argparse.ArgumentParser:
|
|
|
27
28
|
sp_attach.add_argument("--store-dir", default=None)
|
|
28
29
|
sp_attach.add_argument("--user", default=None)
|
|
29
30
|
sp_attach.add_argument("--lane", default=None)
|
|
31
|
+
sp_attach.add_argument("--force", action="store_true",
|
|
32
|
+
help="Write even if the target lane is closed")
|
|
30
33
|
|
|
31
34
|
sp_list = asset_sub.add_parser("list", help="List assets for a Node or Step")
|
|
32
35
|
sp_list.add_argument("--target", required=True, dest="target_id", help="Node or Step ID")
|
|
@@ -72,6 +75,7 @@ def _cli_asset_attach(args) -> int:
|
|
|
72
75
|
return 1
|
|
73
76
|
|
|
74
77
|
try:
|
|
78
|
+
ensure_lane_open(handle, lane_id, force=args.force)
|
|
75
79
|
before = graph_counts(handle)
|
|
76
80
|
payload = handle.attach_asset(
|
|
77
81
|
args.target_id,
|
|
@@ -15,6 +15,8 @@ from arctx_cli.context import (
|
|
|
15
15
|
resolve_lane_id_from_args,
|
|
16
16
|
)
|
|
17
17
|
from arctx_cli.payload_builder import build_payload, parse_field_args, parse_json_object
|
|
18
|
+
from arctx_cli.lane_gate import ensure_lane_open
|
|
19
|
+
from arctx_cli.post_write_check import warn_if_invalid
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
def add_parser(subparsers) -> argparse.ArgumentParser:
|
|
@@ -28,6 +30,8 @@ def add_parser(subparsers) -> argparse.ArgumentParser:
|
|
|
28
30
|
parser.add_argument("--store-dir", default=None)
|
|
29
31
|
parser.add_argument("--user", default=None)
|
|
30
32
|
parser.add_argument("--lane", default=None)
|
|
33
|
+
parser.add_argument("--force", action="store_true",
|
|
34
|
+
help="Write even if the target lane is closed")
|
|
31
35
|
return parser
|
|
32
36
|
|
|
33
37
|
|
|
@@ -42,11 +46,13 @@ def run_attach_command(
|
|
|
42
46
|
store_dir: str,
|
|
43
47
|
user_id: str | None = None,
|
|
44
48
|
lane_id: str | None = None,
|
|
49
|
+
force: bool = False,
|
|
45
50
|
) -> dict:
|
|
46
51
|
store = resolve_store(store_dir)
|
|
47
52
|
if not store.run_path(run_id).exists():
|
|
48
53
|
raise KeyError(f"unknown run_id: {run_id}")
|
|
49
54
|
handle = store.load_run(run_id)
|
|
55
|
+
ensure_lane_open(handle, lane_id, force=force)
|
|
50
56
|
target_kind = resolve_target_kind(handle, target_id)
|
|
51
57
|
if target_kind == "payload":
|
|
52
58
|
raise ValueError("cannot attach a payload to another payload")
|
|
@@ -97,8 +103,9 @@ def run_attach_command(
|
|
|
97
103
|
|
|
98
104
|
def cli_attach(args) -> int:
|
|
99
105
|
try:
|
|
106
|
+
run_id = resolve_run_id_from_args(args)
|
|
100
107
|
result = run_attach_command(
|
|
101
|
-
run_id=
|
|
108
|
+
run_id=run_id,
|
|
102
109
|
target_id=args.target_id,
|
|
103
110
|
payload_kind=args.payload_kind,
|
|
104
111
|
payload_type=args.payload_type,
|
|
@@ -107,9 +114,11 @@ def cli_attach(args) -> int:
|
|
|
107
114
|
store_dir=args.store_dir,
|
|
108
115
|
user_id=resolve_user_id_from_args(args),
|
|
109
116
|
lane_id=resolve_lane_id_from_args(args),
|
|
117
|
+
force=args.force,
|
|
110
118
|
)
|
|
111
119
|
print(json.dumps(result["payload"], ensure_ascii=False, indent=2))
|
|
112
|
-
|
|
120
|
+
strict_rc = warn_if_invalid(run_id, args.store_dir, command_name="attach")
|
|
121
|
+
return strict_rc or 0
|
|
113
122
|
except (KeyError, ValueError, json.JSONDecodeError) as exc:
|
|
114
123
|
print(f"error: {exc}", file=sys.stderr)
|
|
115
124
|
return 1
|
|
@@ -13,6 +13,7 @@ from arctx_cli.context import (
|
|
|
13
13
|
resolve_lane_id_from_args,
|
|
14
14
|
)
|
|
15
15
|
from arctx_cli.append_batch import graph_counts, maybe_append_or_save
|
|
16
|
+
from arctx_cli.post_write_check import warn_if_invalid
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def add_parser(subparsers) -> argparse.ArgumentParser:
|
|
@@ -99,4 +100,5 @@ def cli_cut(args) -> int:
|
|
|
99
100
|
lane_id=resolve_lane_id_from_args(args),
|
|
100
101
|
)
|
|
101
102
|
print(json.dumps(result["cut"], ensure_ascii=False, indent=2))
|
|
102
|
-
|
|
103
|
+
strict_rc = warn_if_invalid(run_id, args.store_dir, command_name="cut")
|
|
104
|
+
return strict_rc or 0
|
|
@@ -30,6 +30,8 @@ def add_parser(subparsers) -> argparse.ArgumentParser:
|
|
|
30
30
|
help="Hide observed (result) output steps")
|
|
31
31
|
parser.add_argument("--full-payloads", action="store_true",
|
|
32
32
|
help="Include full payload metrics / rationale")
|
|
33
|
+
parser.add_argument("--expand-closed-lanes", action="store_true",
|
|
34
|
+
help="Show closed lane internals instead of summary-only collapsed lanes")
|
|
33
35
|
parser.add_argument("--run", default=None)
|
|
34
36
|
parser.add_argument("--store-dir", default=None)
|
|
35
37
|
return parser
|
|
@@ -43,12 +45,18 @@ def run_dump_command(
|
|
|
43
45
|
node_id: str | None = None,
|
|
44
46
|
depth: int | None = None,
|
|
45
47
|
full_payloads: bool = False,
|
|
48
|
+
expand_closed_lanes: bool = False,
|
|
46
49
|
) -> str:
|
|
47
50
|
store = resolve_store(store_dir)
|
|
48
51
|
if not store.run_path(run_id).exists():
|
|
49
52
|
raise KeyError(f"unknown run_id: {run_id}")
|
|
50
53
|
handle = store.load_run(run_id)
|
|
51
|
-
opts = DumpOptions(
|
|
54
|
+
opts = DumpOptions(
|
|
55
|
+
node_id=node_id,
|
|
56
|
+
depth=depth,
|
|
57
|
+
full_payloads=full_payloads,
|
|
58
|
+
expand_closed_lanes=expand_closed_lanes,
|
|
59
|
+
)
|
|
52
60
|
return dump(handle, fmt, opts)
|
|
53
61
|
|
|
54
62
|
|
|
@@ -67,6 +75,7 @@ def cli_dump(args) -> int:
|
|
|
67
75
|
observed_only=args.observed_only,
|
|
68
76
|
predicted_only=args.predicted_only,
|
|
69
77
|
full_payloads=args.full_payloads,
|
|
78
|
+
expand_closed_lanes=args.expand_closed_lanes,
|
|
70
79
|
)
|
|
71
80
|
print(dump(handle, args.fmt, opts))
|
|
72
81
|
return 0
|