- K27 receipt presentation: full decision+evidence+effect packages, expandable sections, UNKNOWN-never-success in every dimension state, spine verdicts via evidence.Evaluate (read-only import) - 2.6-depth compaction: append-per-message (O(1) tail line, 198 bytes steady-state), compaction every 64 appends (512/channel hot), cool archives per memory-continuity contract, nothing deleted, exact-by-ID across compaction+restart+tamper, legacy checkpoints heal on load - K27 composer depth: 3-line input per old TUI contracts (§2.3 history walk, placeholder, stash/restore), deterministic natural-language resolution (40+ pinned phrases), palette natural-tier + ctrl+p bind, Accessibility profile (keyboard-only/no-color/reduced-motion) - K27 themes: 10 fixed Go theme tables (byte-exact from Textual 8.2.8, not guessed; nord/dracula-default/monokai/solarized/catppuccin/tokyo- night/flexoki/gruvbox), /theme command (mirrors _cmd_theme exactly), live-editor data shape (staged per-token preview overrides), base bg rgb(30,30,40), no-drift drift test anchoring Go to retiring Python - Sleight live wiring: operator._context_broker registers seventh contributor 'continuity' (bucket 2, fail-closed); K16 allowlist amended (memory_continuity.working_set); matrices now run against the live road; 102 tests green - .gitignore hygiene: directory-level traps fixed (internal/ re-includes .go/.json/.md only; root-anchored /index/ /journal/ /state/; fixtures + testdata re-included at any depth); 6-test frozen gate; unified_check member wired; real git add (no -f) proven on testmod/internal - Receipts, compaction, composer, themes, sleight-wiring, hygiene all under ADMIT contracts (101+ contracts, 0 research-gate violations) - main.go NOT landed (chat-live P1 wave owns it, still in flight)
166 lines
6.0 KiB
Python
166 lines
6.0 KiB
Python
#!/usr/bin/env python3
|
|
"""check_gitignore_hygiene.py — gitignore hygiene gate (wave-gitignore-hygiene).
|
|
|
|
RED when the .gitignore rules regress into the force-add era traps:
|
|
|
|
1. a probe source file under src/go/testmod/internal/ is ignored or
|
|
unaddable without -f (.go/.json/.md must be addable; nested
|
|
subpackage dirs too) — the trap behind the modegate binary
|
|
fabrication, the K9 sweep accident and the 102MB cache re-lands;
|
|
2. a probe binary under src/go/ IS addable (no binary intent may leak
|
|
through the re-include rules — binaries stay ignored at module
|
|
level AND inside subpackage dirs);
|
|
3. new files under the tracked source dirs memory/index/ and
|
|
memory/journal/ are swallowed by the cwd-local runtime dir rules
|
|
(index/ journal/ must be anchored to the repo root);
|
|
4. committed fixture stores (fixtures/, testdata/) lose addability
|
|
(pinned on real tracked files).
|
|
|
|
The gate builds a scratch module (src/go/testmod), probes it with real
|
|
git commands (check-ignore / status / add --dry-run), and removes the
|
|
scratch afterwards. Usage:
|
|
|
|
python scripts/check_gitignore_hygiene.py [--repo-root PATH]
|
|
Exit 0 = VERDICT: GREEN; 1 = RED with the violation list.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
SCRATCH = Path("src/go/testmod")
|
|
|
|
SCRATCH_FILES: dict[str, bytes] = {
|
|
"go.mod": b"module testmod\n\ngo 1.21\n",
|
|
"main.go": b"package main\n\nfunc main() {}\n",
|
|
# module-level compiled artifact: must stay ignored
|
|
"testmod-bin": b"\x7fELF-module-level-binary-probe",
|
|
"internal/probe.go": b"package internal\n",
|
|
"internal/probe.json": b"{}\n",
|
|
"internal/probe.md": b"# probe\n",
|
|
"internal/deep/nested.go": b"package deep\n",
|
|
# binary inside a subpackage dir: must stay ignored (no binary intent)
|
|
"internal/probe.bin": b"\x7fELF-internal-binary-probe",
|
|
# committed fixture store: must be addable at module level
|
|
"testdata/entry.txt": b"fixture input\n",
|
|
}
|
|
|
|
MUST_BE_ADDABLE = [
|
|
"src/go/testmod/go.mod",
|
|
"src/go/testmod/main.go",
|
|
"src/go/testmod/internal/probe.go",
|
|
"src/go/testmod/internal/probe.json",
|
|
"src/go/testmod/internal/probe.md",
|
|
"src/go/testmod/internal/deep/nested.go",
|
|
"src/go/testmod/testdata/entry.txt",
|
|
]
|
|
|
|
MUST_BE_IGNORED = [
|
|
"src/go/testmod/internal/probe.bin",
|
|
"src/go/testmod/testmod-bin",
|
|
]
|
|
|
|
# sweep pins: new files in these TRACKED source dirs must not be swallowed
|
|
# by the cwd-local runtime dir rules (index/ journal/).
|
|
MUST_BE_ADDABLE_HYPOTHETICAL = [
|
|
"memory/journal/__gitignore_probe__.jsonl",
|
|
"memory/index/__gitignore_probe__.json",
|
|
]
|
|
|
|
# fixture-store pins on real tracked files (fixtures/ + testdata/ stay
|
|
# fully addable, including non-source extensions like .jsonl/.txt).
|
|
MUST_BE_ADDABLE_TRACKED = [
|
|
"src/go/rune-entry/internal/entry/testdata/runtime/crash-replay/effects.jsonl",
|
|
"src/go/builder-bridge/fixtures/store_active/segments/observations.jsonl",
|
|
]
|
|
|
|
|
|
def git(root: Path, *args: str) -> subprocess.CompletedProcess:
|
|
return subprocess.run(["git", "-C", str(root), *args],
|
|
capture_output=True, text=True)
|
|
|
|
|
|
def make_scratch(root: Path) -> None:
|
|
for rel, content in SCRATCH_FILES.items():
|
|
p = root / SCRATCH / rel
|
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
p.write_bytes(content)
|
|
|
|
|
|
def remove_scratch(root: Path) -> None:
|
|
subprocess.run(["rm", "-rf", str(root / SCRATCH)], check=False)
|
|
|
|
|
|
def ignored(root: Path, path: str) -> bool:
|
|
"""True when gitignore rules match the path (exit 0 = ignored)."""
|
|
return git(root, "check-ignore", "-q", "--", path).returncode == 0
|
|
|
|
|
|
def sweep_add_lines(root: Path, target: str) -> list[str]:
|
|
"""What `git add -n <target>` would stage — the wave-realistic form
|
|
(directory adds / status sweeps are where the old trap bit)."""
|
|
r = git(root, "add", "-n", "--", target)
|
|
return [l.strip() for l in r.stdout.splitlines() if l.startswith("add ")]
|
|
|
|
|
|
def collect_violations(root: Path) -> list[str]:
|
|
violations: list[str] = []
|
|
try:
|
|
make_scratch(root)
|
|
|
|
for path in MUST_BE_ADDABLE:
|
|
if ignored(root, path):
|
|
violations.append(f"source not addable without -f: {path}")
|
|
|
|
for path in MUST_BE_IGNORED:
|
|
if not ignored(root, path):
|
|
violations.append(f"binary intent leaked (addable): {path}")
|
|
|
|
status = git(root, "status", "--porcelain", "--", str(SCRATCH))
|
|
if SCRATCH.as_posix() not in status.stdout:
|
|
violations.append(
|
|
f"new module {SCRATCH}/ invisible to git status (dir pruned "
|
|
"by ignore rules — sweeps silently skip whole modules)")
|
|
|
|
added = sweep_add_lines(root, str(SCRATCH))
|
|
for path in MUST_BE_ADDABLE:
|
|
if f"add '{path}'" not in added:
|
|
violations.append(f"directory-form add skips source: {path}")
|
|
for path in MUST_BE_IGNORED:
|
|
if f"add '{path}'" in added:
|
|
violations.append(f"directory-form add sweeps binary: {path}")
|
|
|
|
for path in MUST_BE_ADDABLE_HYPOTHETICAL + MUST_BE_ADDABLE_TRACKED:
|
|
if ignored(root, path):
|
|
violations.append(
|
|
f"tracked source dir swallowed by runtime rule: {path}")
|
|
finally:
|
|
remove_scratch(root)
|
|
return violations
|
|
|
|
|
|
def main(argv=None) -> int:
|
|
ap = argparse.ArgumentParser(description="gitignore hygiene gate")
|
|
ap.add_argument("--repo-root",
|
|
default=str(Path(__file__).resolve().parent.parent))
|
|
args = ap.parse_args(argv)
|
|
root = Path(args.repo_root).resolve()
|
|
|
|
violations = collect_violations(root)
|
|
if violations:
|
|
print("RED: gitignore hygiene violations:")
|
|
for v in violations:
|
|
print(f" - {v}")
|
|
print("VERDICT: RED")
|
|
return 1
|
|
print("GREEN: src/go sources addable without -f, binaries ignored, "
|
|
"memory/ source dirs + fixture stores unswallowed")
|
|
print("VERDICT: GREEN")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|