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
2 changes: 1 addition & 1 deletion .github/workflows/self-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
if [ -f MIRRORS.md ]; then
cf-mirror-check check --repo .
else
echo "::notice::cf-mirror-check SKIPPED — no MIRRORS.md yet; the sticky-intro mirror of canon ADR 0029 must be declared before v0.1"
echo "::notice::cf-mirror-check SKIPPED — no MIRRORS.md yet; the sticky-intro mirror of canon ADR 0029 + ADR 0030 must be declared before v0.1"
fi

- name: cf-recursion-check — recursion declared in the kit's own source
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ whatever package the first agent downloaded.
short introduction to the law that the kit mounts into every consumer repo. Any
model that scans a repo ingests the law as it reads. Outside an enforcement
perimeter it acts as the strongest suggestion in the file; that is its job.
The copy here is a **declared mirror** of canon ADR 0029
(see `src/cf_quality/data/sticky-intro.SOURCE.md` for provenance and content hash).
The copy here is a **declared mirror** of canon ADR 0030 §9 (the v2 block,
which supersedes ADR 0029's v1 block per the ADR's own text; see
`src/cf_quality/data/sticky-intro.SOURCE.md` for provenance and content hash).
2. **The gate** (mechanical surface) — inside CandyFactory CI and inside Bonfire
burns, the law is executable and refusing. Prose teaches; it never substitutes
for the gate.
Expand Down
3 changes: 2 additions & 1 deletion src/cf_quality/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

The executable half of the BubbleGum Law (form is gated, and the law travels
sticky). Ships two surfaces: the canonical sticky-intro mount (a declared
mirror of canon ADR 0029) and the gate battery the factory's CI runs —
mirror of canon ADR 0030 §9, lineage from ADR 0029) and the gate battery
the factory's CI runs —
file budgets, sticky-intro presence, mirror declarations, recursion
declarations, and the exemptions ledger.

Expand Down
9 changes: 5 additions & 4 deletions src/cf_quality/data/sticky-intro.SOURCE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<!-- sticky-intro.md is a DECLARED MIRROR of candyfactory-canon ADR 0029 (the BubbleGum Law). -->
<!-- Parent: candyfactory-canon/decisions/0029-bubblegum-law.md — the fenced block in "The canonical sticky intro". -->
<!-- sticky-intro.md is a DECLARED MIRROR of candyfactory-canon ADR 0030 (BubbleGum v2 · the Seven Pillars). -->
<!-- Parent: candyfactory-canon/decisions/0030-bubblegum-v2-seven-pillars.md — the fenced block in "§9 · The canonical sticky intro v2 (the gum itself)". -->
<!-- Lineage: ADR 0030's block supersedes ADR 0029's v1 block per the ADR's own text; 0029's body keeps its copy, immutable, as history. -->
<!-- The canon copy is canonical; this copy is what the kit mounts into consumer repos. -->
<!-- Divergence from the parent is a decision, never silence: update the canon first, then re-extract here. -->
<!-- Extracted byte-faithful (fence lines excluded) on 2026-06-10. -->
<!-- sha256(sticky-intro.md) = 11e941b17dbbe3e2a09c2e20bfe2edfc3b7608b6af8bfcfec8f2cb2e136940db -->
<!-- Extracted byte-faithful (fence lines excluded) on 2026-06-11. -->
<!-- sha256(sticky-intro.md) = 7dc04712c33f98318563bb2a1b9e60c753b83e257ed5aa403c406911d7d45e07 -->
29 changes: 15 additions & 14 deletions src/cf_quality/data/sticky-intro.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
## The BubbleGum Law (form)

This repository is governed by **the BubbleGum Law** — CandyFactory's form law. It sticks:
if you are reading this to write code here, you are now carrying it.
**The BubbleGum Law** — CandyFactory's form law — governs this repository. It sticks:
read it and you carry it.

