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
8 changes: 5 additions & 3 deletions docs/guides/context-delivery.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,11 @@ response's `recovery` identifies its complete current required view;
Do not substitute one file's hash for another's.

- Use known JSON Pointers directly; repeat `--select` for related fields.
- For an unknown pointer, list the nearest known parent with `--list`; use
`--list-depth 2` when it avoids another discovery round. To list the root,
omit `--select` rather than using `/`, which denotes an empty-name property.
- For an unknown pointer, list the nearest known parent with
`--list /interaction_contract --list-depth 2`. This is equivalent to
`--list --select /interaction_contract --list-depth 2`; repeated `--list POINTER`
and `--select POINTER` combine into the same index. To list the root, use bare
`--list` without selectors or `--list ''`; `/` denotes an empty-name property.
- Follow the returned byte cursor for an unfinished selected range. EOF applies
to that selection, not to unread fields elsewhere in the capture.
- Size both reader pages and the host tool's combined output budget. Valid JSON,
Expand Down
6 changes: 4 additions & 2 deletions docs/guides/context-delivery.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,10 @@ Full 表示完整保存视图可用,不要求判断任何问题前先遍历所
`context_details` 则可指向包含延后证据的原始 capture。不能交换两份文件的 hash。

- 已知 JSON Pointer 直接选择,相关字段用重复 `--select` 批量读取。
- 未知 pointer 先对最近的已知父节点 `--list`;必要时用 `--list-depth 2` 减少发现往返。
列出根节点时省略 `--select`,不要写表示空名称属性的 `/`。
- 未知 pointer 先对最近的已知父节点执行 `--list /interaction_contract --list-depth 2`,
等价于 `--list --select /interaction_contract --list-depth 2`。重复的 `--list POINTER`
与 `--select POINTER` 会合并到同一个索引。列出根节点用不带 selector 的 `--list`
或 `--list ''`;`/` 仍表示空名称属性。
- 未完成的选择沿返回的字节 cursor 继续。EOF 只覆盖该选择,不代表读了其他字段。
- 同时控制 reader 页与宿主工具的合计输出预算。合法 JSON、落盘日志和 EOF 都不能
补上被宿主截掉的字节;缩小批次,从同一 capture 补缺失范围。
Expand Down
3 changes: 2 additions & 1 deletion loopx/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@
dispatch_common_command,
enforce_native_controller_guard,
output_format,
parse_cli_args,
print_payload,
resolve_cli_registry,
resolve_global_output_format,
Expand Down Expand Up @@ -354,7 +355,7 @@ def _main(argv: list[str] | None = None) -> int:
print(render_concise_help(sys.argv[0] if argv is None else "loopx"), end="")
return 0
parser = build_parser()
args = parser.parse_args(raw_argv)
args = parse_cli_args(parser, raw_argv)
from .usage_ping import select_operation
select_operation(args)
args.format = resolve_global_output_format(args)
Expand Down
25 changes: 24 additions & 1 deletion loopx/cli_commands/quota_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,28 @@
)


def normalize_context_list_args(argv: list[str]) -> list[str]:
"""Translate only an explicit JSON pointer after the existing flag."""
normalized = []
index = 0
while index < len(argv):
arg = argv[index]
if arg == "--":
normalized.extend(argv[index:])
break
if arg.startswith("--list="):
normalized.extend(("--list", "--select=" + arg.removeprefix("--list=")))
else:
normalized.append(arg)
if arg == "--list" and index + 1 < len(argv):
pointer = argv[index + 1]
if pointer == "" or pointer.startswith("/"):
normalized.append("--select=" + pointer)
index += 1
index += 1
return normalized


