CANP-WP-0005: document and detect inclusion diamonds
Section 10.4 said nothing about inclusion being textual and repeated, so an author met the behaviour only by reading rendered output carefully. That silence was the defect. Writing it up got the mechanism wrong at first. I documented a dependency "reached by two paths" through two include chains, and wrote a detector that counted composer calls to match. Tested it against the real case that motivated the task — helix/repo-advance — and it did not fire, though the conventions block still rendered twice. The actual mechanism is subtler. helix/commit-sync declares its own `conventions` input; when composed, resolution passes the including package's already-resolved values down by name, so it *inherits* the outer text rather than resolving its own include, and renders it again. Nothing was included twice; the text appeared twice regardless. Both mechanisms are now documented with worked diagrams. duplicate_inclusions detects both — included values that are equal, and an included value contained within another — and resolve, render and eval warn. Nothing is deduplicated, as leaned. Deduplicating means choosing which occurrence survives, since position in a prompt carries meaning, and deciding what happens when two paths select different versions of the same dependency, which section 10.3 permits. That is resolver behaviour and section 10.4 keeps composition declarative. Verified by reintroducing the diamond in a scratch copy of repo-advance, which warns, and confirming the shipped factored collection stays silent. Tests 90 -> 95. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
This commit is contained in:
parent
e16bf024d4
commit
75e0597019
5 changed files with 180 additions and 7 deletions
|
|
@ -867,3 +867,50 @@ def test_bad_index_format_is_rejected(tmp_path: Path) -> None:
|
|||
(store / "index.yaml").write_text("format: something/else\nentries: []\n", encoding="utf-8")
|
||||
with pytest.raises(cp.CannedPromptError, match="unsupported index format"):
|
||||
cp.read_index(store)
|
||||
|
||||
|
||||
# --- inclusion diamonds (§ 10.4) ---
|
||||
|
||||
def test_no_duplicate_when_inclusions_differ() -> None:
|
||||
resolution = cp.Resolution(
|
||||
values={"a": "alpha text", "b": "beta text"},
|
||||
origins={"a": "included from x/a", "b": "included from x/b"},
|
||||
)
|
||||
assert cp.duplicate_inclusions(resolution) == []
|
||||
|
||||
|
||||
def test_two_inputs_including_the_same_thing_is_flagged() -> None:
|
||||
resolution = cp.Resolution(
|
||||
values={"a": "shared text", "b": "shared text"},
|
||||
origins={"a": "included from x/shared", "b": "included from x/shared"},
|
||||
)
|
||||
assert cp.duplicate_inclusions(resolution) == ["a", "b"]
|
||||
|
||||
|
||||
def test_nested_inheritance_duplication_is_flagged() -> None:
|
||||
"""The common case: an included package inherits an outer input by name."""
|
||||
resolution = cp.Resolution(
|
||||
values={"conventions": "RULES", "routine": "step one\nRULES\nstep two"},
|
||||
origins={
|
||||
"conventions": "included from team/conventions",
|
||||
"routine": "included from team/routine",
|
||||
},
|
||||
)
|
||||
assert cp.duplicate_inclusions(resolution) == ["conventions"]
|
||||
|
||||
|
||||
def test_supplied_values_are_not_flagged() -> None:
|
||||
"""Only inclusions are checked; a caller repeating text is their business."""
|
||||
resolution = cp.Resolution(
|
||||
values={"a": "same", "b": "same"},
|
||||
origins={"a": "supplied", "b": "supplied"},
|
||||
)
|
||||
assert cp.duplicate_inclusions(resolution) == []
|
||||
|
||||
|
||||
def test_empty_inclusion_is_not_flagged() -> None:
|
||||
resolution = cp.Resolution(
|
||||
values={"a": "", "b": "anything"},
|
||||
origins={"a": "included from x/empty", "b": "included from x/b"},
|
||||
)
|
||||
assert cp.duplicate_inclusions(resolution) == []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue