feat(fase1): dimension filter on bundle_context, default unchanged (F1)
This commit is contained in:
parent
a44256a994
commit
525ff0dda1
2 changed files with 54 additions and 2 deletions
|
|
@ -130,14 +130,23 @@ def navigate_bundle(bundle_dir: str) -> Bundle:
|
||||||
return Bundle(dir=bundle_dir, files=tuple(files))
|
return Bundle(dir=bundle_dir, files=tuple(files))
|
||||||
|
|
||||||
|
|
||||||
def bundle_context(bundle: Bundle) -> str:
|
def bundle_context(bundle: Bundle, *, dimension: str | None = None) -> str:
|
||||||
"""Render a navigated bundle as agent read-context via progressive disclosure: the ``index.md``
|
"""Render a navigated bundle as agent read-context via progressive disclosure: the ``index.md``
|
||||||
summary, then each concept file as ``## {type}: {title}\\n{body}``. ``type: verdict`` files are
|
summary, then each concept file as ``## {type}: {title}\\n{body}``. ``type: verdict`` files are
|
||||||
EXCLUDED (målbilde §2 step 1 / §4: navigation, not chunk-stuffing — the verdict layer folds in
|
EXCLUDED (målbilde §2 step 1 / §4: navigation, not chunk-stuffing — the verdict layer folds in
|
||||||
only via the gated ExpeL retrieval). Deterministic: index first, then context files in
|
only via the gated ExpeL retrieval). Deterministic: index first, then context files in
|
||||||
navigation order; empty sections are dropped."""
|
navigation order; empty sections are dropped.
|
||||||
|
|
||||||
|
When ``dimension`` is given, only concept files whose frontmatter ``dimension`` matches — or that
|
||||||
|
carry no ``dimension`` at all (un-scoped knowledge is never dropped) — are rendered; the default
|
||||||
|
``dimension=None`` renders every concept file, byte-identical to the prior behavior. ``dimension``
|
||||||
|
is a plain ``str`` (not the ``Dimension`` type) so ``okf`` stays MAF-free and import-cycle-free."""
|
||||||
sections = [bundle.index_summary]
|
sections = [bundle.index_summary]
|
||||||
for f in bundle.context_files:
|
for f in bundle.context_files:
|
||||||
|
if dimension is not None:
|
||||||
|
file_dim = f.frontmatter.get("dimension")
|
||||||
|
if file_dim is not None and file_dim != dimension:
|
||||||
|
continue
|
||||||
title = f.frontmatter.get("title", f.name).strip('"')
|
title = f.frontmatter.get("title", f.name).strip('"')
|
||||||
sections.append(f"## {f.type or 'document'}: {title}\n{f.body}")
|
sections.append(f"## {f.type or 'document'}: {title}\n{f.body}")
|
||||||
return "\n\n".join(s for s in sections if s.strip())
|
return "\n\n".join(s for s in sections if s.strip())
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,49 @@ def test_bundle_context_excludes_verdict_layer() -> None:
|
||||||
assert "progressiv disclosure" in context.lower() # the index summary is the entry point
|
assert "progressiv disclosure" in context.lower() # the index summary is the entry point
|
||||||
|
|
||||||
|
|
||||||
|
def _dimension_bundle(tmp_path) -> str:
|
||||||
|
(tmp_path / "index.md").write_text(
|
||||||
|
"---\ntype: index\n---\n\n# Bundle\n\n"
|
||||||
|
"- [energi](energi-method.md)\n"
|
||||||
|
"- [asfalt](asfalt-method.md)\n"
|
||||||
|
"- [shared](shared-note.md)\n"
|
||||||
|
"- [verdict](verdict-x.md)\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
(tmp_path / "energi-method.md").write_text(
|
||||||
|
"---\ntype: methodology\ndimension: energi\n---\n\nENERGI-SENTINEL body\n", encoding="utf-8"
|
||||||
|
)
|
||||||
|
(tmp_path / "asfalt-method.md").write_text(
|
||||||
|
"---\ntype: methodology\ndimension: asfalt\n---\n\nASFALT-SENTINEL body\n", encoding="utf-8"
|
||||||
|
)
|
||||||
|
(tmp_path / "shared-note.md").write_text(
|
||||||
|
"---\ntype: reference\n---\n\nSHARED-SENTINEL body\n", encoding="utf-8"
|
||||||
|
)
|
||||||
|
(tmp_path / "verdict-x.md").write_text(
|
||||||
|
"---\ntype: verdict\ndimension: energi\n---\n\nVERDICT-SENTINEL body\n", encoding="utf-8"
|
||||||
|
)
|
||||||
|
return str(tmp_path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bundle_context_dimension_filter(tmp_path) -> None:
|
||||||
|
"""SC7 forutsetning: with ``dimension="energi"`` only energi-marked + unmarked concept files
|
||||||
|
render; an asfalt-marked file is omitted. ``dimension=None`` is byte-identical to the no-arg
|
||||||
|
call (backward compat — protects the verdict-exclusion + step7/8 load-bearing tests).
|
||||||
|
``type: verdict`` stays excluded in every case."""
|
||||||
|
bundle = okf.navigate_bundle(_dimension_bundle(tmp_path))
|
||||||
|
|
||||||
|
scoped = okf.bundle_context(bundle, dimension="energi")
|
||||||
|
assert "ENERGI-SENTINEL" in scoped # energi-marked concept file rendered
|
||||||
|
assert "SHARED-SENTINEL" in scoped # unmarked knowledge is never dropped
|
||||||
|
assert "ASFALT-SENTINEL" not in scoped # other-dimension file filtered out
|
||||||
|
assert "VERDICT-SENTINEL" not in scoped # verdict layer still excluded
|
||||||
|
|
||||||
|
default = okf.bundle_context(bundle)
|
||||||
|
assert okf.bundle_context(bundle, dimension=None) == default # None == today, byte-identical
|
||||||
|
assert "ASFALT-SENTINEL" in default # no filter -> asfalt present
|
||||||
|
assert "VERDICT-SENTINEL" not in default # verdict still excluded
|
||||||
|
|
||||||
|
|
||||||
def test_navigate_tolerates_broken_links(tmp_path) -> None:
|
def test_navigate_tolerates_broken_links(tmp_path) -> None:
|
||||||
"""OKF SPEC §4: a consumer MUST tolerate broken links. An index linking a missing file
|
"""OKF SPEC §4: a consumer MUST tolerate broken links. An index linking a missing file
|
||||||
navigates without raising, simply omitting the absent target."""
|
navigates without raising, simply omitting the absent target."""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue