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
46 changes: 28 additions & 18 deletions .claude/skills/sync-template/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,28 @@ the template learned without erasing what this fork decided.
copying the file — and say so in the report.

2. **Find the work list.** Preferred: the release's sync spec
(`sync/SYNC-<version>.md` in the template repo), which classifies
every change as `verbatim` (byte-copy target), `contract` (port
the behavior, not the file), or `conditional` (applies only if a
stated predicate matches this repo). If no spec exists, the
drop's prompt supplies the list — check it against this tree
before executing; a step that does not fit this repo is a finding
to return, not an instruction to force.
(`sync/SYNC-<version>.md` in the template repo — format in
`sync/README.md`), which classifies every change as `verbatim`
(byte-copy target), `contract` (port the behavior, not the file),
or `conditional` (applies only if a stated predicate matches this
repo). Run each item's **detect** check first — already-satisfied
items are reported `already-present` with the evidence (a diff or
passing pins, not an assertion), never re-applied. If no spec
exists, the drop's prompt supplies the list — check it against
this tree before executing; a step that does not fit this repo is
a finding to return, not an instruction to force.

3. **Floors**: a dependency floor lives in several encodings —
requirements.txt, run.py's boot-floor tuple AND its message,
tests, CI asserts. Grep the current number and move every one.
The requirements line changing IS the Docker cache bust; extend
rationale ladders, never rewrite them, and never touch CHANGELOG
history. If this repo has no boot floor, add one — then break it
deliberately once to watch it refuse, and restore it.
3. **Floors**: the CURRENT floor is what `LLMS_PKG_FLOOR` says —
never what grepping the number finds, because the rationale
ladder retains old rungs BY DESIGN (a grep finds history and
calls it the present). When a floor MOVES, it moves in every
encoding at once — requirements.txt, the boot tuple AND its
message, tests, CI asserts; grep is how you FIND the encodings,
not how you read the floor. The requirements line changing IS
the Docker cache bust; extend rationale ladders, never rewrite
them, and never touch CHANGELOG history. If this repo has no
boot floor, add one — then break it deliberately once to watch
it refuse, and restore it.

4. **Apply**: verbatim items byte-copied; contract items ported
against this fork's shape with the template's test pins adapted;
Expand All @@ -41,7 +48,10 @@ the template learned without erasing what this fork decided.
healthz until `build == GITHUB_SHA`); then `/wire-verify` against
production. A sync is not done until the wire agrees.

6. **Report** via `/report`: include per-item disposition
(applied / ported-as-contract / not-applicable-because), any
DIVERGENCES.md updates, and anything the prompt got wrong about
this repo.
6. **Report** via `/report`: a per-item disposition table
(applied / ported-as-contract / already-present /
not-applicable-because — each with evidence), any DIVERGENCES.md
updates (retirements marked, not deleted, when older reports
still describe the divergence as live), and corrections to the
spec or prompt where it mismatched this tree — the spec is
subject to the same contract as any prompt.
98 changes: 98 additions & 0 deletions tests/test_claude_kit.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,36 @@ def _ignored(path: str) -> bool:
)


def _machine_fence(kind: str, text: str, where: str) -> None:
"""The shared pin for machine fences (```yaml sync-verbatim in specs,
```yaml byte-owned in DIVERGENCES.md): exactly one block, `- path`
lines with `#` comments, every path repo-relative and real at HEAD.
Empty is valid — an empty block is a statement, a missing one is an
omission."""
fences = re.findall(
r"^```yaml " + kind + r"[ \t]*\n(.*?)^```[ \t]*$", text, re.M | re.S
)
assert len(fences) == 1, (
f"{where}: expected exactly one ```yaml {kind} fence, "
f"found {len(fences)}"
)
for raw in fences[0].splitlines():
entry = raw.split("#", 1)[0].strip()
if not entry:
continue
assert entry.startswith("- "), (
f"{where} {kind}: {raw!r} is not a `- path` line"
)
path = entry[2:].strip()
assert ".." not in path and not path.startswith("/"), (
f"{where} {kind}: {path!r} escapes the repo"
)
assert (REPO / path).is_file(), (
f"{where} {kind}: {path!r} does not exist at HEAD "
"— the machine would act on nothing or the wrong thing"
)


def test_kit_files_exist_and_are_not_ignored():
"""The blanket `.claude/` ignore kept the contract local-only for the
template's whole life — every fork inherited nothing. The allow-list
Expand Down Expand Up @@ -111,3 +141,71 @@ def test_settings_point_at_this_forks_own_host():
assert f"WebFetch(domain:{host})" in allows, (
f"permissions.allow lacks WebFetch(domain:{host})"
)


def test_sync_specs_are_specifiable():
"""F2: every sync spec item must carry class/detect/acceptance — an
item without detect and acceptance is not specifiable (write a
kickoff instead and fix the item until it is; sync/README.md).

Skips where no sync/ exists: forks CONSUME specs, only the template
authors them — emojimart's F2 correction: this file is a byte-
verbatim kit port, and without the guard it failed on arrival at
every fork. The pin wakes up the day a fork starts authoring specs.

F3b: every spec also carries exactly one ```yaml sync-verbatim
fence — the machine block the fan-out workflow byte-copies from.
Every listed path must exist at HEAD and stay inside the repo; a
wrong entry becomes twelve wrong PRs.
"""
import pytest

sync_dir = REPO / "sync"
if not sync_dir.is_dir():
pytest.skip("no sync/ — this repo consumes specs, it does not author them")
assert (sync_dir / "README.md").is_file(), "sync/README.md (the format) missing"
specs = sorted(sync_dir.glob("SYNC-*.md"))
assert specs, "no sync specs — releases ship one (F2)"
for spec in specs:
text = spec.read_text()
blocks = re.split(r"^### ", text, flags=re.M)[1:]
assert blocks, f"{spec.name}: no items"
for block in blocks:
title = block.splitlines()[0]
for field in ("class:", "detect:", "acceptance:"):
assert field in block, (
f"{spec.name} item {title!r} lacks {field}"
)

_machine_fence("sync-verbatim", text, spec.name)


def test_divergences_carry_the_byte_owned_block():
"""F3b A1's finding: the fan-out honours DIVERGENCES.md by never
overwriting a byte-owned path, and a prose MENTION over-flags —
muicharts' host-pin nuance names tests/test_claude_kit.py while its
bytes are template-owned, a false positive recurring every release.
The fence is the machine answer; when present it is authoritative,
and empty means "the template owns every sync-verbatim path here".

ABSENCE SKIPS, never fails (1.6.22, the ops seat's own correction):
the machine tolerates a missing fence (the mention heuristic —
over-flags, never restores), so the pin must too. Failing here
would let one unported contract item keep every later mechanical
PR red, revoking the fan-out's "verbatim class = green merge"
promise indefinitely. CI guards what a fork HAS declared; the
spec's contract item and its session round drive adoption.
"""
import pytest

div = REPO / "DIVERGENCES.md"
if not div.is_file():
pytest.skip("no DIVERGENCES.md — nothing for the fan-out to honour")
text = div.read_text()
if not re.search(r"^```yaml byte-owned[ \t]*$", text, re.M):
pytest.skip(
"DIVERGENCES.md has no byte-owned fence — port "
"SYNC-1.6.17-1.6.21 item 1; until then the fan-out uses the "
"mention heuristic"
)
_machine_fence("byte-owned", text, "DIVERGENCES.md")
Loading