- **Budgets, not vibes:** functions stay ≤ CC 10 and ≤ 50 statements; new files stay ≤ 500
lines. Existing offenders are baselined and may only shrink (ratchet — never a world-refactor).
- **Reuse before reinvention:** search this repo before writing. In-repo duplication fails the
gate; a cross-repo copy must declare its mirror ("mirrors <repo>'s <artifact>").
- **Recursion is declared, not banned:** structurally-recursive data with a stated bound,
marked as such — or it fails.
- **New code type-checks clean,** and failure speaks typed errors (the Elegance Law).
- **Splitting a file is design, not evasion:** two incohesive 350-line halves satisfy nothing;
the gate measures, review judges.
- **SOLID:** one responsibility per module (new files ≤ 500 lines); extend via registries,
not dispatch chains; overrides substitute cleanly; interfaces stay narrow (≤ 5 methods,
no NotImplementedError stubs); dependencies point inward — core never imports
adapters/packs/tenants; each repo commits its import contract.
- **DRY:** search before writing; never paste what exists; cross-repo copies declare their mirror.
- **KISS:** functions ≤ CC 10, ≤ 50 statements; recursion declares a bound or fails.
- **The bench:** no dead code (YAGNI) · no import cycles (SoC) · compose, don't subclass ·
neighbors, not strangers (Demeter) · failure speaks typed errors (the Elegance Law).

In CandyFactory CI — and inside Bonfire burns — this law is a **gate**: comply by design, not
by suppression; any `# noqa` / `# nosec` carries a written reason and a human's blessing.
Everywhere else, treat it as the strongest suggestion in the file.
Budgets come from measurement; offenders baseline and only shrink — ratchet, never
world-refactor. In CandyFactory CI and Bonfire burns the measured core runs as gates,
each proven by a control rod; the rest is census and review. Comply by design: every
suppression carries a written reason and a registered blessing. Elsewhere, this law is
the strongest suggestion in the file.
3 changes: 2 additions & 1 deletion src/cf_quality/sticky_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
from cf_quality.errors import GateError, GateViolation

MIRROR_HEADER = (
"<!-- declared mirror of candyfactory-canon ADR 0029 · mounted by candyfactory-quality -->"
"<!-- declared mirror of candyfactory-canon ADR 0029 + ADR 0030"
" · mounted by candyfactory-quality -->"
)

_DATA_FILENAME = "sticky-intro.md"
Expand Down
13 changes: 8 additions & 5 deletions tests/test_sticky_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@
)

# The content hash declared in src/cf_quality/data/sticky-intro.SOURCE.md —
# the provenance anchor for the declared mirror of canon ADR 0029.
DECLARED_SHA256 = "11e941b17dbbe3e2a09c2e20bfe2edfc3b7608b6af8bfcfec8f2cb2e136940db"
# the provenance anchor for the declared mirror of canon ADR 0030 §9 (the v2
# block, which supersedes ADR 0029's v1 block per the ADR's own text).
DECLARED_SHA256 = "7dc04712c33f98318563bb2a1b9e60c753b83e257ed5aa403c406911d7d45e07"

KIT_ROOT = Path(__file__).resolve().parents[1]

Expand All @@ -47,7 +48,7 @@ def _repo_with(tmp_path: Path, claude_md: str | None) -> Path:

def _tampered_block() -> str:
"""Chewed gum: one budget number edited inside the canonical block."""
chewed = canonical_text().replace("≤ 500\n", "≤ 5000\n")
chewed = canonical_text().replace("≤ 500 lines", "≤ 5000 lines")
assert chewed != canonical_text(), "tamper fixture must actually differ"
return chewed

Expand Down Expand Up @@ -120,7 +121,8 @@ def test_check_fails_on_tampered_block_with_diff(tmp_path: Path) -> None:

def test_check_whitespace_edit_inside_block_is_tampering(tmp_path: Path) -> None:
# Byte-identical means byte-identical: a doubled space is chewed gum.
chewed = canonical_text().replace("Budgets, not vibes:", "Budgets, not vibes:")
chewed = canonical_text().replace("Budgets come from", "Budgets come from")
assert chewed != canonical_text(), "whitespace tamper fixture must actually differ"
repo = _repo_with(tmp_path, chewed)
violations = check(repo / "CLAUDE.md")
assert [v.code for v in violations] == ["STICKY_INTRO_TAMPERED"]
Expand Down Expand Up @@ -232,7 +234,8 @@ def test_mount_appends_block_with_declared_mirror_header(tmp_path: Path) -> None

def test_mount_header_is_the_ratified_one_liner() -> None:
assert MIRROR_HEADER == (
"<!-- declared mirror of candyfactory-canon ADR 0029 · mounted by candyfactory-quality -->"
"<!-- declared mirror of candyfactory-canon ADR 0029 + ADR 0030"
" · mounted by candyfactory-quality -->"
)
assert "\n" not in MIRROR_HEADER

Expand Down
Loading