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
16 changes: 16 additions & 0 deletions .console/log.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
# Log
## 2026-07-16 — feat: D3 P0-B — self-describing citation instruction in the injected cold block

The injected cold block now teaches the reading agent the citation protocol
itself, so NO per-consumer prompt changes are needed (the protocol travels with
the data). route.py `build_context` appends one closing instruction line
(`COLD_CITATION_NOTE`) to the cold section — `(cite: if you act on a [slug]
item above, add the git trailer "Context-Used: <slug>" to that commit)` — and
ONLY when at least one real slug-bearing cold line surfaced (gated on
`cold_slugs`, so a hypothetical note-only block gets no instruction; a
warm-only block never does). Rendered-only: appended to the block string after
telemetry assembly, so `cold_surfaced` and `cold_slugs` are byte-identical to
before, and the line does not start with `[` so `_cold_slug_from_line` returns
None for it. cold.py `surface_cold` stays pure. Two new tests (presence + last
line + telemetry unchanged; absence on warm-only). Full suite 434 pass; ruff
clean.

## 2026-07-16 — feat: D3 P1 fail-closed CI-status resolver (sha → tests_green)

New standalone module `context_engine/ci_status.py`: `resolve_ci_status(owner,
Expand Down
19 changes: 19 additions & 0 deletions src/context_lifecycle/context_engine/route.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@
# so all breadth tuning lives in one config surface alongside max_docs_per_edit.
COLD_SURFACE_CAP = 5

# One-line citation protocol note appended to the cold block (D3 attribution
# scheme A): the instruction travels WITH the injected data, so every consumer
# learns the ``Context-Used:`` trailer protocol without per-consumer prompt
# changes. Must NOT start with ``[`` (so `_cold_slug_from_line` returns None
# for it) and is appended only to the rendered block — never to `cold_lines` —
# so the `cold_surfaced` count and `cold_slugs` telemetry are unaffected.
COLD_CITATION_NOTE = (
"(cite: if you act on a [slug] item above, add the git trailer "
'"Context-Used: <slug>" to that commit)'
)


@dataclass(frozen=True)
class Route:
Expand Down Expand Up @@ -432,6 +443,14 @@ def build_context(target: str, root: Path) -> str:
cold_section = (
"\n\nRelated cold topics (pull on demand):\n" + "\n".join(cold_lines)
)
# Self-describing block (D3 P0-B): when at least one REAL cold item is
# injected (a slug-bearing line, not just the truncation note), close
# the block with the citation instruction so the acting agent knows the
# `Context-Used:` trailer protocol. Rendered-only: appended after the
# telemetry above was assembled, so it never counts as a surfaced line
# and never appears in cold_slugs.
if cold_slugs:
cold_section += "\n" + COLD_CITATION_NOTE

if not blocks:
# No injectable warm docs. Emit any diagnostics + cold section.
Expand Down
48 changes: 48 additions & 0 deletions tests/test_context_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,54 @@ def test_injection_event_omits_truncation_note_from_cold_slugs(tmp_path):
assert all("more cold topic" not in s for s in slugs)


def test_cold_block_is_self_describing(tmp_path):
# D3 P0-B: when real cold items surface, the block closes with ONE
# instruction line teaching the acting agent the `Context-Used:` trailer
# protocol — the protocol travels with the data, not per-consumer prompts.
import json as _json

ctx = tmp_path / ".context"
kn = ctx / "knowledge"
kn.mkdir(parents=True)
(kn / "projection.md").write_text(_COLD_ITEM)

block = route.build_context("src/x/thing.py", tmp_path)

# Exactly one instruction line, it names the trailer key, and it is the
# LAST line of the block (after every surfaced item line).
assert block.count(route.COLD_CITATION_NOTE) == 1
assert "Context-Used:" in route.COLD_CITATION_NOTE
assert block.splitlines()[-1] == route.COLD_CITATION_NOTE
assert "[projection]" in block # the item line it refers to is above it

# The instruction is rendered-only: not a slug line, not counted, not in
# the cold_slugs telemetry.
assert route._cold_slug_from_line(route.COLD_CITATION_NOTE) is None
log = ctx / "sessions" / ".telemetry" / "injection.jsonl"
event = _json.loads(log.read_text().splitlines()[-1])
assert event["cold_surfaced"] == 1
assert [rec["slug"] for rec in event["cold_slugs"]] == ["projection"]


def test_no_citation_instruction_without_cold_items(tmp_path):
# Warm-only injection (no cold items surfaced): the instruction line must
# NOT appear — it describes the cold block only.
ctx = tmp_path / ".context"
ctx.mkdir()
(ctx / "routes.yaml").write_text(
'engine_compat: ">=0.2 <0.3"\n'
'routes:\n - match: "src/loader.py"\n'
' inject: ["docs/inject/loader.md"]\n priority: 10\n'
)
doc = tmp_path / "docs" / "inject"
doc.mkdir(parents=True)
(doc / "loader.md").write_text("## Inject\n- Fail-closed.\n")

block = route.build_context("src/loader.py", tmp_path)
assert "Fail-closed." in block
assert "Context-Used" not in block


def test_telemetry_failure_never_breaks_router(tmp_path, monkeypatch):
ctx = tmp_path / ".context"
ctx.mkdir()
Expand Down
Loading