git-repokit 0.9.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- git_repokit-0.9.10.dist-info/METADATA +422 -0
- git_repokit-0.9.10.dist-info/RECORD +174 -0
- git_repokit-0.9.10.dist-info/WHEEL +5 -0
- git_repokit-0.9.10.dist-info/entry_points.txt +2 -0
- git_repokit-0.9.10.dist-info/licenses/LICENSE +9 -0
- git_repokit-0.9.10.dist-info/top_level.txt +1 -0
- repokit/__init__.py +8 -0
- repokit/__main__.py +12 -0
- repokit/__version__.py +4 -0
- repokit/_vendor/__init__.py +0 -0
- repokit/_vendor/ailib/__init__.py +1 -0
- repokit/_vendor/ailib/analyzer.py +177 -0
- repokit/_vendor/ailib/backend.py +95 -0
- repokit/_vendor/ailib/cache.py +97 -0
- repokit/_vendor/ailib/parsers.py +52 -0
- repokit/_vendor/ailib/transports/__init__.py +16 -0
- repokit/_vendor/ailib/transports/cli.py +312 -0
- repokit/_vendor/ailib/transports/openai_compat.py +383 -0
- repokit/_vendor/ailib/transports/prompt_file.py +41 -0
- repokit/_vendor/ailib/types.py +224 -0
- repokit/auth_integration.py +244 -0
- repokit/badges.py +183 -0
- repokit/branch_utils.py +364 -0
- repokit/cli.py +2997 -0
- repokit/common_kit.py +202 -0
- repokit/config.py +686 -0
- repokit/defaults.py +220 -0
- repokit/directory_analyzer.py +1244 -0
- repokit/directory_profiles.py +423 -0
- repokit/git_config.py +370 -0
- repokit/github_create.py +142 -0
- repokit/github_setup.py +248 -0
- repokit/guardrails.py +707 -0
- repokit/history_cleaner.py +449 -0
- repokit/history_protection.py +434 -0
- repokit/hooks/pre-commit-guardrails +122 -0
- repokit/hooks/pre-commit-template +28 -0
- repokit/layers.py +510 -0
- repokit/licenses/0bsd.txt +14 -0
- repokit/licenses/LICENSES.md +27 -0
- repokit/licenses/__init__.py +142 -0
- repokit/licenses/agpl-3.0.txt +661 -0
- repokit/licenses/all-rights-reserved.txt +10 -0
- repokit/licenses/apache-2.0.txt +201 -0
- repokit/licenses/bsd-2-clause.txt +24 -0
- repokit/licenses/bsd-3-clause.txt +28 -0
- repokit/licenses/bsl-1.0.txt +23 -0
- repokit/licenses/cc-by-4.0.txt +395 -0
- repokit/licenses/cc-by-sa-4.0.txt +427 -0
- repokit/licenses/cc0-1.0.txt +121 -0
- repokit/licenses/epl-2.0.txt +277 -0
- repokit/licenses/gpl-2.0.txt +339 -0
- repokit/licenses/gpl-3.0.txt +674 -0
- repokit/licenses/isc.txt +15 -0
- repokit/licenses/lgpl-2.1.txt +504 -0
- repokit/licenses/lgpl-3.0.txt +165 -0
- repokit/licenses/mit.txt +21 -0
- repokit/licenses/mpl-2.0.txt +373 -0
- repokit/licenses/unlicense.txt +24 -0
- repokit/readme_blocks.py +112 -0
- repokit/remote_integration.py +627 -0
- repokit/repo_manager.py +2796 -0
- repokit/template_engine.py +391 -0
- repokit/templates/ai/claude/CLAUDE-nested.md.template +25 -0
- repokit/templates/ai/claude/CLAUDE.md.template +98 -0
- repokit/templates/ai/claude/instructions/step1_context_rebuilder.md +33 -0
- repokit/templates/ai/claude/instructions/step2_dev_workflow_process.md +67 -0
- repokit/templates/ai/claude/instructions/step3_context_bridge.md +52 -0
- repokit/templates/common/CHANGELOG.md.template +15 -0
- repokit/templates/common/CONTRIBUTING.md.template +31 -0
- repokit/templates/common/README.md.template +30 -0
- repokit/templates/common/gitattributes.template +17 -0
- repokit/templates/common/gitignore.template +141 -0
- repokit/templates/extras/FUNDING.yml.template +13 -0
- repokit/templates/extras/ROADMAP.md.template +17 -0
- repokit/templates/extras/dependabot.yml.template +22 -0
- repokit/templates/extras/one-offs-README.md.template +28 -0
- repokit/templates/extras/platform-support.md.template +11 -0
- repokit/templates/extras/pull_request_template.md.template +25 -0
- repokit/templates/extras/release-template.md.template +34 -0
- repokit/templates/extras/settings.json.template +11 -0
- repokit/templates/extras/stale.yml.template +14 -0
- repokit/templates/github/CODEOWNERS.template +11 -0
- repokit/templates/github/ISSUE_TEMPLATE/bug-report.md.template +29 -0
- repokit/templates/github/ISSUE_TEMPLATE/feature-request.md.template +19 -0
- repokit/templates/github/issues/notes.md.template +18 -0
- repokit/templates/github/issues/roadmap.md.template +15 -0
- repokit/templates/hooks/pre-commit.template +64 -0
- repokit/templates/layers/archetypes/any/claude-plugin/.claude-plugin/marketplace.json +20 -0
- repokit/templates/layers/archetypes/any/claude-plugin/plugin/.claude-plugin/plugin.json +15 -0
- repokit/templates/layers/archetypes/any/claude-plugin/plugin/README.md +28 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.github/workflows/ci-c-cpp.yml +24 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.repokit-common.toml.rkappend +4 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.vscode/launch.json +29 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.vscode/tasks.json +26 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/CMakeLists.txt +47 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/CMakePresets.json +23 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/cmake/GenerateStamp.cmake +45 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/src/main.cpp +27 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/tests/test_version.cpp +20 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/vcpkg.json +5 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/.vscode/launch.json +27 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/Makefile +10 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/build.bat +7 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/src/__RK_SNAKE__.c +26 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/.vscode/launch.json +17 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/.vscode/tasks.json +20 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/__RK_PROJECT__.sln +19 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/__RK_PROJECT__.vcxproj +41 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/src/main.cpp +26 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/.repokit-common.toml +28 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/.vscode/launch.json +34 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/hooks/hooks.json +15 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/hooks/scripts/session-start.mjs +23 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/tests/manifest.test.js +30 -0
- repokit/templates/layers/archetypes/js-ts/node/.repokit-common.toml.rkappend +6 -0
- repokit/templates/layers/archetypes/js-ts/node/package.json +13 -0
- repokit/templates/layers/archetypes/js-ts/node-package/.github/workflows/release.yml +39 -0
- repokit/templates/layers/archetypes/js-ts/node-package/package.json +32 -0
- repokit/templates/layers/archetypes/js-ts/node-package/src/index.ts +13 -0
- repokit/templates/layers/archetypes/js-ts/node-package/tests/index.test.ts +11 -0
- repokit/templates/layers/archetypes/js-ts/script/.vscode/launch.json +34 -0
- repokit/templates/layers/archetypes/js-ts/script/__RK_PROJECT__.js +37 -0
- repokit/templates/layers/archetypes/js-ts/script/tests/version.test.js +26 -0
- repokit/templates/layers/archetypes/js-ts/ts/package.json +18 -0
- repokit/templates/layers/archetypes/js-ts/ts/tsconfig.json +15 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.github/workflows/ci-python.yml +44 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.repokit-common.toml +21 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.vscode/launch.json +33 -0
- repokit/templates/layers/archetypes/python/claude-plugin/VERSION +1 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/hooks.json +15 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/scripts/run-hook.mjs +42 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/scripts/session_start.py +31 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/tests/test_manifest.py +42 -0
- repokit/templates/layers/archetypes/python/pypi/.github/workflows/release.yml +63 -0
- repokit/templates/layers/archetypes/python/pypi/.vscode/launch.json +43 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/__init__.py +5 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/__main__.py +6 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/_version.py +96 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/cli.py +18 -0
- repokit/templates/layers/archetypes/python/pypi/pyproject.toml +71 -0
- repokit/templates/layers/archetypes/python/pypi/tests/conftest.py +11 -0
- repokit/templates/layers/archetypes/python/pypi/tests/test_version.py +50 -0
- repokit/templates/layers/archetypes/python/script/.github/workflows/ci-python.yml +43 -0
- repokit/templates/layers/archetypes/python/script/.repokit-common.toml +14 -0
- repokit/templates/layers/archetypes/python/script/.vscode/launch.json +33 -0
- repokit/templates/layers/archetypes/python/script/VERSION +1 -0
- repokit/templates/layers/archetypes/python/script/__RK_PROJECT__.py +40 -0
- repokit/templates/layers/archetypes/python/script/tests/test_version.py +25 -0
- repokit/templates/layers/archetypes/python/setuppy/requirements.txt +8 -0
- repokit/templates/layers/archetypes/python/setuppy/setup.py +23 -0
- repokit/templates/layers/languages/c-cpp/.gitignore.rkappend +20 -0
- repokit/templates/layers/languages/c-cpp/.repokit-common.toml +7 -0
- repokit/templates/layers/languages/c-cpp/include/__RK_SNAKE__/version.h +20 -0
- repokit/templates/layers/languages/c-cpp/src/app.rc +25 -0
- repokit/templates/layers/languages/js-ts/.editorconfig +15 -0
- repokit/templates/layers/languages/js-ts/.github/workflows/ci-js-ts.yml +41 -0
- repokit/templates/layers/languages/js-ts/.gitignore.rkappend +13 -0
- repokit/templates/layers/languages/js-ts/.repokit-common.toml +19 -0
- repokit/templates/layers/languages/js-ts/.vscode/launch.json +24 -0
- repokit/templates/layers/languages/js-ts/VERSION +1 -0
- repokit/templates/layers/languages/python/.github/workflows/ci-python.yml +76 -0
- repokit/templates/layers/languages/python/.gitignore.rkappend +15 -0
- repokit/templates/layers/languages/python/.vscode/launch.json +33 -0
- repokit/templates/layers/manifest.toml +302 -0
- repokit/templates/vault/CLAUDE.md +29 -0
- repokit/templates/vault/claude/_maps/MOC-project.md +15 -0
- repokit/templates/vault/claude/_oracle/backlinks.md +3 -0
- repokit/templates/vault/claude/_oracle/concepts.md +12 -0
- repokit/templates/vault/claude/_oracle/manifest.md +10 -0
- repokit/templates/vault/claude/_todo/ACTIVE-WORK.md +18 -0
- repokit/templates/vault/claude/_todo/OPEN-LOOPS.md +14 -0
- repokit/utils.py +316 -0
- repokit/vault.py +248 -0
repokit/branch_utils.py
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Branch utilities for RepoKit.
|
|
4
|
+
|
|
5
|
+
Provides branch detection and validation for private content protection.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import subprocess
|
|
9
|
+
import os
|
|
10
|
+
from typing import Set, List, Dict, Optional, Tuple
|
|
11
|
+
|
|
12
|
+
# Lines RepoKit writes into .git/info/exclude for either branch type. Anything
|
|
13
|
+
# else in the file belongs to the user (or another tool) and is kept.
|
|
14
|
+
_REPOKIT_EXCLUDE_LINES = {
|
|
15
|
+
"CLAUDE.md", "private/claude/", "private/docs/", "private/notes/", "private/temp/",
|
|
16
|
+
"revisions/", "test-runs/", "test_runs/",
|
|
17
|
+
"# AI integration files", "# Development conversation logs",
|
|
18
|
+
"# Private development documentation", "# Development notes and temporary files",
|
|
19
|
+
"# Revision history (private branch only)", "# Test artifacts", "# Test artifacts (not needed in git)",
|
|
20
|
+
"# Private branches can contain all content",
|
|
21
|
+
"# These files are tracked in 'private' branch but ignored in public branches",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _write_excludes(exclude_file: str, excludes: List[str]) -> None:
|
|
26
|
+
"""Write RepoKit's branch excludes, keeping every line that is not RepoKit's.
|
|
27
|
+
|
|
28
|
+
The file used to be overwritten, which silently dropped the user's own
|
|
29
|
+
excludes (and git's template comments) each time branch excludes were set up.
|
|
30
|
+
"""
|
|
31
|
+
kept: List[str] = []
|
|
32
|
+
if os.path.exists(exclude_file):
|
|
33
|
+
with open(exclude_file, encoding="utf-8", errors="replace") as f:
|
|
34
|
+
for line in f.read().splitlines():
|
|
35
|
+
stripped = line.strip()
|
|
36
|
+
if stripped.startswith("# RepoKit:") or stripped in _REPOKIT_EXCLUDE_LINES:
|
|
37
|
+
continue
|
|
38
|
+
kept.append(line)
|
|
39
|
+
while kept and not kept[-1].strip():
|
|
40
|
+
kept.pop()
|
|
41
|
+
while kept and not kept[0].strip():
|
|
42
|
+
kept.pop(0)
|
|
43
|
+
with open(exclude_file, "w", encoding="utf-8") as f:
|
|
44
|
+
f.write("\n".join(kept + ([""] if kept else []) + excludes) + "\n")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class BranchContext:
|
|
48
|
+
"""Manages branch context and security rules with configurable patterns."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, repo_path: str = '.', config: Optional[Dict] = None):
|
|
51
|
+
"""
|
|
52
|
+
Initialize branch context for given repository.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
repo_path: Path to the repository
|
|
56
|
+
config: Optional configuration dictionary to override default patterns
|
|
57
|
+
"""
|
|
58
|
+
self.repo_path = os.path.abspath(repo_path)
|
|
59
|
+
self._current_branch = None
|
|
60
|
+
self._branch_type = None
|
|
61
|
+
|
|
62
|
+
# Load configuration or use defaults
|
|
63
|
+
config = config or {}
|
|
64
|
+
|
|
65
|
+
# Configure private branches (can contain private content)
|
|
66
|
+
self.PRIVATE_BRANCHES = set(config.get('private_branches', ['private', 'local']))
|
|
67
|
+
|
|
68
|
+
# Configure public branches (must not contain private content)
|
|
69
|
+
self.PUBLIC_BRANCHES = set(config.get('public_branches',
|
|
70
|
+
['main', 'master', 'dev', 'test', 'staging', 'live', 'prod', 'production']))
|
|
71
|
+
|
|
72
|
+
# Configure private content patterns
|
|
73
|
+
self.PRIVATE_PATTERNS = set(config.get('private_patterns', {
|
|
74
|
+
'CLAUDE.md',
|
|
75
|
+
'private/',
|
|
76
|
+
'convos/',
|
|
77
|
+
'logs/',
|
|
78
|
+
'credentials/',
|
|
79
|
+
'secrets/',
|
|
80
|
+
'.env.local',
|
|
81
|
+
'.env.private',
|
|
82
|
+
'**/__private__*',
|
|
83
|
+
'**/private_*',
|
|
84
|
+
}))
|
|
85
|
+
|
|
86
|
+
# Configure exclude patterns for public branches
|
|
87
|
+
self.EXCLUDE_FROM_PUBLIC = set(config.get('exclude_from_public', {
|
|
88
|
+
'CLAUDE.md',
|
|
89
|
+
'private/claude/',
|
|
90
|
+
'private/docs/',
|
|
91
|
+
'private/notes/',
|
|
92
|
+
'private/temp/',
|
|
93
|
+
'revisions/',
|
|
94
|
+
'test-runs/',
|
|
95
|
+
'test_runs/',
|
|
96
|
+
}))
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def current_branch(self) -> str:
|
|
100
|
+
"""Get current branch name."""
|
|
101
|
+
if self._current_branch is None:
|
|
102
|
+
self._current_branch = self._get_current_branch()
|
|
103
|
+
return self._current_branch
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def branch_type(self) -> str:
|
|
107
|
+
"""Get branch type: 'private', 'public', or 'unknown'."""
|
|
108
|
+
if self._branch_type is None:
|
|
109
|
+
self._branch_type = self._determine_branch_type()
|
|
110
|
+
return self._branch_type
|
|
111
|
+
|
|
112
|
+
def _get_current_branch(self) -> str:
|
|
113
|
+
"""Get current git branch name."""
|
|
114
|
+
try:
|
|
115
|
+
result = subprocess.run(
|
|
116
|
+
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
|
|
117
|
+
cwd=self.repo_path,
|
|
118
|
+
capture_output=True,
|
|
119
|
+
text=True,
|
|
120
|
+
check=True
|
|
121
|
+
)
|
|
122
|
+
return result.stdout.strip()
|
|
123
|
+
except subprocess.CalledProcessError:
|
|
124
|
+
return 'unknown'
|
|
125
|
+
|
|
126
|
+
def _determine_branch_type(self) -> str:
|
|
127
|
+
"""Determine if current branch is private, public, or unknown."""
|
|
128
|
+
branch = self.current_branch.lower()
|
|
129
|
+
|
|
130
|
+
# Check explicit private branches
|
|
131
|
+
if branch in self.PRIVATE_BRANCHES:
|
|
132
|
+
return 'private'
|
|
133
|
+
|
|
134
|
+
# Check explicit public branches
|
|
135
|
+
if branch in self.PUBLIC_BRANCHES:
|
|
136
|
+
return 'public'
|
|
137
|
+
|
|
138
|
+
# Check patterns
|
|
139
|
+
if branch.startswith('private/') or branch.startswith('local/'):
|
|
140
|
+
return 'private'
|
|
141
|
+
|
|
142
|
+
if branch.startswith('feature/') or branch.startswith('feat/'):
|
|
143
|
+
return 'private' # Feature branches can have private content
|
|
144
|
+
|
|
145
|
+
# Default to public for safety
|
|
146
|
+
return 'public'
|
|
147
|
+
|
|
148
|
+
def is_private_branch(self) -> bool:
|
|
149
|
+
"""Check if current branch allows private content."""
|
|
150
|
+
return self.branch_type == 'private'
|
|
151
|
+
|
|
152
|
+
def is_public_branch(self) -> bool:
|
|
153
|
+
"""Check if current branch must not have private content."""
|
|
154
|
+
return self.branch_type == 'public'
|
|
155
|
+
|
|
156
|
+
def get_staged_files(self) -> List[str]:
|
|
157
|
+
"""Get list of files staged for commit."""
|
|
158
|
+
try:
|
|
159
|
+
result = subprocess.run(
|
|
160
|
+
['git', 'diff', '--cached', '--name-only'],
|
|
161
|
+
cwd=self.repo_path,
|
|
162
|
+
capture_output=True,
|
|
163
|
+
text=True,
|
|
164
|
+
check=True
|
|
165
|
+
)
|
|
166
|
+
return [f.strip() for f in result.stdout.strip().split('\n') if f.strip()]
|
|
167
|
+
except subprocess.CalledProcessError:
|
|
168
|
+
return []
|
|
169
|
+
|
|
170
|
+
def check_private_files(self, files: List[str]) -> List[Tuple[str, str]]:
|
|
171
|
+
"""
|
|
172
|
+
Check if files contain private content.
|
|
173
|
+
|
|
174
|
+
Returns list of (file, reason) tuples for files that are private.
|
|
175
|
+
"""
|
|
176
|
+
private_files = []
|
|
177
|
+
|
|
178
|
+
for file in files:
|
|
179
|
+
# Check exact matches
|
|
180
|
+
if file in self.PRIVATE_PATTERNS:
|
|
181
|
+
private_files.append((file, "Exact match to private pattern"))
|
|
182
|
+
continue
|
|
183
|
+
|
|
184
|
+
# Check directory prefixes
|
|
185
|
+
for pattern in self.PRIVATE_PATTERNS:
|
|
186
|
+
if pattern.endswith('/') and file.startswith(pattern):
|
|
187
|
+
private_files.append((file, f"Inside private directory: {pattern}"))
|
|
188
|
+
break
|
|
189
|
+
|
|
190
|
+
# Check file patterns
|
|
191
|
+
if 'private' in file.lower() or '__private__' in file:
|
|
192
|
+
private_files.append((file, "Contains 'private' in path"))
|
|
193
|
+
|
|
194
|
+
# Check specific files that should be excluded
|
|
195
|
+
for exclude in self.EXCLUDE_FROM_PUBLIC:
|
|
196
|
+
if file == exclude or file.startswith(exclude):
|
|
197
|
+
private_files.append((file, f"Matches exclude pattern: {exclude}"))
|
|
198
|
+
break
|
|
199
|
+
|
|
200
|
+
return private_files
|
|
201
|
+
|
|
202
|
+
def validate_commit(self) -> Tuple[bool, List[str]]:
|
|
203
|
+
"""
|
|
204
|
+
Validate if current commit is safe for the branch.
|
|
205
|
+
|
|
206
|
+
Returns (is_valid, error_messages).
|
|
207
|
+
"""
|
|
208
|
+
# Check for override mechanisms
|
|
209
|
+
if self._check_override():
|
|
210
|
+
return True, []
|
|
211
|
+
|
|
212
|
+
if self.is_private_branch():
|
|
213
|
+
# Private branches can have any content
|
|
214
|
+
return True, []
|
|
215
|
+
|
|
216
|
+
# Public branch - check for private content
|
|
217
|
+
staged_files = self.get_staged_files()
|
|
218
|
+
private_files = self.check_private_files(staged_files)
|
|
219
|
+
|
|
220
|
+
if private_files:
|
|
221
|
+
errors = [
|
|
222
|
+
f"ERROR: Attempting to commit private files to public branch '{self.current_branch}':",
|
|
223
|
+
""
|
|
224
|
+
]
|
|
225
|
+
for file, reason in private_files:
|
|
226
|
+
errors.append(f" - {file}: {reason}")
|
|
227
|
+
|
|
228
|
+
errors.extend([
|
|
229
|
+
"",
|
|
230
|
+
"These files should only exist in private branches.",
|
|
231
|
+
"To fix this:",
|
|
232
|
+
" 1. Switch to private branch: git checkout private",
|
|
233
|
+
" 2. Or unstage these files: git reset HEAD <file>",
|
|
234
|
+
" 3. Or remove from working directory: rm <file>",
|
|
235
|
+
" 4. Or override with: REPOKIT_OVERRIDE=true git commit ...",
|
|
236
|
+
" 5. Or add '[override]' to commit message",
|
|
237
|
+
])
|
|
238
|
+
return False, errors
|
|
239
|
+
|
|
240
|
+
return True, []
|
|
241
|
+
|
|
242
|
+
def _check_override(self) -> bool:
|
|
243
|
+
"""Check if guardrails should be overridden."""
|
|
244
|
+
# Environment variable override
|
|
245
|
+
if os.environ.get('REPOKIT_OVERRIDE', '').lower() in ('true', '1', 'yes'):
|
|
246
|
+
print("🔓 REPOKIT_OVERRIDE detected - bypassing guardrails")
|
|
247
|
+
return True
|
|
248
|
+
|
|
249
|
+
# Check commit message for override flag
|
|
250
|
+
try:
|
|
251
|
+
result = subprocess.run(
|
|
252
|
+
['git', 'log', '--format=%B', '-n', '1'],
|
|
253
|
+
cwd=self.repo_path,
|
|
254
|
+
capture_output=True,
|
|
255
|
+
text=True,
|
|
256
|
+
check=False
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# Also check if we're in the middle of a commit (check COMMIT_EDITMSG)
|
|
260
|
+
commit_msg_file = os.path.join(self.repo_path, '.git', 'COMMIT_EDITMSG')
|
|
261
|
+
commit_message = ""
|
|
262
|
+
|
|
263
|
+
if os.path.exists(commit_msg_file):
|
|
264
|
+
with open(commit_msg_file, 'r') as f:
|
|
265
|
+
commit_message = f.read().strip()
|
|
266
|
+
elif result.returncode == 0:
|
|
267
|
+
commit_message = result.stdout.strip()
|
|
268
|
+
|
|
269
|
+
if '[override]' in commit_message.lower() or '[repokit-override]' in commit_message.lower():
|
|
270
|
+
print("🔓 Override flag detected in commit message - bypassing guardrails")
|
|
271
|
+
return True
|
|
272
|
+
|
|
273
|
+
except Exception:
|
|
274
|
+
pass # If we can't check commit message, continue with normal validation
|
|
275
|
+
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
def get_merge_excludes(self, source_branch: str, target_branch: str) -> Set[str]:
|
|
279
|
+
"""Get list of files to exclude when merging between branches."""
|
|
280
|
+
# If merging from private to public, exclude private files
|
|
281
|
+
if source_branch in self.PRIVATE_BRANCHES and target_branch in self.PUBLIC_BRANCHES:
|
|
282
|
+
return self.EXCLUDE_FROM_PUBLIC
|
|
283
|
+
|
|
284
|
+
return set()
|
|
285
|
+
|
|
286
|
+
def setup_branch_excludes(self) -> None:
|
|
287
|
+
"""Set up .git/info/exclude for current branch."""
|
|
288
|
+
exclude_file = os.path.join(self.repo_path, '.git', 'info', 'exclude')
|
|
289
|
+
|
|
290
|
+
if self.is_public_branch():
|
|
291
|
+
# Add private patterns to exclude file
|
|
292
|
+
excludes = [
|
|
293
|
+
"# RepoKit: Branch-conditional excludes for public branch",
|
|
294
|
+
"# These files are tracked in 'private' branch but ignored in public branches",
|
|
295
|
+
"",
|
|
296
|
+
"# AI integration files",
|
|
297
|
+
"CLAUDE.md",
|
|
298
|
+
"",
|
|
299
|
+
"# Development conversation logs",
|
|
300
|
+
"private/claude/",
|
|
301
|
+
"",
|
|
302
|
+
"# Private development documentation",
|
|
303
|
+
"private/docs/",
|
|
304
|
+
"",
|
|
305
|
+
"# Development notes and temporary files",
|
|
306
|
+
"private/notes/",
|
|
307
|
+
"private/temp/",
|
|
308
|
+
"",
|
|
309
|
+
"# Revision history (private branch only)",
|
|
310
|
+
"revisions/",
|
|
311
|
+
"",
|
|
312
|
+
"# Test artifacts",
|
|
313
|
+
"test-runs/",
|
|
314
|
+
"test_runs/",
|
|
315
|
+
]
|
|
316
|
+
|
|
317
|
+
_write_excludes(exclude_file, excludes)
|
|
318
|
+
|
|
319
|
+
print(f"✓ Set up branch excludes for public branch '{self.current_branch}'")
|
|
320
|
+
else:
|
|
321
|
+
# Private branch - minimal excludes
|
|
322
|
+
excludes = [
|
|
323
|
+
"# RepoKit: Minimal excludes for private branch",
|
|
324
|
+
"# Private branches can contain all content",
|
|
325
|
+
"",
|
|
326
|
+
"# Test artifacts (not needed in git)",
|
|
327
|
+
"test-runs/",
|
|
328
|
+
"test_runs/",
|
|
329
|
+
]
|
|
330
|
+
|
|
331
|
+
_write_excludes(exclude_file, excludes)
|
|
332
|
+
|
|
333
|
+
print(f"✓ Set up branch excludes for private branch '{self.current_branch}'")
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def check_branch_safety() -> bool:
|
|
337
|
+
"""Quick check if current operations are safe."""
|
|
338
|
+
context = BranchContext()
|
|
339
|
+
is_valid, errors = context.validate_commit()
|
|
340
|
+
|
|
341
|
+
if not is_valid:
|
|
342
|
+
for error in errors:
|
|
343
|
+
print(error)
|
|
344
|
+
return False
|
|
345
|
+
|
|
346
|
+
return True
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
if __name__ == "__main__":
|
|
350
|
+
# Quick test when run directly
|
|
351
|
+
context = BranchContext()
|
|
352
|
+
print(f"Current branch: {context.current_branch}")
|
|
353
|
+
print(f"Branch type: {context.branch_type}")
|
|
354
|
+
print(f"Is private: {context.is_private_branch()}")
|
|
355
|
+
print(f"Is public: {context.is_public_branch()}")
|
|
356
|
+
|
|
357
|
+
staged = context.get_staged_files()
|
|
358
|
+
if staged:
|
|
359
|
+
print(f"\nStaged files: {staged}")
|
|
360
|
+
private = context.check_private_files(staged)
|
|
361
|
+
if private:
|
|
362
|
+
print("\nPrivate files detected:")
|
|
363
|
+
for file, reason in private:
|
|
364
|
+
print(f" - {file}: {reason}")
|