Skip to content
Open
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
18 changes: 17 additions & 1 deletion loopx/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,22 @@ def apply_onboarding_todos_to_state(
return "\n".join(lines) + "\n"


def render_objective_markdown(objective: str) -> str:
"""Render objective prose as an isolated Markdown blockquote.

Generated state grammar owns fences, comments, and headings. Raw
objective text is quoted line by line so a start-goal-collapsed or
unclosed fence, comment, or heading example inside the objective can
never open machine grammar and hide the generated Todo sections below.
The frontmatter ``objective`` field keeps the raw text; readers of the
body section strip the quote prefix to recover it.
"""
lines = str(objective or "").splitlines()
if not lines:
return ""
return "\n".join(f"> {line}" if line.strip() else ">" for line in lines)


def render_state_markdown(
*,
project: Path,
Expand Down Expand Up @@ -516,7 +532,7 @@ def render_state_markdown(

## Objective

{objective}
{render_objective_markdown(objective)}

## Authority Sources

Expand Down
15 changes: 2 additions & 13 deletions loopx/chat_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from .chat_ssh_source_api import SshSourceRequestMixin
from .chat_store import ChatSessionStore
from .capabilities.manager_runtime import manager_runtime_capability_projection
from .control_plane.goals.active_state_sections import active_state_section_text
from .control_plane.status.ssh_host_catalog import (
SSH_HOST_CATALOG_PATH,
ssh_host_catalog_payload,
Expand Down Expand Up @@ -125,19 +126,7 @@ def _compact_text(value: Any, *, limit: int = 600) -> str:


def _active_state_section(state_text: str, heading: str) -> str:
marker = f"## {heading}"
start = state_text.find(marker)
if start < 0:
return ""
content_start = start + len(marker)
end = state_text.find("\n## ", content_start)
section = state_text[content_start : end if end >= 0 else None]
lines = [
line.strip().removeprefix("- ").strip()
for line in section.splitlines()
if line.strip() and not line.lstrip().startswith("<!--")
]
return _compact_text(" ".join(lines))
return active_state_section_text(state_text, heading, normalize_text=_compact_text)


def _goal_public_context(registry: dict[str, Any], goal: dict[str, Any]) -> dict[str, Any]:
Expand Down
33 changes: 33 additions & 0 deletions loopx/control_plane/goals/active_state_sections.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,39 @@ def active_state_sections(
return sections


def active_state_section_text(
state_text: str,
heading: str,
*,
normalize_text: NormalizeText,
) -> str:
"""Read back one ``## <heading>`` section as flattened prose.

Counterpart of the quote-isolated objective writers: lines starting
with ``>`` lose their quote prefix so quote-isolated prose round-trips
verbatim, legacy lines keep the bullet-prefix flattening, and comment
markers, blank lines, and later ``## `` sections never become content.
"""
marker = f"## {heading}"
start = state_text.find(marker)
if start < 0:
return ""
content_start = start + len(marker)
end = state_text.find("\n## ", content_start)
section = state_text[content_start : end if end >= 0 else None]
lines = []
for line in section.splitlines():
stripped = line.strip()
if not stripped or stripped.startswith("<!--"):
continue
if stripped.startswith(">"):
stripped = stripped[1:].strip()
else:
stripped = stripped.removeprefix("- ").strip()
lines.append(stripped)
return normalize_text(" ".join(lines))


def active_state_section_entries(
lines: list[str],
*,
Expand Down
4 changes: 2 additions & 2 deletions loopx/control_plane/projects/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from pathlib import Path
from typing import Any

from ...bootstrap import build_goal_entry
from ...bootstrap import build_goal_entry, render_objective_markdown
from ...control_plane.runtime.time import now_local_iso
from ...file_lock import exclusive_cross_runtime_file_lock as exclusive_file_lock
from ...paths import resolve_runtime_root
Expand Down Expand Up @@ -142,7 +142,7 @@ def bullets(items: list[str], *, empty: str) -> str:

## Objective

{objective}
{render_objective_markdown(objective)}

## Acceptance

Expand Down
119 changes: 119 additions & 0 deletions tests/control_plane/test_objective_isolation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from __future__ import annotations

from pathlib import Path

import pytest

from loopx.bootstrap import render_objective_markdown, render_state_markdown
from loopx.chat_server import _active_state_section
from loopx.control_plane.projects.registry import _state_markdown
from loopx.control_plane.todos.active_state_todo_parser import parse_todo_source


BOUNDARY_OBJECTIVES = [
# A start-goal-collapsed closed fence becomes one opening-fence line.
"```text Implement and validate the task. ```",
"~~~text Implement and validate the task. ~~~",
# An unclosed comment example would hide every following section.
"<!-- unclosed objective example comment",
# An unfenced heading example must not become a Todo source region.
"## Agent Todo example",
# A task-shaped example must not be adopted as a Todo.
"- [ ] fake objective todo",
]

PLAIN_OBJECTIVE = "Implement and validate the task."
CLOSED_MULTILINE_FENCE_OBJECTIVE = "```text\nImplement and validate the task.\n```"


def _bootstrap_state(tmp_path: Path, objective: str) -> str:
return render_state_markdown(
project=tmp_path,
goal_id="objective-example",
adapter_kind="read_only_project_map_v0",
objective=objective,
updated_at="2026-09-15T00:00:00+00:00",
goal_doc=None,
execution_profile=None,
)


def test_render_objective_markdown_quotes_every_line() -> None:
assert render_objective_markdown("") == ""
assert render_objective_markdown(PLAIN_OBJECTIVE) == f"> {PLAIN_OBJECTIVE}"
assert (
render_objective_markdown("first\n\nsecond")
== "> first\n>\n> second"
)


@pytest.mark.parametrize("objective", BOUNDARY_OBJECTIVES)
def test_bootstrap_state_keeps_generated_todos_readable_beyond_objective_examples(
tmp_path: Path, objective: str
) -> None:
state = _bootstrap_state(tmp_path, objective)
items, _archive, sources = parse_todo_source(state)

assert sources["user"] == "User Todo / Owner Review Reading Queue"
assert sources["agent"] == "Agent Todo"
# The generated onboarding connection-validation Todo stays readable.
assert len(items["agent"]) == 1
assert "fake objective todo" not in str(items["agent"][0].get("text") or "")


@pytest.mark.parametrize(
"objective",
[PLAIN_OBJECTIVE, CLOSED_MULTILINE_FENCE_OBJECTIVE],
)
def test_bootstrap_state_keeps_plain_objective_controls_readable(
tmp_path: Path, objective: str
) -> None:
state = _bootstrap_state(tmp_path, objective)
items, _archive, sources = parse_todo_source(state)

assert sources["user"] is not None
assert sources["agent"] is not None
assert len(items["agent"]) == 1


def test_bootstrap_state_preserves_objective_text_in_frontmatter_and_body(
tmp_path: Path,
) -> None:
objective = BOUNDARY_OBJECTIVES[0]
state = _bootstrap_state(tmp_path, objective)

# The frontmatter keeps the raw objective for exact readback.
assert f'objective: "{objective}"' in state
# The body is quote-isolated: no unquoted machine-grammar line remains.
objective_lines = [f"> {line}" for line in objective.splitlines()]
assert "\n".join(objective_lines) in state


@pytest.mark.parametrize("objective", BOUNDARY_OBJECTIVES)
def test_chat_context_readback_strips_objective_quote_isolation(
tmp_path: Path, objective: str
) -> None:
state = _bootstrap_state(tmp_path, objective)

assert _active_state_section(state, "Objective") == objective


def test_registry_state_markdown_keeps_todo_sections_readable_beyond_fenced_objective() -> None:
state = _state_markdown(
project_id="project-example",
goal_id="goal-example",
objective="```text Implement the pipeline. ```",
non_goals=[],
acceptance=["Pipeline works."],
unknowns=[],
next_effect="Register the project.",
stop_condition="Pipeline accepted.",
updated_at="2026-09-15T00:00:00+00:00",
)
items, _archive, sources = parse_todo_source(state)

assert sources["user"] == "User Todo / Owner Review Reading Queue"
assert sources["agent"] == "Agent Todo"
assert items["user"] == []
assert items["agent"] == []
assert 'objective: "```text Implement the pipeline. ```"' in state