def register_quota_command(
subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
) -> None:
Expand Down Expand Up @@ -257,7 +279,8 @@ def register_quota_command(
help="For context-read or enabled native JSON context-project: repeat an exact RFC 6901 JSON pointer to read a batch from the original capture. "
"Selected detail pages never acknowledge full-context retention; omit for unchanged full capture reads.")
quota_parser.add_argument("--list", dest="list_fields", action="store_true",
help="For context-read: list immediate fields at the selected pointers (root by default), "
help="For context-read: list immediate fields at the selected pointers (root by default). "
"An optional exact JSON pointer is shorthand for --list --select POINTER; repeat to list related parents, "
"with exact pointers, types and sizes. Does not read bodies or establish a delta baseline.")
quota_parser.add_argument("--list-depth", type=int,
help="For context-read --list: expand 1 to 8 levels in one index (default: 1). "
Expand Down
10 changes: 9 additions & 1 deletion loopx/cli_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,14 @@ def _top_level_command(argv: list[str]) -> str | None:
return None


def parse_cli_args(parser: LoopXArgumentParser, argv: list[str]) -> argparse.Namespace:
if _top_level_command(argv) == "quota":
from .cli_commands.quota_registration import normalize_context_list_args

argv = normalize_context_list_args(argv)
return parser.parse_args(argv)


@contextmanager
def cli_invocation_route(argv: list[str]):
"""Read the global route only; the canonical parser still validates argv."""
Expand Down Expand Up @@ -958,7 +966,7 @@ def _run_command(command: str | None, raw_argv: list[str]) -> int:
return _run_full_cli(raw_argv)
if command in _SELECTED_COMMANDS:
try:
args = _build_selected_parser(command).parse_args(raw_argv)
args = parse_cli_args(_build_selected_parser(command), raw_argv)
except _SelectedParseRejected:
return _run_full_cli(raw_argv)
if command == "history" and args.history_action is not None and not _standalone_core():
Expand Down
6 changes: 4 additions & 2 deletions skills/loopx-self-repair/references/targeted-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ available content; it does not prove that content was read. Avoid selecting both
a parent and its children, which repeats the same evidence.

For captured LoopX context, repeat `--select` on the original capture/hash.
Use `--list` only for unknown pointers and `--list-depth 2` for useful nested
discovery. A missing pointer rejects the entire selection: inspect its parent in
Use `--list --select /parent --list-depth 2` for unknown nested pointers;
current readers also accept `--list /parent --list-depth 2`. For the root, omit
the pointer or pass `''`; `/` selects an empty-name property. A missing pointer
rejects the entire selection: inspect its parent in
that same capture before retrying, rather than guessing another batch or rerunning
admission. For large arrays, prefer the known record selector or a focused item
over recursively listing every record.
Expand Down
74 changes: 72 additions & 2 deletions tests/control_plane/test_context_capture_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,11 +302,80 @@ def test_cli_index_defaults_to_root_without_registry_or_runtime(tmp_path):
assert not registry.exists() and not runtime.exists()


def test_cli_nested_index_preserves_depth_and_digest_across_pages(tmp_path):
@pytest.mark.parametrize("listing", [
["--list", "context-read"],
["--list", "/rows/0", "context-read", "--list=/a~1b"],
])
def test_cli_list_before_context_read_preserves_action(tmp_path, listing):
path, raw, _ = fixture_capture(tmp_path)
registry, runtime = tmp_path / "absent.json", tmp_path / "absent-runtime"
capture = ["--capture-file", str(path), "--capture-sha256", hashlib.sha256(raw).hexdigest()]
expected_args = ["--select", "/rows/0", "--select", "/a~1b"] if len(listing) > 2 else []
code, expected = _run_cli(registry, runtime, "quota", "context-read", "--list",
*expected_args, *capture, cwd=tmp_path)
assert code == 0 and expected["eof"], expected
code, actual = _run_cli(registry, runtime, "quota", *listing, *capture, cwd=tmp_path)
assert code == 0 and actual == expected, actual
assert not registry.exists() and not runtime.exists()


@pytest.mark.parametrize("targets", [["/rows/0"], ["/a~1b"], [""], ["/rows/0", "/a~1b"]])
def test_cli_list_target_shorthand_matches_explicit_selection(tmp_path, targets):
path, raw, original = fixture_capture(tmp_path)
registry, runtime = tmp_path / "absent.json", tmp_path / "absent-runtime"
command = ["quota", "context-read", "--capture-file", str(path),
"--capture-sha256", hashlib.sha256(raw).hexdigest(), "--list-depth", "2"]
explicit = ["--list"] + [word for target in targets for word in ("--select", target)]
shorthand = [word for target in targets for word in ("--list", target)]
code, expected = _run_cli(registry, runtime, *command, *explicit, cwd=tmp_path)
assert code == 0 and expected["eof"], expected
code, actual = _run_cli(registry, runtime, *command, *shorthand, cwd=tmp_path)
assert code == 0 and actual == expected, actual
assert original["rows"][0]["tail"] not in actual["content"]
assert capture_page_range(actual, raw=raw, digest=actual["sha256"]) is None
assert path.read_bytes() == raw and not registry.exists() and not runtime.exists()


@pytest.mark.parametrize("options", [
["--list", "/rows/0", "--select", "/a~1b"],
["--select", "/rows/0", "--list", "/a~1b"],
])
def test_cli_list_target_combines_with_existing_selectors(tmp_path, options):
path, raw, _ = fixture_capture(tmp_path)
registry, runtime = tmp_path / "absent.json", tmp_path / "absent-runtime"
code, page = _run_cli(registry, runtime, "quota", "context-read", "--capture-file", str(path),
"--capture-sha256", hashlib.sha256(raw).hexdigest(), *options, cwd=tmp_path)
assert code == 0 and page["eof"], page
assert set(page["selected_pointers"]) == {"/rows/0", "/a~1b"}
assert not registry.exists() and not runtime.exists()


def test_cli_list_slash_does_not_silently_select_root(tmp_path):
path, raw, _ = fixture_capture(tmp_path)
registry, runtime = tmp_path / "absent.json", tmp_path / "absent-runtime"
command = ["quota", "context-read", "--capture-file", str(path),
"--capture-sha256", hashlib.sha256(raw).hexdigest()]
code, failure = _run_cli(registry, runtime, *command, "--list", "/", cwd=tmp_path)
assert code != 0 and failure["diagnostic_code"] == "context_selection_missing_pointer"
assert "content" not in failure
# RFC 6901 '/' selects the empty-name property when one actually exists.
raw = json.dumps({"": {"child": "private body"}, "other": 1}).encode()
path.write_bytes(raw)
command[-1] = hashlib.sha256(raw).hexdigest()
code, page = _run_cli(registry, runtime, *command, "--list", "/", cwd=tmp_path)
assert code == 0 and page["selected_pointers"] == ["/"], page
fields = json.loads(page["content"])["selections"][0]["fields"]
assert [field["pointer"] for field in fields] == ["//child"]
assert "private body" not in page["content"]
assert not registry.exists() and not runtime.exists()


@pytest.mark.parametrize("index_target", [[], [""]])
def test_cli_nested_index_preserves_depth_and_digest_across_pages(tmp_path, index_target):
path, raw, original = fixture_capture(tmp_path)
registry, runtime = tmp_path / "absent.json", tmp_path / "absent-runtime"
command = ["quota", "context-read", "--capture-file", str(path),
"--capture-sha256", hashlib.sha256(raw).hexdigest(), "--list", "--list-depth", "3",
"--capture-sha256", hashlib.sha256(raw).hexdigest(), "--list", *index_target, "--list-depth", "3",
"--page-bytes", "256", "--max-page-output-bytes", "2000"]
combined, first, offset = b"", None, 0
while True:
Expand Down Expand Up @@ -342,6 +411,7 @@ def test_cli_nested_index_preserves_depth_and_digest_across_pages(tmp_path):
["context-read", "--list", "--list-depth", "0"],
["context-read", "--list", "--list-depth", "9"],
["should-run", "--list-depth", "2"],
["should-run", "--list", "/rows", "--list-depth", "2"],
])
def test_cli_rejects_invalid_depth_before_authority(tmp_path, options):
registry, runtime = tmp_path / "absent.json", tmp_path / "absent-runtime"
Expand Down
Loading