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
13 changes: 13 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ canonical Python server in a per-reader container. Its container adapter,
`worker/server.py`, is ordinary Python that `tests/` covers; its TypeScript half
is the one part of the tree with a gate of its own that `tests/` does not reach.

`docs/` is the site's own content: each product document there is a Leaf source,
which `scripts/site.py` publishes as a complete page directory beside the worked
examples. Changing what <https://leaf.page/> says is a page edit, not a template
edit.

`TODO.md` is the ordered priority list, and `notes/` holds what its items link
out to — the approaches considered and not taken, the sweeps of nearby projects,
the MCP App experiment record, and the research briefs behind a TODO section.
Only pre-commit's linters read `notes/`, and no test checks a note against the
tree, so a note is dated evidence for the decision it records rather than a
claim about the current tree; where the two disagree, the tree is right and the
note is history.

Read the scoped instructions for the area being changed:

- `skills/leaf/assets/CLAUDE.md`: browser runtime, widget modules, registry, and theme;
Expand Down
30 changes: 30 additions & 0 deletions tests/test_interact_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,36 @@ def test_the_root_instructions_name_every_directory_ci_gates_on_its_own():
assert not unnamed, f"unnamed in CLAUDE.md: {unnamed}"


def test_the_root_instructions_name_every_directory_of_the_projects_own_tree():
"""A top-level directory a session works in must be named where sessions read.

The dotted directories belong to the hosts and the tooling that read them, and
a session finds each through the host rather than through this map. The rest
are the project's own tree, and every one of them is somewhere a session is
sent to read or write. A session that lands in one the map never names has
only the files in front of it to say what the directory is for — which is how
`notes/` and `docs/` went unnamed while commits kept landing in both. The set
comes from the tracked tree rather than a list here, for the reason the
routing above states: a list is the second copy, and the directory added
without the paragraph would stay green.

The prefix is matched without its closing backtick, because a part is named at
whatever depth it is owned at — `bin/leaf` is a launcher and
`skills/leaf/assets/` is a tree, and both name their directory.
"""
instructions = (ROOT / "CLAUDE.md").read_text(encoding="utf-8")
tops = {
relative.parts[0]
for path in shipped_payload()
if len((relative := path.relative_to(PLUGIN_ROOT)).parts) > 1
}
directories = sorted(top for top in tops if not top.startswith("."))

assert directories, "no directories read — an empty set names itself"
unnamed = [d for d in directories if f"`{d}/" not in instructions]
assert not unnamed, f"unnamed in CLAUDE.md: {unnamed}"


def test_workflow_shell_continuations_use_literal_blocks():
"""A plain YAML scalar folds the newline before the shell sees the command."""
workflows = list((ROOT / ".github" / "workflows").glob("*.y*ml"))
Expand Down
Loading