Files
computer-playbook/cli/build/docs/readme/schema.py
kevinveenbirkenbachandClaude Opus 5.5 3610e7b9ef fix(code-scanning): resolve the alerts that describe real code
Of the 76 open code scanning alerts, 39 point at code that can change.

svc-ai-mcp-adapter:
- A path argument that is empty or made only of dots is refused.
  Percent-encoding keeps a separator inside its segment, but "." and ".."
  need none, so an upstream that resolves dot segments could answer from a
  path the contract does not name.
- parse_duration splits on the units. The pattern it used before rescanned
  a long run of digits from every start position.

Tests and the README generators:
- Every Jinja environment built with autoescape=False and a "noqa: S701"
  uses select_autoescape now, as six test modules and
  utils/templating/jinja.py already did. It escapes templates named
  *.html, *.htm and *.xml, which none of these call sites loads.
- Dead code goes: a doubled "rel" block in the email lint, an unread
  _SKIP_DIRS, and a module that _resolve returned to no caller.
- Two "except ...: pass" blocks become contextlib.suppress and
  assertRaises.
- Nine test modules import unittest.mock one way instead of two, and one
  lookup test imports its module once.

Closes #807

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 09:18:00 +02:00

82 lines
2.7 KiB
Python

"""Derive the role-README section schema from the Jinja template.
``templates/roles/README.md.j2.tmpl`` is the single source of truth. The
required sections are the H2s the template emits with no optional context;
the optional sections are the extra H2s that appear once every
``{% if ... %}``-guarded variable is supplied. Rendering the template both
ways and diffing the H2 sets keeps the lint and the generator in lock-step
with the template.
"""
from __future__ import annotations
import functools
import jinja2
from cli.build.docs.readme.sections import h2_titles
from utils.cache.files import PROJECT_ROOT
TEMPLATE_DIR = PROJECT_ROOT / "templates" / "roles"
TEMPLATE_NAME = "README.md.j2.tmpl"
_REQUIRED_CTX: dict = {
"application_id": "web-app-example",
"application_name": "Example",
"application_url": "https://example.com/",
"application_description": "is an example application.",
"application_overview": "This role deploys the example application.",
"application_features": [{"name": "Example", "description": "An example feature."}],
"application_author": "Kevin Veen-Birkenbach",
"cosmos_mermaid": 'flowchart LR\n a["a"] --> b["b"]',
}
_OPTIONAL_CTX: dict = {
"application_invokable": True,
"cosmos_intro": "Example cosmos intro.",
"application_author_url": "https://www.veen.world",
"application_use_cases": "Example use case.",
"application_developer_notes": [{"file": "Administration.md", "description": "notes."}],
"application_further_resources": [{"label": "Example", "url": "https://example.com/"}],
}
def _render(ctx: dict) -> str:
env = jinja2.Environment(
loader=jinja2.FileSystemLoader(str(TEMPLATE_DIR)),
keep_trailing_newline=True,
autoescape=jinja2.select_autoescape(),
)
return env.get_template(TEMPLATE_NAME).render(**ctx)
def render_required() -> str:
return _render(dict(_REQUIRED_CTX))
def render_full() -> str:
return _render({**_REQUIRED_CTX, **_OPTIONAL_CTX})
@functools.lru_cache(maxsize=1)
def required_sections() -> tuple[str, ...]:
"""H2 titles the template emits without any optional context (mandatory)."""
return tuple(h2_titles(render_required()))
@functools.lru_cache(maxsize=1)
def canonical_order() -> tuple[str, ...]:
"""Full H2 order (required + optional) as the template lays them out."""
return tuple(h2_titles(render_full()))
@functools.lru_cache(maxsize=1)
def optional_sections() -> tuple[str, ...]:
required = set(required_sections())
return tuple(title for title in canonical_order() if title not in required)
def credits_heading() -> str:
"""The last required section (Credits by convention)."""
return required_sections()[-1]