kn_r2.py python
103 lines 3.4 KB
Raw
sha256:0e9549ec7b463911bc08b7d586dc320b1ac9b1f5c943ee7e3865dcc6cb0f6f83 chore(governance): sync handover+roadmap to 84db8c8 (drift:… Human 9 hours ago
1 """Knowtation KN-R2 rule-fragment semantic parity (§K6.3.2)."""
2
3 from __future__ import annotations
4
5 import re
6 from pathlib import Path
7
8 from adapters.config import OverseerConfig
9 from adapters.templating import render_template
10 from cli.kit_root import kit_root
11
12 KN_R2_RULE_REL = "cursor/rules/no-docs-only-pr-to-main.mdc"
13 KN_R2_DEST = ".cursor/rules/no-docs-only-pr-to-main.mdc"
14
15
16 def render_kn_r2_rule(config: OverseerConfig, *, kit: Path | None = None) -> bytes:
17 """Render the kit no-docs-only rule with consumer tokens."""
18 root = kit or kit_root()
19 path = root / KN_R2_RULE_REL
20 return render_template(path, config).encode("utf-8")
21
22
23 def kn_r2_always_apply(text: str) -> bool:
24 """Return True when frontmatter sets ``alwaysApply: true``."""
25 match = re.search(r"(?mi)^alwaysApply:\s*true\s*$", text)
26 return match is not None
27
28
29 def kn_r2_forbids_docs_only_to_main(text: str, main_branch: str) -> bool:
30 """Return True when text forbids docs-only PR/merge to ``main_branch``."""
31 lowered = text.lower()
32 if "docs-only" not in lowered and "docs only" not in lowered:
33 return False
34 if main_branch.lower() not in lowered and "`main`" not in lowered and " main" not in lowered:
35 # Accept hardcoded main even when config branch differs for KN-R3.
36 if "main" not in lowered:
37 return False
38 forbid_markers = ("do **not**", "do not", "forbid", "never open", "never merge", "avoid")
39 return any(marker in lowered for marker in forbid_markers)
40
41
42 def kn_r2_allows_feature_branch(text: str) -> bool:
43 """Return True when text allows / encourages feature-branch commits."""
44 lowered = text.lower()
45 return "feature branch" in lowered or "feature-branch" in lowered
46
47
48 def kn_r2_semantic_parity(
49 consumer_text: str,
50 rendered_kit_text: str,
51 *,
52 main_branch: str,
53 ) -> bool:
54 """KN-R2: semantic parity between consumer rule and rendered kit rule.
55
56 Criteria (frozen): forbids docs-only PR/merge to main branch; allows
57 feature-branch commits; ``alwaysApply: true``. Byte-identity is not required.
58 """
59 for text in (consumer_text, rendered_kit_text):
60 if not kn_r2_always_apply(text):
61 return False
62 if not kn_r2_forbids_docs_only_to_main(text, main_branch):
63 return False
64 if not kn_r2_allows_feature_branch(text):
65 return False
66 return True
67
68
69 def evaluate_kn_r2(
70 repo_root: Path,
71 config: OverseerConfig,
72 *,
73 kit: Path | None = None,
74 ) -> tuple[bool, bytes, str | None]:
75 """Evaluate KN-R2 against on-disk consumer rule.
76
77 Returns ``(pass, rendered_kit_bytes, unified_diff_or_None)``.
78 When the destination is absent, returns ``(False, rendered, None)``.
79 """
80 import difflib
81
82 rendered = render_kn_r2_rule(config, kit=kit)
83 dest = repo_root / KN_R2_DEST
84 if not dest.is_file():
85 return False, rendered, None
86 consumer = dest.read_text(encoding="utf-8")
87 rendered_text = rendered.decode("utf-8")
88 passed = kn_r2_semantic_parity(
89 consumer,
90 rendered_text,
91 main_branch=config.vcs.git.main_branch,
92 )
93 if passed:
94 return True, rendered, None
95 diff = "".join(
96 difflib.unified_diff(
97 consumer.splitlines(keepends=True),
98 rendered_text.splitlines(keepends=True),
99 fromfile=f"a/{KN_R2_DEST}",
100 tofile=f"b/{KN_R2_DEST}",
101 )
102 )
103 return False, rendered, diff
File History 1 commit
sha256:6abcf1fa82a7a621ccbc945f19acdba5bc0db54569599404a1452fb4a096a199 fix(ISR): default require_independent_second_reviewer to require Human minor 8 hours ago