@ictechgy/context-guard 0.5.1 → 0.6.0
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.
- package/CHANGELOG.md +2 -0
- package/README.ko.md +10 -1
- package/README.md +10 -1
- package/docs/distribution.md +2 -2
- package/package.json +2 -1
- package/plugins/context-guard/.claude-plugin/plugin.json +1 -1
- package/plugins/context-guard/README.ko.md +10 -1
- package/plugins/context-guard/README.md +10 -0
- package/plugins/context-guard/bin/context-guard-artifact +1 -1
- package/plugins/context-guard/bin/context-guard-pack +626 -3
- package/plugins/context-guard/bin/context-guard-setup +89 -12
- package/plugins/context-guard/bin/context-guard-task-memory +635 -0
- package/plugins/context-guard/lib/context_guard_commands.py +21 -1
- package/plugins/context-guard/lib/context_pack_git_boundary.py +19 -0
- package/plugins/context-guard/lib/context_pack_identity.py +115 -0
- package/plugins/context-guard/lib/context_pack_receipts.py +9 -0
- package/plugins/context-guard/lib/context_pack_rendering.py +12 -0
- package/plugins/context-guard/lib/context_pack_scanning.py +10 -0
- package/plugins/context-guard/lib/context_pack_selection.py +6 -0
|
@@ -52,6 +52,8 @@ ADAPTIVE_K_SCHEMA_VERSION = "contextguard.pack-adaptive-k.v1"
|
|
|
52
52
|
ADAPTIVE_K_APPLICATION_SCHEMA_VERSION = "contextguard.pack-adaptive-k-application.v1"
|
|
53
53
|
SYMBOL_MEMORY_SCHEMA_VERSION = "contextguard.pack-symbol-memory.v1"
|
|
54
54
|
GRAPH_APPLICATION_SCHEMA_VERSION = "contextguard.pack-graph-application.v1"
|
|
55
|
+
SELF_FINANCING_SELECTION_SCHEMA_VERSION = "contextguard.pack-self-financing-selection.v1"
|
|
56
|
+
SELECTION_PLAN_SCHEMA_VERSION = "contextguard.pack-selection-plan.v1"
|
|
55
57
|
CONTENT_ADDRESS_SCHEMA_VERSION = "contextguard.pack-content-address.v1"
|
|
56
58
|
ROLLING_DELTA_SCHEMA_VERSION = "contextguard.pack-rolling-delta.v1"
|
|
57
59
|
SKETCH_DUPLICATE_SHINGLE_WIDTH = 5
|
|
@@ -4720,6 +4722,270 @@ def line_identity_from_dict(value: object) -> str:
|
|
|
4720
4722
|
return f"{value.get('start')}:{value.get('end')}"
|
|
4721
4723
|
|
|
4722
4724
|
|
|
4725
|
+
def frozen_source_content_sha256(
|
|
4726
|
+
path: str,
|
|
4727
|
+
lines: object,
|
|
4728
|
+
source_cache: _SourceSnapshotCache,
|
|
4729
|
+
) -> str | None:
|
|
4730
|
+
rel, _reason = lexical_rel(path)
|
|
4731
|
+
if rel is None:
|
|
4732
|
+
return None
|
|
4733
|
+
snapshot = source_cache.entries.get(
|
|
4734
|
+
(rel.as_posix(), line_identity_from_dict(lines), "source_code")
|
|
4735
|
+
)
|
|
4736
|
+
if snapshot is None:
|
|
4737
|
+
return None
|
|
4738
|
+
return sha256_text("".join(snapshot.selected_lines))
|
|
4739
|
+
|
|
4740
|
+
|
|
4741
|
+
def frozen_source_identity(
|
|
4742
|
+
path: str,
|
|
4743
|
+
lines: object,
|
|
4744
|
+
identities: dict[str, tuple[int, int, int, int, int, int, int, int]],
|
|
4745
|
+
source_cache: _SourceSnapshotCache,
|
|
4746
|
+
) -> str:
|
|
4747
|
+
identity = identities.get(path)
|
|
4748
|
+
content_sha256 = frozen_source_content_sha256(path, lines, source_cache)
|
|
4749
|
+
material = json.dumps(
|
|
4750
|
+
{
|
|
4751
|
+
"content_sha256": content_sha256,
|
|
4752
|
+
"lines": line_identity_from_dict(lines),
|
|
4753
|
+
"path": path,
|
|
4754
|
+
"stat": identity,
|
|
4755
|
+
},
|
|
4756
|
+
ensure_ascii=False,
|
|
4757
|
+
sort_keys=True,
|
|
4758
|
+
separators=(",", ":"),
|
|
4759
|
+
)
|
|
4760
|
+
return f"sha256:{sha256_text(material)}"
|
|
4761
|
+
|
|
4762
|
+
|
|
4763
|
+
def exact_source_fallback(
|
|
4764
|
+
root_arg: str,
|
|
4765
|
+
path: str,
|
|
4766
|
+
lines: object,
|
|
4767
|
+
*,
|
|
4768
|
+
expected_content_sha256: str | None,
|
|
4769
|
+
unavailable_reason: str | None = None,
|
|
4770
|
+
) -> dict[str, Any]:
|
|
4771
|
+
if unavailable_reason is not None:
|
|
4772
|
+
return {"kind": "unavailable", "reason": unavailable_reason}
|
|
4773
|
+
rel, _reason = lexical_rel(path)
|
|
4774
|
+
safe_root = safe_root_arg_for_retrieval(root_arg)
|
|
4775
|
+
if (
|
|
4776
|
+
rel is None
|
|
4777
|
+
or safe_root is None
|
|
4778
|
+
or repo_map_path_has_sensitive_evidence(path)
|
|
4779
|
+
or not isinstance(lines, dict)
|
|
4780
|
+
or not isinstance(lines.get("start"), int)
|
|
4781
|
+
or isinstance(lines.get("start"), bool)
|
|
4782
|
+
or not isinstance(lines.get("end"), int)
|
|
4783
|
+
or isinstance(lines.get("end"), bool)
|
|
4784
|
+
or lines["start"] < 1
|
|
4785
|
+
or lines["end"] < lines["start"]
|
|
4786
|
+
or expected_content_sha256 is None
|
|
4787
|
+
):
|
|
4788
|
+
return {"kind": "unavailable", "reason": "exact_snapshot_unavailable"}
|
|
4789
|
+
line_identity = line_identity_from_dict(lines)
|
|
4790
|
+
args = [
|
|
4791
|
+
"context-guard-pack", "slice", "--root", safe_root,
|
|
4792
|
+
"--path", rel.as_posix(), "--lines", line_identity, "--json",
|
|
4793
|
+
]
|
|
4794
|
+
return {
|
|
4795
|
+
"kind": "exact_source_slice",
|
|
4796
|
+
"command": " ".join(shlex.quote(part) for part in args),
|
|
4797
|
+
"expected_content_sha256": expected_content_sha256,
|
|
4798
|
+
"path": rel.as_posix(),
|
|
4799
|
+
"lines": copy.deepcopy(lines),
|
|
4800
|
+
}
|
|
4801
|
+
|
|
4802
|
+
|
|
4803
|
+
def self_financing_candidate_receipt(
|
|
4804
|
+
*, phase: str, source: dict[str, Any], reason: str, status: str,
|
|
4805
|
+
secret_decision: str, identities: dict[str, tuple[int, int, int, int, int, int, int, int]],
|
|
4806
|
+
root_arg: str, source_cache: _SourceSnapshotCache, byte_delta: int = 0,
|
|
4807
|
+
removed_sources: list[dict[str, Any]] | None = None,
|
|
4808
|
+
) -> dict[str, Any]:
|
|
4809
|
+
path = str(source.get("path", ""))
|
|
4810
|
+
content_sha256 = frozen_source_content_sha256(
|
|
4811
|
+
path, source.get("lines"), source_cache
|
|
4812
|
+
)
|
|
4813
|
+
return {
|
|
4814
|
+
"phase": phase,
|
|
4815
|
+
"status": status,
|
|
4816
|
+
"path": path,
|
|
4817
|
+
"lines": copy.deepcopy(source.get("lines")),
|
|
4818
|
+
"reason": reason,
|
|
4819
|
+
"hop_count": 1 if phase == "graph" else 0,
|
|
4820
|
+
"frozen_identity": frozen_source_identity(
|
|
4821
|
+
path, source.get("lines"), identities, source_cache
|
|
4822
|
+
),
|
|
4823
|
+
"byte_delta": byte_delta,
|
|
4824
|
+
"secret_risk": {"decision": secret_decision, "signal": "bounded_local_pattern_scan"},
|
|
4825
|
+
"exact_fallback": exact_source_fallback(
|
|
4826
|
+
root_arg,
|
|
4827
|
+
path,
|
|
4828
|
+
source.get("lines"),
|
|
4829
|
+
expected_content_sha256=content_sha256,
|
|
4830
|
+
unavailable_reason="secret_risk" if secret_decision == "reject" else None,
|
|
4831
|
+
),
|
|
4832
|
+
"removed_sources": copy.deepcopy(removed_sources or []),
|
|
4833
|
+
}
|
|
4834
|
+
|
|
4835
|
+
|
|
4836
|
+
def selection_plan_source(
|
|
4837
|
+
item: dict[str, Any], identities: dict[str, tuple[int, int, int, int, int, int, int, int]],
|
|
4838
|
+
source_cache: _SourceSnapshotCache, root_arg: str,
|
|
4839
|
+
) -> dict[str, Any]:
|
|
4840
|
+
path = str(item.get("path", ""))
|
|
4841
|
+
lines = copy.deepcopy(item.get("requested_lines", item.get("lines")))
|
|
4842
|
+
content_sha256 = frozen_source_content_sha256(path, lines, source_cache)
|
|
4843
|
+
fallback = exact_source_fallback(
|
|
4844
|
+
root_arg, path, lines, expected_content_sha256=content_sha256
|
|
4845
|
+
)
|
|
4846
|
+
if fallback.get("kind") != "exact_source_slice":
|
|
4847
|
+
raise PackError("selection plan missing exact recovery")
|
|
4848
|
+
return {
|
|
4849
|
+
"path": path,
|
|
4850
|
+
"lines": lines,
|
|
4851
|
+
"identity": frozen_source_identity(path, lines, identities, source_cache),
|
|
4852
|
+
"content_sha256": content_sha256,
|
|
4853
|
+
"exact_fallback": fallback,
|
|
4854
|
+
}
|
|
4855
|
+
|
|
4856
|
+
|
|
4857
|
+
def build_selection_plan(
|
|
4858
|
+
args: argparse.Namespace, ordinary_build: dict[str, Any], selected_build: dict[str, Any],
|
|
4859
|
+
receipt: dict[str, Any], repo_map: dict[str, Any], suggest_payload: dict[str, Any],
|
|
4860
|
+
identities: dict[str, tuple[int, int, int, int, int, int, int, int]],
|
|
4861
|
+
source_cache: _SourceSnapshotCache, *, root_arg: str,
|
|
4862
|
+
) -> dict[str, Any]:
|
|
4863
|
+
if getattr(args, "selection_plan", False) and (args.manifest_out or args.pack_out):
|
|
4864
|
+
raise PackError("selection planning is read-only; output paths are unsupported")
|
|
4865
|
+
if not args.json:
|
|
4866
|
+
raise PackError("selection planning requires --json")
|
|
4867
|
+
if getattr(args, "selection_plan", False) and getattr(args, "apply_selection_plan", None):
|
|
4868
|
+
raise PackError("selection plan and apply are mutually exclusive")
|
|
4869
|
+
if args.delta_from_pack_id:
|
|
4870
|
+
raise PackError("selection plan does not cross private receipt boundaries")
|
|
4871
|
+
explicit_paths = split_suggest_files(args.files) + list(args.output or []) + list(args.test_output or [])
|
|
4872
|
+
if any(repo_map_path_has_sensitive_evidence(path) or re.search(r"(?i)(?:^|[-_/.])(scorer|private)(?:[-_/.]|$)", path) for path in explicit_paths):
|
|
4873
|
+
raise PackError("selection plan refuses scorer/private data")
|
|
4874
|
+
if any(
|
|
4875
|
+
isinstance(item, dict) and str(item.get("path", "")).startswith("redacted-path#")
|
|
4876
|
+
for item in repo_map.get("token_tree", [])
|
|
4877
|
+
):
|
|
4878
|
+
raise PackError("selection plan refuses scorer/private data")
|
|
4879
|
+
caps = repo_map.get("caps", {}) if isinstance(repo_map.get("caps"), dict) else {}
|
|
4880
|
+
summary = repo_map.get("summary", {}) if isinstance(repo_map.get("summary"), dict) else {}
|
|
4881
|
+
graph = repo_map.get("graph", {}) if isinstance(repo_map.get("graph"), dict) else {}
|
|
4882
|
+
if SECRET_CONTENT_RE.search(str(args.query)):
|
|
4883
|
+
raise PackError("selection plan refuses secret-risk input")
|
|
4884
|
+
if (
|
|
4885
|
+
any(bool(caps.get(key)) for key in ("files_capped", "candidate_files_capped", "scan_files_capped"))
|
|
4886
|
+
or int(summary.get("bytes_per_file_capped_count", 0) or 0) != 0
|
|
4887
|
+
or bool(repo_map.get("omitted_files"))
|
|
4888
|
+
or int(graph.get("edges_omitted_by_cap", 0) or 0) != 0
|
|
4889
|
+
or bool(ordinary_build.get("input", {}).get("capped"))
|
|
4890
|
+
or bool(selected_build.get("input", {}).get("capped"))
|
|
4891
|
+
or any(
|
|
4892
|
+
isinstance(item, dict) and item.get("reason") == "query_scan_truncated"
|
|
4893
|
+
for item in suggest_payload.get("omitted_sources", [])
|
|
4894
|
+
)
|
|
4895
|
+
):
|
|
4896
|
+
raise PackError("selection plan requires a complete scan")
|
|
4897
|
+
secret_scan = repo_map.get("secret_scan", {}) if isinstance(repo_map.get("secret_scan"), dict) else {}
|
|
4898
|
+
if (
|
|
4899
|
+
int(ordinary_build.get("redaction", {}).get("redacted_lines", 0) or 0) != 0
|
|
4900
|
+
or int(selected_build.get("redaction", {}).get("redacted_lines", 0) or 0) != 0
|
|
4901
|
+
or bool(secret_scan.get("files_with_risks"))
|
|
4902
|
+
or int(secret_scan.get("files_omitted_by_cap", 0) or 0) != 0
|
|
4903
|
+
):
|
|
4904
|
+
raise PackError("selection plan refuses secret-risk input")
|
|
4905
|
+
|
|
4906
|
+
ordinary = [
|
|
4907
|
+
selection_plan_source(item, identities, source_cache, root_arg)
|
|
4908
|
+
for item in ordinary_build.get("included_sources", []) if isinstance(item, dict)
|
|
4909
|
+
]
|
|
4910
|
+
decisions = [copy.deepcopy(item) for item in receipt.get("decisions", []) if isinstance(item, dict)]
|
|
4911
|
+
for decision in decisions:
|
|
4912
|
+
fallback = decision.get("exact_fallback", {})
|
|
4913
|
+
if fallback.get("kind") != "exact_source_slice":
|
|
4914
|
+
raise PackError("selection plan missing exact recovery")
|
|
4915
|
+
recovered_removed = []
|
|
4916
|
+
for removed in decision.get("removed_sources", []):
|
|
4917
|
+
if not isinstance(removed, dict):
|
|
4918
|
+
raise PackError("selection plan missing exact recovery")
|
|
4919
|
+
recovered = copy.deepcopy(removed)
|
|
4920
|
+
if recovered.get("exact_fallback", {}).get("kind") != "exact_source_slice":
|
|
4921
|
+
source = selection_plan_source(recovered, identities, source_cache, root_arg)
|
|
4922
|
+
recovered["frozen_identity"] = source["identity"]
|
|
4923
|
+
recovered["exact_fallback"] = exact_source_fallback(
|
|
4924
|
+
root_arg, source["path"], source["lines"],
|
|
4925
|
+
expected_content_sha256=source["content_sha256"],
|
|
4926
|
+
)
|
|
4927
|
+
recovered_removed.append(recovered)
|
|
4928
|
+
decision["removed_sources"] = recovered_removed
|
|
4929
|
+
selected = [item for item in decisions if item.get("status") == "selected"]
|
|
4930
|
+
omitted = [item for item in decisions if item.get("status") != "selected"]
|
|
4931
|
+
replacement = [
|
|
4932
|
+
{"candidate_identity": item["frozen_identity"], "removed": copy.deepcopy(item.get("removed_sources", []))}
|
|
4933
|
+
for item in selected if item.get("removed_sources")
|
|
4934
|
+
]
|
|
4935
|
+
fallback = [
|
|
4936
|
+
{"identity": item["identity"], "exact_fallback": copy.deepcopy(item["exact_fallback"])}
|
|
4937
|
+
for item in ordinary
|
|
4938
|
+
] + [
|
|
4939
|
+
{"identity": item["frozen_identity"], "exact_fallback": copy.deepcopy(item["exact_fallback"])}
|
|
4940
|
+
for item in decisions
|
|
4941
|
+
]
|
|
4942
|
+
material: dict[str, Any] = {
|
|
4943
|
+
"schema_version": SELECTION_PLAN_SCHEMA_VERSION,
|
|
4944
|
+
"ordinary": ordinary,
|
|
4945
|
+
"candidate": decisions,
|
|
4946
|
+
"selected": selected,
|
|
4947
|
+
"omitted": omitted,
|
|
4948
|
+
"replacement": replacement,
|
|
4949
|
+
"ceiling": {
|
|
4950
|
+
"unit": "rendered_bytes",
|
|
4951
|
+
"ordinary": int(receipt.get("ordinary_pack_bytes", 0) or 0),
|
|
4952
|
+
"selected": int(receipt.get("selected_rendered_bytes", 0) or 0),
|
|
4953
|
+
},
|
|
4954
|
+
"fallback": fallback,
|
|
4955
|
+
"provenance": {
|
|
4956
|
+
"query_sha256": sha256_text(str(args.query)),
|
|
4957
|
+
"diff": cap_label(args.diff) if args.diff else None,
|
|
4958
|
+
"ordinary_pack_id": ordinary_build.get("pack_id"),
|
|
4959
|
+
"selected_pack_id": selected_build.get("pack_id"),
|
|
4960
|
+
"source_identities": sorted(item["identity"] for item in ordinary),
|
|
4961
|
+
},
|
|
4962
|
+
"safety": {
|
|
4963
|
+
"read_only": True, "provider_free": True, "complete_scan": True,
|
|
4964
|
+
"source_revalidation_required_on_apply": True,
|
|
4965
|
+
},
|
|
4966
|
+
"claim_boundary": {"provider_token_or_cost_savings_claim_allowed": False},
|
|
4967
|
+
}
|
|
4968
|
+
plan_id = "sha256:" + sha256_text(json.dumps(material, ensure_ascii=False, sort_keys=True, separators=(",", ":")))
|
|
4969
|
+
return {"schema_version": material.pop("schema_version"), "plan_id": plan_id, **material}
|
|
4970
|
+
|
|
4971
|
+
|
|
4972
|
+
def read_selection_plan(root: Path, raw_path: str) -> dict[str, Any]:
|
|
4973
|
+
rel = output_rel_for_collision_check(raw_path, "--apply-selection-plan")
|
|
4974
|
+
try:
|
|
4975
|
+
value = json.loads(
|
|
4976
|
+
read_manifest_bytes_no_follow(root / rel).decode("utf-8"),
|
|
4977
|
+
object_pairs_hook=strict_json_object,
|
|
4978
|
+
parse_constant=reject_json_constant,
|
|
4979
|
+
parse_int=parse_receipt_int,
|
|
4980
|
+
)
|
|
4981
|
+
json_depth(value)
|
|
4982
|
+
except (UnicodeDecodeError, ValueError, RecursionError) as exc:
|
|
4983
|
+
raise PackError("invalid selection plan JSON") from exc
|
|
4984
|
+
if not isinstance(value, dict) or value.get("schema_version") != SELECTION_PLAN_SCHEMA_VERSION:
|
|
4985
|
+
raise PackError("unsupported selection plan schema")
|
|
4986
|
+
return value
|
|
4987
|
+
|
|
4988
|
+
|
|
4723
4989
|
def apply_symbol_memory_graph(
|
|
4724
4990
|
manifest: dict[str, Any],
|
|
4725
4991
|
repo_map: dict[str, Any],
|
|
@@ -5095,6 +5361,9 @@ def build_auto_explain_payload(
|
|
|
5095
5361
|
|
|
5096
5362
|
|
|
5097
5363
|
def auto_pack(root: Path, args: argparse.Namespace, *, root_arg: str) -> tuple[dict[str, Any], int]:
|
|
5364
|
+
plan_only = bool(getattr(args, "selection_plan", False))
|
|
5365
|
+
apply_plan_path = getattr(args, "apply_selection_plan", None)
|
|
5366
|
+
expected_plan = read_selection_plan(root, apply_plan_path) if apply_plan_path else None
|
|
5098
5367
|
source_cache = _SourceSnapshotCache()
|
|
5099
5368
|
input_budget = _SourceInputBudget()
|
|
5100
5369
|
manifest_rel = output_rel_for_collision_check(args.manifest_out, "--manifest-out") if args.manifest_out else None
|
|
@@ -5113,7 +5382,8 @@ def auto_pack(root: Path, args: argparse.Namespace, *, root_arg: str) -> tuple[d
|
|
|
5113
5382
|
validate_output_path_under_root(root, args.pack_out, "--pack-out")
|
|
5114
5383
|
suggest_args = copy.copy(args)
|
|
5115
5384
|
suggest_args.manifest_out = None
|
|
5116
|
-
|
|
5385
|
+
self_financing = bool(getattr(args, "self_financing_selection", False) or plan_only or apply_plan_path)
|
|
5386
|
+
apply_adaptive_k = bool(getattr(args, "apply_adaptive_k", False) or self_financing)
|
|
5117
5387
|
if apply_adaptive_k:
|
|
5118
5388
|
suggest_args.adaptive_k = True
|
|
5119
5389
|
suggest_payload, rc = suggest_pack(
|
|
@@ -5124,6 +5394,18 @@ def auto_pack(root: Path, args: argparse.Namespace, *, root_arg: str) -> tuple[d
|
|
|
5124
5394
|
_input_budget=input_budget,
|
|
5125
5395
|
)
|
|
5126
5396
|
manifest = suggest_payload["manifest"]
|
|
5397
|
+
ordinary_build_payload: dict[str, Any] | None = None
|
|
5398
|
+
if self_financing:
|
|
5399
|
+
ordinary_build_payload = build_pack(
|
|
5400
|
+
root,
|
|
5401
|
+
manifest_to_source_specs(manifest),
|
|
5402
|
+
budget_bytes=bounded_int(args.budget_bytes, DEFAULT_BUDGET_BYTES, MIN_BUDGET_BYTES, MAX_BUDGET_BYTES),
|
|
5403
|
+
root_arg=root_arg,
|
|
5404
|
+
store_artifact=False,
|
|
5405
|
+
sketch_duplicate_veto=getattr(args, "sketch_duplicate_veto", False),
|
|
5406
|
+
_source_cache=source_cache,
|
|
5407
|
+
_input_budget=input_budget,
|
|
5408
|
+
)
|
|
5127
5409
|
adaptive_k_application: dict[str, Any] | None = None
|
|
5128
5410
|
if apply_adaptive_k and isinstance(suggest_payload.get("adaptive_k"), dict):
|
|
5129
5411
|
manifest, adaptive_k_application = apply_adaptive_k_manifest(
|
|
@@ -5166,7 +5448,7 @@ def auto_pack(root: Path, args: argparse.Namespace, *, root_arg: str) -> tuple[d
|
|
|
5166
5448
|
)
|
|
5167
5449
|
repo_map_payload: dict[str, Any] | None = None
|
|
5168
5450
|
graph_application: dict[str, Any] | None = None
|
|
5169
|
-
apply_symbol_memory = bool(getattr(args, "apply_symbol_memory", False))
|
|
5451
|
+
apply_symbol_memory = bool(getattr(args, "apply_symbol_memory", False) or self_financing)
|
|
5170
5452
|
complete_secret_paths: set[str] | None = set() if apply_symbol_memory else None
|
|
5171
5453
|
repo_map_source_identities: dict[
|
|
5172
5454
|
str,
|
|
@@ -5184,7 +5466,295 @@ def auto_pack(root: Path, args: argparse.Namespace, *, root_arg: str) -> tuple[d
|
|
|
5184
5466
|
repo_map_source_identities if apply_symbol_memory else None
|
|
5185
5467
|
),
|
|
5186
5468
|
)
|
|
5187
|
-
|
|
5469
|
+
self_financing_receipt: dict[str, Any] | None = None
|
|
5470
|
+
if self_financing and isinstance(repo_map_payload, dict) and ordinary_build_payload is not None:
|
|
5471
|
+
ordinary_ceiling = int(ordinary_build_payload.get("pack_bytes", 0) or 0)
|
|
5472
|
+
decisions: list[dict[str, Any]] = []
|
|
5473
|
+
recorded_secret_decisions: set[tuple[str, str]] = set()
|
|
5474
|
+
ordinary_sources = ordinary_build_payload.get("included_sources", [])
|
|
5475
|
+
retained_keys = {
|
|
5476
|
+
(str(item.get("path", "")), line_range_identity(item.get("lines")))
|
|
5477
|
+
for item in manifest.get("sources", []) if isinstance(item, dict)
|
|
5478
|
+
}
|
|
5479
|
+
for item in ordinary_sources if isinstance(ordinary_sources, list) else []:
|
|
5480
|
+
if not isinstance(item, dict):
|
|
5481
|
+
continue
|
|
5482
|
+
key = (str(item.get("path", "")), line_range_identity(item.get("requested_lines")))
|
|
5483
|
+
if key not in retained_keys:
|
|
5484
|
+
source = {"path": key[0], "lines": copy.deepcopy(item.get("requested_lines"))}
|
|
5485
|
+
decisions.append(self_financing_candidate_receipt(
|
|
5486
|
+
phase="adaptive", source=source, reason="adaptive_headroom_removal",
|
|
5487
|
+
status="selected", secret_decision="allow", identities=repo_map_source_identities,
|
|
5488
|
+
root_arg=root_arg, source_cache=source_cache,
|
|
5489
|
+
byte_delta=-int(item.get("bytes", 0) or 0),
|
|
5490
|
+
removed_sources=[source],
|
|
5491
|
+
))
|
|
5492
|
+
|
|
5493
|
+
existing_paths = {str(item.get("path", "")) for item in manifest.get("sources", []) if isinstance(item, dict)}
|
|
5494
|
+
query_terms = suggest_tokens(str(suggest_payload.get("query", "")))
|
|
5495
|
+
candidates: list[tuple[str, dict[str, Any], str]] = []
|
|
5496
|
+
for signature in repo_map_payload.get("signature_index", []):
|
|
5497
|
+
if not isinstance(signature, dict):
|
|
5498
|
+
continue
|
|
5499
|
+
path = str(signature.get("path", ""))
|
|
5500
|
+
searchable = suggest_tokens(f"{signature.get('name', '')} {signature.get('signature', '')}")
|
|
5501
|
+
if not query_terms.intersection(searchable):
|
|
5502
|
+
continue
|
|
5503
|
+
source = {
|
|
5504
|
+
"path": path, "priority": 2, "label": f"symbol:{path}"[:MAX_LABEL_CHARS],
|
|
5505
|
+
"lines": copy.deepcopy(signature.get("lines")),
|
|
5506
|
+
}
|
|
5507
|
+
if path in (complete_secret_paths or set()):
|
|
5508
|
+
decisions.append(self_financing_candidate_receipt(
|
|
5509
|
+
phase="symbol", source=source, reason="secret_risk", status="no_op",
|
|
5510
|
+
secret_decision="reject", identities=repo_map_source_identities,
|
|
5511
|
+
root_arg=root_arg, source_cache=source_cache,
|
|
5512
|
+
))
|
|
5513
|
+
recorded_secret_decisions.add(("symbol", path))
|
|
5514
|
+
continue
|
|
5515
|
+
if path in existing_paths:
|
|
5516
|
+
existing_source = next(
|
|
5517
|
+
(
|
|
5518
|
+
item for item in build_payload.get("included_sources", [])
|
|
5519
|
+
if isinstance(item, dict)
|
|
5520
|
+
and item.get("path") == path
|
|
5521
|
+
and item.get("status") == "included"
|
|
5522
|
+
and item.get("requested_lines") == item.get("included_lines")
|
|
5523
|
+
),
|
|
5524
|
+
None,
|
|
5525
|
+
)
|
|
5526
|
+
if existing_source is not None:
|
|
5527
|
+
source["lines"] = copy.deepcopy(existing_source["requested_lines"])
|
|
5528
|
+
decisions.append(self_financing_candidate_receipt(
|
|
5529
|
+
phase="symbol", source=source, reason="duplicate_source", status="no_op",
|
|
5530
|
+
secret_decision="allow", identities=repo_map_source_identities,
|
|
5531
|
+
root_arg=root_arg, source_cache=source_cache,
|
|
5532
|
+
))
|
|
5533
|
+
continue
|
|
5534
|
+
candidates.append(("symbol", source, "task_matching_symbol"))
|
|
5535
|
+
if len([item for item in candidates if item[0] == "symbol"]) >= MAX_GRAPH_APPLICATION_SOURCES:
|
|
5536
|
+
break
|
|
5537
|
+
graph_manifest, graph_preview = apply_symbol_memory_graph(
|
|
5538
|
+
manifest, repo_map_payload, complete_secret_paths=complete_secret_paths,
|
|
5539
|
+
)
|
|
5540
|
+
graph_sources = graph_manifest.get("sources", [])
|
|
5541
|
+
for source in graph_sources[len(manifest.get("sources", [])):]:
|
|
5542
|
+
if isinstance(source, dict):
|
|
5543
|
+
candidates.append(("graph", source, "direct_import_neighbor"))
|
|
5544
|
+
|
|
5545
|
+
frozen_candidate_sources = {
|
|
5546
|
+
(str(source.get("path", "")), line_range_identity(source.get("lines")))
|
|
5547
|
+
for _phase, source, _reason in candidates
|
|
5548
|
+
}
|
|
5549
|
+
candidate_specs = manifest_to_source_specs(build_suggest_manifest(
|
|
5550
|
+
[source for _phase, source, _reason in candidates]
|
|
5551
|
+
))
|
|
5552
|
+
candidate_snapshot_rejections = bind_graph_sources_to_repo_snapshot(
|
|
5553
|
+
root, candidate_specs, frozen_candidate_sources, repo_map_source_identities,
|
|
5554
|
+
source_cache=source_cache, input_budget=input_budget,
|
|
5555
|
+
)
|
|
5556
|
+
|
|
5557
|
+
# Record secret-risk direct neighbors as explicit no-ops without exposing their contents.
|
|
5558
|
+
graph = repo_map_payload.get("graph", {})
|
|
5559
|
+
for edge in graph.get("edges", []) if isinstance(graph, dict) else []:
|
|
5560
|
+
if not isinstance(edge, dict):
|
|
5561
|
+
continue
|
|
5562
|
+
for path in (edge.get("from"), edge.get("to")):
|
|
5563
|
+
if isinstance(path, str) and path in (complete_secret_paths or set()) and path not in existing_paths:
|
|
5564
|
+
if ("graph", path) in recorded_secret_decisions:
|
|
5565
|
+
continue
|
|
5566
|
+
source = {"path": path, "lines": None}
|
|
5567
|
+
decisions.append(self_financing_candidate_receipt(
|
|
5568
|
+
phase="graph", source=source, reason="secret_risk", status="no_op",
|
|
5569
|
+
secret_decision="reject", identities=repo_map_source_identities,
|
|
5570
|
+
root_arg=root_arg, source_cache=source_cache,
|
|
5571
|
+
))
|
|
5572
|
+
recorded_secret_decisions.add(("graph", path))
|
|
5573
|
+
|
|
5574
|
+
current_manifest = copy.deepcopy(manifest)
|
|
5575
|
+
current_build = build_payload
|
|
5576
|
+
protected_sources = [
|
|
5577
|
+
(
|
|
5578
|
+
str(item.get("path", "")),
|
|
5579
|
+
copy.deepcopy(item.get("lines")) if isinstance(item.get("lines"), dict) else None,
|
|
5580
|
+
)
|
|
5581
|
+
for item in current_manifest.get("sources", [])
|
|
5582
|
+
if isinstance(item, dict)
|
|
5583
|
+
and str(item.get("label", "")).startswith(
|
|
5584
|
+
("file:", "output:", "test-output:", "diff:", "critical:")
|
|
5585
|
+
)
|
|
5586
|
+
]
|
|
5587
|
+
|
|
5588
|
+
def protected_sources_are_exact(build: dict[str, Any]) -> bool:
|
|
5589
|
+
included_sources = [
|
|
5590
|
+
item for item in build.get("included_sources", [])
|
|
5591
|
+
if isinstance(item, dict)
|
|
5592
|
+
]
|
|
5593
|
+
for protected_path, protected_lines in protected_sources:
|
|
5594
|
+
matched = False
|
|
5595
|
+
for item in included_sources:
|
|
5596
|
+
if (
|
|
5597
|
+
item.get("path") != protected_path
|
|
5598
|
+
or item.get("status") != "included"
|
|
5599
|
+
or item.get("requested_lines") != item.get("included_lines")
|
|
5600
|
+
):
|
|
5601
|
+
continue
|
|
5602
|
+
if protected_lines is not None and item.get("requested_lines") != protected_lines:
|
|
5603
|
+
continue
|
|
5604
|
+
matched = True
|
|
5605
|
+
break
|
|
5606
|
+
if not matched:
|
|
5607
|
+
return False
|
|
5608
|
+
return True
|
|
5609
|
+
|
|
5610
|
+
seen_candidates: set[tuple[str, str]] = set()
|
|
5611
|
+
for phase, candidate, reason in candidates:
|
|
5612
|
+
key = (str(candidate.get("path", "")), line_range_identity(candidate.get("lines")))
|
|
5613
|
+
if key in seen_candidates or key[0] in {str(item.get("path", "")) for item in current_manifest.get("sources", []) if isinstance(item, dict)}:
|
|
5614
|
+
decisions.append(self_financing_candidate_receipt(
|
|
5615
|
+
phase=phase, source=candidate, reason="duplicate_source", status="no_op",
|
|
5616
|
+
secret_decision="allow", identities=repo_map_source_identities,
|
|
5617
|
+
root_arg=root_arg, source_cache=source_cache,
|
|
5618
|
+
))
|
|
5619
|
+
continue
|
|
5620
|
+
seen_candidates.add(key)
|
|
5621
|
+
trial_sources = [copy.deepcopy(item) for item in current_manifest.get("sources", []) if isinstance(item, dict)] + [copy.deepcopy(candidate)]
|
|
5622
|
+
candidate_priority = int(candidate.get("priority", 0) or 0)
|
|
5623
|
+
removable = sorted(
|
|
5624
|
+
[
|
|
5625
|
+
item for item in trial_sources[:-1]
|
|
5626
|
+
if not str(item.get("label", "")).startswith(
|
|
5627
|
+
("file:", "output:", "test-output:", "diff:", "critical:")
|
|
5628
|
+
)
|
|
5629
|
+
and int(item.get("priority", 0) or 0) < candidate_priority
|
|
5630
|
+
],
|
|
5631
|
+
key=lambda item: (int(item.get("priority", 0) or 0), str(item.get("path", ""))),
|
|
5632
|
+
)
|
|
5633
|
+
removed: list[dict[str, Any]] = []
|
|
5634
|
+
accepted_build: dict[str, Any] | None = None
|
|
5635
|
+
while True:
|
|
5636
|
+
trial_manifest = build_suggest_manifest(trial_sources)
|
|
5637
|
+
trial_build = build_pack(
|
|
5638
|
+
root, manifest_to_source_specs(trial_manifest), budget_bytes=max(MIN_BUDGET_BYTES, ordinary_ceiling),
|
|
5639
|
+
root_arg=root_arg, store_artifact=False,
|
|
5640
|
+
sketch_duplicate_veto=getattr(args, "sketch_duplicate_veto", False),
|
|
5641
|
+
_source_cache=source_cache, _input_budget=input_budget,
|
|
5642
|
+
_required_snapshot_sources=frozen_candidate_sources,
|
|
5643
|
+
_expected_source_identities=repo_map_source_identities,
|
|
5644
|
+
_snapshot_rejections=candidate_snapshot_rejections,
|
|
5645
|
+
)
|
|
5646
|
+
candidate_included_exactly = any(
|
|
5647
|
+
isinstance(item, dict)
|
|
5648
|
+
and str(item.get("path", "")) == key[0]
|
|
5649
|
+
and line_range_identity(item.get("requested_lines")) == key[1]
|
|
5650
|
+
and line_range_identity(item.get("included_lines")) == key[1]
|
|
5651
|
+
and item.get("status") == "included"
|
|
5652
|
+
for item in trial_build.get("included_sources", [])
|
|
5653
|
+
)
|
|
5654
|
+
if (
|
|
5655
|
+
int(trial_build.get("pack_bytes", 0) or 0) <= ordinary_ceiling
|
|
5656
|
+
and candidate_included_exactly
|
|
5657
|
+
and protected_sources_are_exact(trial_build)
|
|
5658
|
+
):
|
|
5659
|
+
accepted_build = trial_build
|
|
5660
|
+
break
|
|
5661
|
+
if not removable:
|
|
5662
|
+
break
|
|
5663
|
+
victim = removable.pop(0)
|
|
5664
|
+
trial_sources.remove(victim)
|
|
5665
|
+
victim_source = {
|
|
5666
|
+
"path": victim.get("path"),
|
|
5667
|
+
"lines": copy.deepcopy(victim.get("lines")),
|
|
5668
|
+
}
|
|
5669
|
+
if not isinstance(victim_source["lines"], dict):
|
|
5670
|
+
prior = next(
|
|
5671
|
+
(
|
|
5672
|
+
item for item in current_build.get("included_sources", [])
|
|
5673
|
+
if isinstance(item, dict)
|
|
5674
|
+
and item.get("path") == victim_source["path"]
|
|
5675
|
+
and item.get("status") == "included"
|
|
5676
|
+
and item.get("requested_lines") == item.get("included_lines")
|
|
5677
|
+
),
|
|
5678
|
+
None,
|
|
5679
|
+
)
|
|
5680
|
+
if prior is not None:
|
|
5681
|
+
victim_source["lines"] = copy.deepcopy(prior["requested_lines"])
|
|
5682
|
+
removed.append({
|
|
5683
|
+
"path": victim_source["path"], "lines": victim_source["lines"],
|
|
5684
|
+
"reason": "lower_value_replacement",
|
|
5685
|
+
"frozen_identity": frozen_source_identity(
|
|
5686
|
+
str(victim_source["path"] or ""), victim_source["lines"],
|
|
5687
|
+
repo_map_source_identities, source_cache,
|
|
5688
|
+
),
|
|
5689
|
+
"exact_fallback": exact_source_fallback(
|
|
5690
|
+
root_arg, str(victim_source["path"] or ""), victim_source["lines"],
|
|
5691
|
+
expected_content_sha256=frozen_source_content_sha256(
|
|
5692
|
+
str(victim_source["path"] or ""), victim_source["lines"], source_cache
|
|
5693
|
+
),
|
|
5694
|
+
),
|
|
5695
|
+
})
|
|
5696
|
+
if accepted_build is None:
|
|
5697
|
+
decisions.append(self_financing_candidate_receipt(
|
|
5698
|
+
phase=phase, source=candidate, reason="ordinary_ceiling_no_safe_replacement", status="no_op",
|
|
5699
|
+
secret_decision="allow", identities=repo_map_source_identities,
|
|
5700
|
+
root_arg=root_arg, source_cache=source_cache,
|
|
5701
|
+
))
|
|
5702
|
+
continue
|
|
5703
|
+
previous_bytes = int(current_build.get("pack_bytes", 0) or 0)
|
|
5704
|
+
current_manifest = build_suggest_manifest(trial_sources)
|
|
5705
|
+
current_build = accepted_build
|
|
5706
|
+
decisions.append(self_financing_candidate_receipt(
|
|
5707
|
+
phase=phase, source=candidate, reason=reason, status="selected",
|
|
5708
|
+
secret_decision="allow", identities=repo_map_source_identities,
|
|
5709
|
+
root_arg=root_arg, source_cache=source_cache,
|
|
5710
|
+
byte_delta=int(current_build.get("pack_bytes", 0) or 0) - previous_bytes,
|
|
5711
|
+
removed_sources=removed,
|
|
5712
|
+
))
|
|
5713
|
+
manifest = current_manifest
|
|
5714
|
+
build_payload = current_build
|
|
5715
|
+
selected_rendered_bytes = int(build_payload.get("pack_bytes", 0) or 0)
|
|
5716
|
+
if (
|
|
5717
|
+
selected_rendered_bytes > ordinary_ceiling
|
|
5718
|
+
or not protected_sources_are_exact(build_payload)
|
|
5719
|
+
):
|
|
5720
|
+
raise PackError("self-financing selection invariant failed")
|
|
5721
|
+
suggest_payload["manifest"] = manifest
|
|
5722
|
+
suggest_payload["estimated_pack_bytes"] = build_payload.get("pack_bytes", 0)
|
|
5723
|
+
suggest_payload["token_proxy"] = copy.deepcopy(build_payload.get("token_proxy", {}))
|
|
5724
|
+
selected_graph_decisions = [
|
|
5725
|
+
item for item in decisions
|
|
5726
|
+
if item.get("phase") == "graph" and item.get("status") == "selected"
|
|
5727
|
+
]
|
|
5728
|
+
graph_application = {
|
|
5729
|
+
**graph_preview,
|
|
5730
|
+
"selected_source_count": len(selected_graph_decisions),
|
|
5731
|
+
"selected_sources": [
|
|
5732
|
+
{
|
|
5733
|
+
"path": item.get("path"), "lines": copy.deepcopy(item.get("lines")),
|
|
5734
|
+
"reason": item.get("reason"),
|
|
5735
|
+
}
|
|
5736
|
+
for item in selected_graph_decisions
|
|
5737
|
+
],
|
|
5738
|
+
}
|
|
5739
|
+
phase_results = {}
|
|
5740
|
+
for phase in ("adaptive", "symbol", "graph"):
|
|
5741
|
+
phase_decisions = [item for item in decisions if item.get("phase") == phase]
|
|
5742
|
+
phase_results[phase] = {
|
|
5743
|
+
"status": "applied" if any(item.get("status") == "selected" for item in phase_decisions) else "no_op",
|
|
5744
|
+
"selected_count": sum(item.get("status") == "selected" for item in phase_decisions),
|
|
5745
|
+
"no_op_count": sum(item.get("status") == "no_op" for item in phase_decisions),
|
|
5746
|
+
}
|
|
5747
|
+
self_financing_receipt = {
|
|
5748
|
+
"schema_version": SELF_FINANCING_SELECTION_SCHEMA_VERSION,
|
|
5749
|
+
"mode": "explicit_opt_in", "phase_order": ["adaptive", "symbol", "graph"],
|
|
5750
|
+
"ordinary_pack_bytes": ordinary_ceiling,
|
|
5751
|
+
"selected_rendered_bytes": selected_rendered_bytes,
|
|
5752
|
+
"ceiling_respected": selected_rendered_bytes <= ordinary_ceiling,
|
|
5753
|
+
"phase_results": phase_results,
|
|
5754
|
+
"decisions": decisions,
|
|
5755
|
+
"claim_boundary": {"provider_token_or_cost_savings_claim_allowed": False},
|
|
5756
|
+
}
|
|
5757
|
+
elif apply_symbol_memory and isinstance(repo_map_payload, dict):
|
|
5188
5758
|
repo_map_payload["safety"]["explain_only"] = False
|
|
5189
5759
|
repo_map_payload["safety"]["caveats"] = [
|
|
5190
5760
|
"Repo-map bytes are local sampled UTF-8 bytes and estimated chars_div_4 token proxies, not provider-token or savings claims.",
|
|
@@ -5233,6 +5803,22 @@ def auto_pack(root: Path, args: argparse.Namespace, *, root_arg: str) -> tuple[d
|
|
|
5233
5803
|
suggest_payload["token_proxy"] = copy.deepcopy(
|
|
5234
5804
|
build_payload.get("token_proxy", {})
|
|
5235
5805
|
)
|
|
5806
|
+
selection_plan_payload: dict[str, Any] | None = None
|
|
5807
|
+
if plan_only or apply_plan_path:
|
|
5808
|
+
if not isinstance(self_financing_receipt, dict) or not isinstance(repo_map_payload, dict) or ordinary_build_payload is None:
|
|
5809
|
+
raise PackError("selection plan unavailable")
|
|
5810
|
+
selection_plan_payload = build_selection_plan(
|
|
5811
|
+
args, ordinary_build_payload, build_payload, self_financing_receipt,
|
|
5812
|
+
repo_map_payload, suggest_payload, repo_map_source_identities, source_cache, root_arg=root_arg,
|
|
5813
|
+
)
|
|
5814
|
+
if plan_only:
|
|
5815
|
+
return {
|
|
5816
|
+
"tool": TOOL_NAME, "schema_version": AUTO_SCHEMA_VERSION,
|
|
5817
|
+
"version": VERSION, "mode": "selection_plan",
|
|
5818
|
+
"selection_plan": selection_plan_payload,
|
|
5819
|
+
}, rc
|
|
5820
|
+
if expected_plan != selection_plan_payload:
|
|
5821
|
+
raise PackError("selection plan drift; regenerate the plan")
|
|
5236
5822
|
if not args.no_artifact:
|
|
5237
5823
|
receipt_rel = Path(PACK_DIR) / f"{build_payload['pack_id']}.json"
|
|
5238
5824
|
if manifest_rel is not None:
|
|
@@ -5301,6 +5887,14 @@ def auto_pack(root: Path, args: argparse.Namespace, *, root_arg: str) -> tuple[d
|
|
|
5301
5887
|
payload["adaptive_k_application"] = adaptive_k_application
|
|
5302
5888
|
if graph_application is not None:
|
|
5303
5889
|
payload["graph_application"] = graph_application
|
|
5890
|
+
if self_financing_receipt is not None:
|
|
5891
|
+
payload["self_financing_selection"] = self_financing_receipt
|
|
5892
|
+
if selection_plan_payload is not None:
|
|
5893
|
+
payload["selection_plan"] = selection_plan_payload
|
|
5894
|
+
payload["selection_plan_application"] = {
|
|
5895
|
+
"status": "applied", "explicit": True,
|
|
5896
|
+
"revalidated_plan_id": selection_plan_payload["plan_id"],
|
|
5897
|
+
}
|
|
5304
5898
|
if (getattr(args, "symbol_memory", False) or apply_symbol_memory) and isinstance(repo_map_payload, dict):
|
|
5305
5899
|
payload["symbol_memory"] = build_symbol_memory_payload(
|
|
5306
5900
|
repo_map_payload, applied=apply_symbol_memory
|
|
@@ -5436,6 +6030,19 @@ def print_auto_text(payload: dict[str, Any]) -> None:
|
|
|
5436
6030
|
print(f"omitted reasons: {reason_text}")
|
|
5437
6031
|
print_adaptive_k_text(payload)
|
|
5438
6032
|
print_symbol_memory_text(payload)
|
|
6033
|
+
self_financing = payload.get("self_financing_selection")
|
|
6034
|
+
if isinstance(self_financing, dict):
|
|
6035
|
+
phases = self_financing.get("phase_results", {})
|
|
6036
|
+
phase_text = ",".join(
|
|
6037
|
+
f"{name}={phases.get(name, {}).get('status', 'no_op')}"
|
|
6038
|
+
for name in ("adaptive", "symbol", "graph")
|
|
6039
|
+
)
|
|
6040
|
+
print(
|
|
6041
|
+
"self-financing: "
|
|
6042
|
+
f"ceiling={self_financing.get('ordinary_pack_bytes', 0)} "
|
|
6043
|
+
f"selected={self_financing.get('selected_rendered_bytes', 0)} "
|
|
6044
|
+
f"{phase_text} provider_savings_claim=false"
|
|
6045
|
+
)
|
|
5439
6046
|
if payload.get("manifest_path"):
|
|
5440
6047
|
print(f"manifest: {payload['manifest_path']}")
|
|
5441
6048
|
if payload.get("pack_path"):
|
|
@@ -5549,6 +6156,22 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
5549
6156
|
"repo map to the manifest and pack; implies --symbol-memory"
|
|
5550
6157
|
),
|
|
5551
6158
|
)
|
|
6159
|
+
auto.add_argument(
|
|
6160
|
+
"--self-financing-selection",
|
|
6161
|
+
action="store_true",
|
|
6162
|
+
help=(
|
|
6163
|
+
"explicitly apply Adaptive, then Symbol, then one-hop Graph selection while replacing "
|
|
6164
|
+
"only lower-value non-caller sources and never exceeding the ordinary pack bytes"
|
|
6165
|
+
),
|
|
6166
|
+
)
|
|
6167
|
+
auto.add_argument(
|
|
6168
|
+
"--selection-plan", action="store_true",
|
|
6169
|
+
help="emit a read-only provider-free self-financing selection plan; requires --json",
|
|
6170
|
+
)
|
|
6171
|
+
auto.add_argument(
|
|
6172
|
+
"--apply-selection-plan", metavar="PLAN_JSON",
|
|
6173
|
+
help="explicitly apply a previously emitted selection plan after identity revalidation",
|
|
6174
|
+
)
|
|
5552
6175
|
return parser
|
|
5553
6176
|
|
|
5554
6177
|
|