gator-command 1.0.0__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.
- gator_command/__init__.py +2 -0
- gator_command/cli.py +137 -0
- gator_command/scripts/crawler.py +633 -0
- gator_command/scripts/dashboard/dashboard.css +982 -0
- gator_command/scripts/dashboard/dashboard.html +84 -0
- gator_command/scripts/dashboard/dashboard.js +419 -0
- gator_command/scripts/dashboard/views/audit.js +270 -0
- gator_command/scripts/dashboard/views/fleet.js +307 -0
- gator_command/scripts/dashboard/views/repo.js +599 -0
- gator_command/scripts/dashboard/views/settings.js +173 -0
- gator_command/scripts/dashboard/views/updates.js +308 -0
- gator_command/scripts/enforcer-prompt.md +22 -0
- gator_command/scripts/extract-claude-sessions.py +489 -0
- gator_command/scripts/extract-codex-sessions.py +477 -0
- gator_command/scripts/extract-gemini-sessions.py +410 -0
- gator_command/scripts/gator-audit.py +956 -0
- gator_command/scripts/gator-charter-draft.py +919 -0
- gator_command/scripts/gator-charter-lint.py +427 -0
- gator_command/scripts/gator-charter-verify.py +606 -0
- gator_command/scripts/gator-dashboard.py +1271 -0
- gator_command/scripts/gator-deploy.py +916 -0
- gator_command/scripts/gator-drift.py +569 -0
- gator_command/scripts/gator-enforce.py +82 -0
- gator_command/scripts/gator-fleet-intel.py +460 -0
- gator_command/scripts/gator-fleet-report.py +615 -0
- gator_command/scripts/gator-init-command-post.py +315 -0
- gator_command/scripts/gator-init.py +434 -0
- gator_command/scripts/gator-machine-id.py +153 -0
- gator_command/scripts/gator-policy-status.py +631 -0
- gator_command/scripts/gator-pulse.py +459 -0
- gator_command/scripts/gator-repo-status.py +649 -0
- gator_command/scripts/gator-session-common.py +372 -0
- gator_command/scripts/gator-session-sink.py +831 -0
- gator_command/scripts/gator-sessions.py +1244 -0
- gator_command/scripts/gator-update.py +615 -0
- gator_command/scripts/gator-version.py +38 -0
- gator_command/scripts/gator_core.py +489 -0
- gator_command/scripts/gator_remote.py +381 -0
- gator_command/scripts/gator_runtime.py +142 -0
- gator_command/scripts/gatorize-actions.sh +989 -0
- gator_command/scripts/gatorize-lib.sh +166 -0
- gator_command/scripts/gatorize-post.sh +394 -0
- gator_command/scripts/gatorize.py +1163 -0
- gator_command/scripts/gatorize.sh +185 -0
- gator_command/scripts/generate_markdown.py +212 -0
- gator_command/scripts/generate_wiki.py +424 -0
- gator_command/scripts/graph_health.py +780 -0
- gator_command/scripts/memex-lint.py +286 -0
- gator_command/scripts/memex-lint.sh +205 -0
- gator_command/scripts/memex.py +1472 -0
- gator_command/scripts/memex_formatters.py +191 -0
- gator_command/scripts/memex_state.py +236 -0
- gator_command/scripts/spawn.py +650 -0
- gator_command/templates/gator-starter/blueprints/README.md +32 -0
- gator_command/templates/gator-starter/charterignore +53 -0
- gator_command/templates/gator-starter/charters/README.md +178 -0
- gator_command/templates/gator-starter/charters/_template.md +31 -0
- gator_command/templates/gator-starter/commands/commit.md +33 -0
- gator_command/templates/gator-starter/commands/init.md +11 -0
- gator_command/templates/gator-starter/commands/update.md +5 -0
- gator_command/templates/gator-starter/constitution.md +165 -0
- gator_command/templates/gator-starter/field-guides/README.md +25 -0
- gator_command/templates/gator-starter/gator-start-up.md +119 -0
- gator_command/templates/gator-starter/procedures/charter-alignment.md +83 -0
- gator_command/templates/gator-starter/procedures/enforcer-review.md +317 -0
- gator_command/templates/gator-starter/procedures/field-guide-generation.md +176 -0
- gator_command/templates/gator-starter/procedures/knowledge-capture.md +57 -0
- gator_command/templates/gator-starter/procedures/significance-check.md +69 -0
- gator_command/templates/gator-starter/reference-notes/concierge-responses.md +535 -0
- gator_command/templates/gator-starter/reference-notes/dangerous-patterns.md +91 -0
- gator_command/templates/gator-starter/reference-notes/dashboard-operations.md +22 -0
- gator_command/templates/gator-starter/reference-notes/enforcer-configuration.md +232 -0
- gator_command/templates/gator-starter/reference-notes/example-project.md +289 -0
- gator_command/templates/gator-starter/reference-notes/failure-modes-and-self-correction.md +72 -0
- gator_command/templates/gator-starter/reference-notes/git-workflow.md +60 -0
- gator_command/templates/gator-starter/reference-notes/identity-and-ownership.md +37 -0
- gator_command/templates/gator-starter/reference-notes/refactor-approach.md +155 -0
- gator_command/templates/gator-starter/reference-notes/what-gator-requires-from-a-model.md +108 -0
- gator_command/templates/gator-starter/reference-notes/why-navigation-coding-feels-different.md +99 -0
- gator_command/templates/gator-starter/reference-notes/workflow-profiles.md +155 -0
- gator_command/templates/gator-starter/scripts/__pycache__/enforcer-review.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/__pycache__/gator-approve.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/__pycache__/gator-init.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/__pycache__/gator-pre-commit.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/__pycache__/gator-update.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/enforcer-prompt.md +55 -0
- gator_command/templates/gator-starter/scripts/enforcer-review.py +1551 -0
- gator_command/templates/gator-starter/scripts/gator-approve.py +139 -0
- gator_command/templates/gator-starter/scripts/gator-enforce.py +82 -0
- gator_command/templates/gator-starter/scripts/gator-init.py +434 -0
- gator_command/templates/gator-starter/scripts/gator-pre-commit.py +2670 -0
- gator_command/templates/gator-starter/scripts/gator-pulse.py +459 -0
- gator_command/templates/gator-starter/scripts/gator-update.py +615 -0
- gator_command/templates/gator-starter/scripts/gator-version.py +38 -0
- gator_command/templates/gator-starter/scripts/gator_core.py +487 -0
- gator_command/templates/gator-starter/scripts/hooks/__pycache__/commit-msgcpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/hooks/__pycache__/post-commitcpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/hooks/__pycache__/pre-commitcpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/hooks/commit-msg +5 -0
- gator_command/templates/gator-starter/scripts/hooks/post-commit +7 -0
- gator_command/templates/gator-starter/scripts/hooks/pre-commit +5 -0
- gator_command/templates/gator-starter/sessions/.gitignore +7 -0
- gator_command/templates/gator-starter/vault/.gitkeep +0 -0
- gator_command/templates/gator-starter/whiteboard.md +5 -0
- gator_command-1.0.0.dist-info/METADATA +122 -0
- gator_command-1.0.0.dist-info/RECORD +110 -0
- gator_command-1.0.0.dist-info/WHEEL +5 -0
- gator_command-1.0.0.dist-info/entry_points.txt +2 -0
- gator_command-1.0.0.dist-info/licenses/LICENSE +21 -0
- gator_command-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1163 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
gatorize.py — Install, upgrade, or morph Gator into a project repo.
|
|
4
|
+
|
|
5
|
+
Cross-platform Python implementation of gatorize.sh. Works on Windows
|
|
6
|
+
CMD/PowerShell, Git Bash, macOS Terminal, and Linux.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python gatorize.py <target-directory>
|
|
10
|
+
python gatorize.py .
|
|
11
|
+
|
|
12
|
+
Handles five scenarios:
|
|
13
|
+
1. Fresh directory (no git) → git init + install
|
|
14
|
+
2. Git repo, clean → install
|
|
15
|
+
3. Git repo, has .gator/ → run update
|
|
16
|
+
4. Git repo, has memex structure → morph memex → gator
|
|
17
|
+
5. Git repo, has both → warn, let user choose
|
|
18
|
+
|
|
19
|
+
@reads: templates in gator-starter/
|
|
20
|
+
@writes: target .gator/, entry points, hooks, registry
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import os
|
|
25
|
+
import shutil
|
|
26
|
+
import stat
|
|
27
|
+
import subprocess
|
|
28
|
+
import sys
|
|
29
|
+
from datetime import datetime
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
33
|
+
# Scripts are at src/gator_command/scripts/ — repo root is 3 levels up
|
|
34
|
+
COMMAND_POST = SCRIPTS_DIR.parent.parent.parent
|
|
35
|
+
|
|
36
|
+
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
37
|
+
from gator_core import find_command_post, ensure_utf8_stdout, import_sibling # noqa: E402
|
|
38
|
+
|
|
39
|
+
# ── constants ──────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
GATOR_BRANCH = "gator-install"
|
|
42
|
+
TODAY = datetime.now().strftime("%Y-%m-%d")
|
|
43
|
+
NOW = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
|
|
44
|
+
GATOR_MARKER = "# --- Gator Navigation Coding ---"
|
|
45
|
+
COMMAND_POST_MARKER = "# --- Gator Command Post ---"
|
|
46
|
+
|
|
47
|
+
# Detect template directory
|
|
48
|
+
TEMPLATES = None
|
|
49
|
+
for tpl_rel in ("templates/gator-starter", "../templates/gator-starter"):
|
|
50
|
+
candidate = (SCRIPTS_DIR.parent / tpl_rel).resolve()
|
|
51
|
+
if candidate.is_dir():
|
|
52
|
+
TEMPLATES = candidate
|
|
53
|
+
break
|
|
54
|
+
if not TEMPLATES:
|
|
55
|
+
# Try gator-engine layout
|
|
56
|
+
for tpl_rel in ("gator-engine/templates/gator-starter",
|
|
57
|
+
"gator-command/templates/gator-starter"):
|
|
58
|
+
candidate = (COMMAND_POST / tpl_rel).resolve()
|
|
59
|
+
if candidate.is_dir():
|
|
60
|
+
TEMPLATES = candidate
|
|
61
|
+
break
|
|
62
|
+
|
|
63
|
+
# Read generation from gator_core.py
|
|
64
|
+
GATOR_GEN = 2
|
|
65
|
+
try:
|
|
66
|
+
from gator_core import CURRENT_GENERATION
|
|
67
|
+
GATOR_GEN = CURRENT_GENERATION
|
|
68
|
+
except ImportError:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ── helpers ────────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
def git(*args, cwd=None):
|
|
75
|
+
"""Run a git command. Returns (stdout, success)."""
|
|
76
|
+
try:
|
|
77
|
+
result = subprocess.run(
|
|
78
|
+
["git"] + list(args),
|
|
79
|
+
capture_output=True, text=True, timeout=30,
|
|
80
|
+
cwd=str(cwd) if cwd else None,
|
|
81
|
+
)
|
|
82
|
+
return result.stdout.strip(), result.returncode == 0
|
|
83
|
+
except Exception as e:
|
|
84
|
+
return str(e), False
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def log_step(msg):
|
|
88
|
+
print(f" {msg}")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def prompt(question, options="", default=""):
|
|
92
|
+
"""Interactive prompt. Returns user input."""
|
|
93
|
+
suffix = f" [{options}]: " if options else ": "
|
|
94
|
+
try:
|
|
95
|
+
return input(f" {question}{suffix}").strip()
|
|
96
|
+
except (EOFError, KeyboardInterrupt):
|
|
97
|
+
return default
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def confirm(question, default="Y"):
|
|
101
|
+
"""Y/N confirmation."""
|
|
102
|
+
choice = prompt(question, "Y/n" if default == "Y" else "y/N")
|
|
103
|
+
if not choice:
|
|
104
|
+
return default == "Y"
|
|
105
|
+
return choice.lower().startswith("y")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def copy_tree_overlay(src, dest):
|
|
109
|
+
"""Copy files from src to dest, preserving extras in dest."""
|
|
110
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
111
|
+
for item in sorted(src.iterdir()):
|
|
112
|
+
if item.name in ("__pycache__",) or item.suffix == ".pyc":
|
|
113
|
+
continue
|
|
114
|
+
target = dest / item.name
|
|
115
|
+
if item.is_dir():
|
|
116
|
+
copy_tree_overlay(item, target)
|
|
117
|
+
else:
|
|
118
|
+
shutil.copy2(item, target)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ── detection ──────────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
def detect_scenario(target):
|
|
124
|
+
"""Returns scenario number 1-5."""
|
|
125
|
+
has_git = (target / ".git").is_dir()
|
|
126
|
+
has_gator = (target / ".gator").is_dir()
|
|
127
|
+
has_memex = (target / "memex").is_dir() or (target / ".memex").is_dir()
|
|
128
|
+
|
|
129
|
+
if not has_git and not has_memex and not has_gator:
|
|
130
|
+
return 1
|
|
131
|
+
if has_git and not has_memex and not has_gator:
|
|
132
|
+
return 2
|
|
133
|
+
if has_git and not has_memex and has_gator:
|
|
134
|
+
return 3
|
|
135
|
+
if has_git and has_memex and not has_gator:
|
|
136
|
+
return 4
|
|
137
|
+
if has_git and has_memex and has_gator:
|
|
138
|
+
return 5
|
|
139
|
+
# Edge: memex without git
|
|
140
|
+
print(" Error: memex structure found but no git. Initialize git first.")
|
|
141
|
+
sys.exit(1)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def detect_generation(target):
|
|
145
|
+
"""Returns generation number of existing .gator/."""
|
|
146
|
+
version_file = target / ".gator" / ".gator-version"
|
|
147
|
+
if version_file.exists():
|
|
148
|
+
for line in version_file.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
149
|
+
if line.startswith("generation:"):
|
|
150
|
+
try:
|
|
151
|
+
return int(line.split(":", 1)[1].strip())
|
|
152
|
+
except ValueError:
|
|
153
|
+
pass
|
|
154
|
+
return 2
|
|
155
|
+
if (target / ".gator" / "command-post.md").exists():
|
|
156
|
+
return 1
|
|
157
|
+
return 0
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# ── git operations ─────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
def action_git_init(target):
|
|
163
|
+
"""Scenario 1: initialize git in a fresh directory."""
|
|
164
|
+
print()
|
|
165
|
+
log_step(f"Initializing git in {target}...")
|
|
166
|
+
git("init", cwd=target)
|
|
167
|
+
# Ensure main branch
|
|
168
|
+
branch, _ = git("branch", "--show-current", cwd=target)
|
|
169
|
+
if branch != "main":
|
|
170
|
+
git("branch", "-M", "main", cwd=target)
|
|
171
|
+
# Initial commit
|
|
172
|
+
files = [f for f in target.iterdir() if f.name != ".git"]
|
|
173
|
+
if files:
|
|
174
|
+
git("add", "-A", cwd=target)
|
|
175
|
+
git("commit", "-m", "Initial commit (pre-Gator)", cwd=target)
|
|
176
|
+
else:
|
|
177
|
+
git("commit", "--allow-empty", "-m", "Initial commit (pre-Gator)", cwd=target)
|
|
178
|
+
git("branch", "dev", cwd=target)
|
|
179
|
+
git("checkout", "-b", GATOR_BRANCH, cwd=target)
|
|
180
|
+
log_step(f"On '{GATOR_BRANCH}' (git initialized).")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def action_feature_branch(target):
|
|
184
|
+
"""Create or switch to gator-install branch."""
|
|
185
|
+
# Check for uncommitted changes
|
|
186
|
+
status, _ = git("status", "--porcelain", cwd=target)
|
|
187
|
+
if status:
|
|
188
|
+
print()
|
|
189
|
+
print(f" Warning: uncommitted changes detected in {target}.")
|
|
190
|
+
print(" Strongly recommend committing or stashing before gatorizing.")
|
|
191
|
+
print()
|
|
192
|
+
choice = prompt("Continue anyway or abort?", "c/a")
|
|
193
|
+
if choice.lower() == "a":
|
|
194
|
+
print(" Aborted.")
|
|
195
|
+
sys.exit(0)
|
|
196
|
+
|
|
197
|
+
# Handle existing gator-install branch
|
|
198
|
+
_, has_branch = git("rev-parse", "--verify", GATOR_BRANCH, cwd=target)
|
|
199
|
+
if has_branch:
|
|
200
|
+
print()
|
|
201
|
+
print(f" Branch '{GATOR_BRANCH}' already exists (prior install attempt?).")
|
|
202
|
+
print()
|
|
203
|
+
print(" [1] Delete and start fresh (recommended)")
|
|
204
|
+
print(" [2] Continue on existing branch")
|
|
205
|
+
print(" [x] Cancel installation")
|
|
206
|
+
print()
|
|
207
|
+
choice = prompt("Choice", "1/2/x")
|
|
208
|
+
if choice.lower() == "x":
|
|
209
|
+
print(" Installation cancelled.")
|
|
210
|
+
sys.exit(0)
|
|
211
|
+
elif choice == "1":
|
|
212
|
+
git("checkout", "dev", cwd=target)
|
|
213
|
+
git("checkout", "main", cwd=target) # fallback
|
|
214
|
+
git("branch", "-D", GATOR_BRANCH, cwd=target)
|
|
215
|
+
log_step(f"Deleted old '{GATOR_BRANCH}' branch.")
|
|
216
|
+
|
|
217
|
+
# Get branches (excluding gator-install)
|
|
218
|
+
branch_out, _ = git("branch", cwd=target)
|
|
219
|
+
branches = []
|
|
220
|
+
current = ""
|
|
221
|
+
for line in (branch_out or "").splitlines():
|
|
222
|
+
is_current = line.startswith("*")
|
|
223
|
+
name = line.lstrip("* ").strip()
|
|
224
|
+
if name == GATOR_BRANCH:
|
|
225
|
+
continue
|
|
226
|
+
branches.append(name)
|
|
227
|
+
if is_current:
|
|
228
|
+
current = name
|
|
229
|
+
|
|
230
|
+
if not branches:
|
|
231
|
+
log_step("No branches found. Installing on current state.")
|
|
232
|
+
git("checkout", "-b", GATOR_BRANCH, cwd=target)
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
print()
|
|
236
|
+
print(f" Gator installs on a feature branch ('{GATOR_BRANCH}') for safety.")
|
|
237
|
+
print(" This keeps the install reversible — delete the branch if anything goes wrong,")
|
|
238
|
+
print(" merge to dev when you're happy.")
|
|
239
|
+
print()
|
|
240
|
+
|
|
241
|
+
source_branch = current
|
|
242
|
+
if len(branches) == 1:
|
|
243
|
+
source_branch = branches[0]
|
|
244
|
+
print(f" Only one branch: {source_branch}")
|
|
245
|
+
else:
|
|
246
|
+
print(" Available branches:")
|
|
247
|
+
print()
|
|
248
|
+
for i, b in enumerate(branches):
|
|
249
|
+
marker = "* " if b == current else " "
|
|
250
|
+
print(f" [{i + 1}] {marker}{b}")
|
|
251
|
+
print()
|
|
252
|
+
choice = prompt(f"Which branch should '{GATOR_BRANCH}' branch from?", f"1-{len(branches)}")
|
|
253
|
+
try:
|
|
254
|
+
idx = int(choice) - 1
|
|
255
|
+
if 0 <= idx < len(branches):
|
|
256
|
+
source_branch = branches[idx]
|
|
257
|
+
except (ValueError, IndexError):
|
|
258
|
+
pass
|
|
259
|
+
|
|
260
|
+
git("checkout", source_branch, cwd=target)
|
|
261
|
+
# Ensure dev exists — track origin/dev if available, else create from current
|
|
262
|
+
_, has_dev = git("rev-parse", "--verify", "dev", cwd=target)
|
|
263
|
+
if not has_dev:
|
|
264
|
+
_, has_remote_dev = git("rev-parse", "--verify", "origin/dev", cwd=target)
|
|
265
|
+
if has_remote_dev:
|
|
266
|
+
git("branch", "dev", "origin/dev", cwd=target)
|
|
267
|
+
log_step("Created 'dev' branch tracking origin/dev.")
|
|
268
|
+
else:
|
|
269
|
+
git("branch", "dev", cwd=target)
|
|
270
|
+
log_step("Created 'dev' branch.")
|
|
271
|
+
|
|
272
|
+
# Create or switch to gator-install
|
|
273
|
+
_, has_gi = git("rev-parse", "--verify", GATOR_BRANCH, cwd=target)
|
|
274
|
+
if has_gi:
|
|
275
|
+
git("checkout", GATOR_BRANCH, cwd=target)
|
|
276
|
+
else:
|
|
277
|
+
git("checkout", "-b", GATOR_BRANCH, cwd=target)
|
|
278
|
+
log_step(f"On '{GATOR_BRANCH}' (from '{source_branch}').")
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# ── install ────────────────────────────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
def action_install_gator(target):
|
|
284
|
+
"""Fresh install of .gator/ from templates."""
|
|
285
|
+
log_step(f"Installing .gator/ into {target}...")
|
|
286
|
+
|
|
287
|
+
gator_dir = target / ".gator"
|
|
288
|
+
|
|
289
|
+
# Create directory structure
|
|
290
|
+
for d in ["charters", "blueprints", "reference-notes", "procedures",
|
|
291
|
+
"scripts", "threads", "artifacts", "vault", "policies", "sessions"]:
|
|
292
|
+
(gator_dir / d).mkdir(parents=True, exist_ok=True)
|
|
293
|
+
|
|
294
|
+
# Copy templates
|
|
295
|
+
for name in ("constitution.md", "gator-start-up.md"):
|
|
296
|
+
src = TEMPLATES / name
|
|
297
|
+
if src.exists():
|
|
298
|
+
shutil.copy2(src, gator_dir / name)
|
|
299
|
+
|
|
300
|
+
# Charter scaffolding
|
|
301
|
+
for name in ("README.md", "_template.md"):
|
|
302
|
+
src = TEMPLATES / "charters" / name
|
|
303
|
+
if src.exists():
|
|
304
|
+
shutil.copy2(src, gator_dir / "charters" / name)
|
|
305
|
+
|
|
306
|
+
# Blueprint scaffolding
|
|
307
|
+
src = TEMPLATES / "blueprints" / "README.md"
|
|
308
|
+
if src.exists():
|
|
309
|
+
shutil.copy2(src, gator_dir / "blueprints" / "README.md")
|
|
310
|
+
|
|
311
|
+
# Overlay directories
|
|
312
|
+
for subdir in ("reference-notes", "procedures"):
|
|
313
|
+
src_dir = TEMPLATES / subdir
|
|
314
|
+
if src_dir.is_dir():
|
|
315
|
+
copy_tree_overlay(src_dir, gator_dir / subdir)
|
|
316
|
+
|
|
317
|
+
# Scripts (including subdirectories like hooks/)
|
|
318
|
+
scripts_src = TEMPLATES / "scripts"
|
|
319
|
+
if scripts_src.is_dir():
|
|
320
|
+
copy_tree_overlay(scripts_src, gator_dir / "scripts")
|
|
321
|
+
|
|
322
|
+
# Slash commands
|
|
323
|
+
commands_src = TEMPLATES / "commands"
|
|
324
|
+
if commands_src.is_dir():
|
|
325
|
+
claude_commands = target / ".claude" / "commands"
|
|
326
|
+
claude_commands.mkdir(parents=True, exist_ok=True)
|
|
327
|
+
copy_tree_overlay(commands_src, claude_commands)
|
|
328
|
+
|
|
329
|
+
# Install hooks
|
|
330
|
+
install_hooks(target)
|
|
331
|
+
|
|
332
|
+
# Write stubs
|
|
333
|
+
write_stubs(gator_dir)
|
|
334
|
+
|
|
335
|
+
# Write version
|
|
336
|
+
write_gator_version(gator_dir, "install")
|
|
337
|
+
|
|
338
|
+
log_step(".gator/ installed.")
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def install_hooks(target):
|
|
342
|
+
"""Install git hooks."""
|
|
343
|
+
hooks_dir = target / ".git" / "hooks"
|
|
344
|
+
hooks_dir.mkdir(parents=True, exist_ok=True)
|
|
345
|
+
hook_src = TEMPLATES / "scripts" / "hooks"
|
|
346
|
+
|
|
347
|
+
# Use the shared hook builder from gator-update.py — one definition of
|
|
348
|
+
# "correctly installed hooks" across gatorize, gator-update, and gator-init.
|
|
349
|
+
gator_update = import_sibling("gator-update")
|
|
350
|
+
hook_wrappers = gator_update.build_git_hook_wrappers()
|
|
351
|
+
|
|
352
|
+
for hook_name, hook_content in hook_wrappers.items():
|
|
353
|
+
dest = hooks_dir / hook_name
|
|
354
|
+
|
|
355
|
+
# Backup existing non-Gator hook
|
|
356
|
+
if dest.exists():
|
|
357
|
+
content = dest.read_text(encoding="utf-8", errors="replace")
|
|
358
|
+
if "Gator" not in content and "gator" not in content:
|
|
359
|
+
backup = dest.with_suffix(".pre-gator")
|
|
360
|
+
shutil.copy2(dest, backup)
|
|
361
|
+
|
|
362
|
+
dest.write_text(hook_content, encoding="utf-8")
|
|
363
|
+
# Make executable on Unix
|
|
364
|
+
if os.name != "nt":
|
|
365
|
+
dest.chmod(dest.stat().st_mode | stat.S_IEXEC)
|
|
366
|
+
|
|
367
|
+
log_step("Git hooks installed.")
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def write_stubs(gator_dir):
|
|
371
|
+
"""Write stub content files (only if missing)."""
|
|
372
|
+
stubs = {
|
|
373
|
+
"mission.md": "# Mission\n\n[What are we building and why?]\n",
|
|
374
|
+
"roadmap.md": "# Roadmap\n\n[Priority-ordered. Updated as items complete.]\n\n"
|
|
375
|
+
"**Status key**: Done · Building · Designed · Considering · Deferred\n",
|
|
376
|
+
"inbox.md": "# Inbox\n\nDrop anything here. No formatting needed.\n\n---\n\n",
|
|
377
|
+
"identity.md": "---\noperating-mode: designer\n---\n\n# Identity\n\n"
|
|
378
|
+
"## Basics\n\n- **Name**: [Your name]\n- **Location**: [Your timezone]\n"
|
|
379
|
+
"- **Role**: [Your role]\n",
|
|
380
|
+
"issues.md": "# Issues\n\nActive bugs, blockers, and known fragilities.\n\n"
|
|
381
|
+
"**Status key**: Open · Working · Resolved\n\n---\n\n",
|
|
382
|
+
"commit_draft.md": "---\nmessage: \"\"\nchange-type:\nsignificance:\n"
|
|
383
|
+
"decision-tags: []\nagent:\narchitect:\n---\n\n"
|
|
384
|
+
"# Session Change Log\n\n",
|
|
385
|
+
"patterns.md": "# Patterns\n\nRecurring rhythms, obligations, schedules.\n\n---\n\n",
|
|
386
|
+
"whiteboard.md": "# Whiteboard\n\nNo findings.\n",
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
for name, content in stubs.items():
|
|
390
|
+
path = gator_dir / name
|
|
391
|
+
if not path.exists():
|
|
392
|
+
path.write_text(content, encoding="utf-8")
|
|
393
|
+
|
|
394
|
+
# JSON stubs
|
|
395
|
+
lint_allow = gator_dir / "lint-allow.json"
|
|
396
|
+
if not lint_allow.exists():
|
|
397
|
+
lint_allow.write_text("[]", encoding="utf-8")
|
|
398
|
+
|
|
399
|
+
commit_issues = gator_dir / "commit_issues.md"
|
|
400
|
+
if not commit_issues.exists():
|
|
401
|
+
commit_issues.write_text("# Commit Issues\n\nNo findings.\n", encoding="utf-8")
|
|
402
|
+
|
|
403
|
+
config = gator_dir / "config.json"
|
|
404
|
+
if not config.exists():
|
|
405
|
+
config.write_text('{\n "enforcement_level": "strict"\n}\n', encoding="utf-8")
|
|
406
|
+
|
|
407
|
+
# Charter INDEX
|
|
408
|
+
index = gator_dir / "charters" / "INDEX.md"
|
|
409
|
+
if not index.exists():
|
|
410
|
+
index.write_text(
|
|
411
|
+
"# Charter Index\n\n**Always read first:** [Cross-Cutting](cross-cutting.md) (when it exists)\n\n"
|
|
412
|
+
"| If you're changing... | Read these charters |\n|---|---|\n| | |\n",
|
|
413
|
+
encoding="utf-8",
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
# Sessions .gitignore
|
|
417
|
+
sessions_gi = gator_dir / "sessions" / ".gitignore"
|
|
418
|
+
sessions_gi.parent.mkdir(exist_ok=True)
|
|
419
|
+
if not sessions_gi.exists():
|
|
420
|
+
sessions_gi.write_text("_active/\n", encoding="utf-8")
|
|
421
|
+
|
|
422
|
+
# Vault — gitignored directory for sensitive material and large files
|
|
423
|
+
vault_dir = gator_dir / "vault"
|
|
424
|
+
vault_dir.mkdir(exist_ok=True)
|
|
425
|
+
vault_gitkeep = vault_dir / ".gitkeep"
|
|
426
|
+
if not vault_gitkeep.exists():
|
|
427
|
+
vault_gitkeep.write_text("", encoding="utf-8")
|
|
428
|
+
ensure_repo_gitignore(gator_dir.parent)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def ensure_repo_gitignore(repo_root):
|
|
432
|
+
"""Ensure standard gitignore rules exist in the repo's .gitignore.
|
|
433
|
+
|
|
434
|
+
Called on both fresh install and upgrade paths so all governed repos
|
|
435
|
+
converge on the same set of ignore rules.
|
|
436
|
+
"""
|
|
437
|
+
repo_gi = repo_root / ".gitignore"
|
|
438
|
+
gi_rules = {
|
|
439
|
+
".gator/vault/": "# Vault — sensitive material and large files",
|
|
440
|
+
".vscode/": "# IDE settings",
|
|
441
|
+
"__pycache__/": "# Python cache",
|
|
442
|
+
}
|
|
443
|
+
if repo_gi.exists():
|
|
444
|
+
gi_text = repo_gi.read_text(encoding="utf-8", errors="replace")
|
|
445
|
+
else:
|
|
446
|
+
gi_text = ""
|
|
447
|
+
additions = []
|
|
448
|
+
for rule, comment in gi_rules.items():
|
|
449
|
+
if rule not in gi_text:
|
|
450
|
+
additions.append(f"{comment}\n{rule}")
|
|
451
|
+
if additions:
|
|
452
|
+
with open(repo_gi, "a" if gi_text else "w", encoding="utf-8") as f:
|
|
453
|
+
f.write("\n" + "\n".join(additions) + "\n")
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def write_gator_version(gator_dir, action):
|
|
457
|
+
"""Write .gator-version with generation and timestamps."""
|
|
458
|
+
version_file = gator_dir / ".gator-version"
|
|
459
|
+
|
|
460
|
+
# Preserve install date on upgrade
|
|
461
|
+
installed = TODAY
|
|
462
|
+
if version_file.exists():
|
|
463
|
+
for line in version_file.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
464
|
+
if line.startswith("installed:"):
|
|
465
|
+
prev = line.split(":", 1)[1].strip()
|
|
466
|
+
if prev:
|
|
467
|
+
installed = prev
|
|
468
|
+
|
|
469
|
+
version_file.write_text(
|
|
470
|
+
f"generation: {GATOR_GEN}\n"
|
|
471
|
+
f"installed: {installed}\n"
|
|
472
|
+
f"updated: {NOW}\n"
|
|
473
|
+
f"action: {action}\n"
|
|
474
|
+
f"installer: gatorize.py\n",
|
|
475
|
+
encoding="utf-8",
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
# ── entry points ───────────────────────────────────────────────────────────
|
|
480
|
+
|
|
481
|
+
GATOR_BEGIN = "<!-- GATOR:BEGIN -->"
|
|
482
|
+
GATOR_END = "<!-- GATOR:END -->"
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def render_entry_content(has_command_post, agent_type="claude"):
|
|
486
|
+
"""Render the canonical Gator-managed block for an entry point."""
|
|
487
|
+
gator_init_block = (
|
|
488
|
+
'**"gator init" means**: run `python .gator/scripts/gator-init.py` '
|
|
489
|
+
"and display the output exactly as printed. It is NOT a repo bootstrap "
|
|
490
|
+
"or git init — it is the standardized Gator boot sequence. Run the script, "
|
|
491
|
+
"show the output, then proceed with session opening.\n\n"
|
|
492
|
+
'**"gator pulse"**: run `python .gator/scripts/gator-pulse.py` to generate '
|
|
493
|
+
"`.gator/pulse.md` — a strategic operations brief with next steps, project "
|
|
494
|
+
"assessment, roadmap check, priorities, and recent activity.\n\n"
|
|
495
|
+
"**Project assessment**: when the Architect asks for a project assessment, "
|
|
496
|
+
"write a 2-paragraph expert evaluation to "
|
|
497
|
+
"`.gator/artifacts/YYYY-MM-DD-project-assessment.md` with YAML frontmatter "
|
|
498
|
+
"(`date`, `type: project-assessment`, `model: <your model name>`). "
|
|
499
|
+
"Write as an expert consultant advising an engineering manager. "
|
|
500
|
+
"The next `gator pulse` will include it automatically."
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
constitution_line = "Read [`.gator/constitution.md`](.gator/constitution.md) before your first response. It governs how you work here."
|
|
504
|
+
bootstrap_line = "If this is a **fresh project** (charters/ is empty or contains only templates), follow the bootstrap procedure in [`.gator/gator-start-up.md`](.gator/gator-start-up.md)."
|
|
505
|
+
|
|
506
|
+
cp_section = ""
|
|
507
|
+
if has_command_post:
|
|
508
|
+
cp_section = (
|
|
509
|
+
f"\n\n{COMMAND_POST_MARKER}\n"
|
|
510
|
+
"This repo is governed by a Gator Command post. "
|
|
511
|
+
"Read [`.gator/command-post.md`](.gator/command-post.md) for the command post location. "
|
|
512
|
+
"Read org standards from there at session open. "
|
|
513
|
+
"**Never write to the command post from this session.** "
|
|
514
|
+
"Cross-repo discoveries go in [`outbox.md`](outbox.md)."
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
content = f"{gator_init_block}\n\n{constitution_line}\n\n{bootstrap_line}"
|
|
518
|
+
|
|
519
|
+
if agent_type == "agents":
|
|
520
|
+
content += (
|
|
521
|
+
"\n\nIf the PI asks for an **enforcer review**, do not repurpose yourself as the enforcer. "
|
|
522
|
+
"Use the dedicated enforcer prompt at [`.gator/scripts/enforcer-prompt.md`](.gator/scripts/enforcer-prompt.md)."
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
content += cp_section
|
|
526
|
+
return content
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def action_install_entry_points(target, has_command_post):
|
|
530
|
+
"""Install or refresh CLAUDE.md, AGENTS.md, GEMINI.md.
|
|
531
|
+
|
|
532
|
+
Three cases:
|
|
533
|
+
- File missing: create with sentinels
|
|
534
|
+
- Non-Gator file (no markers): interactive prompt (backup/append/overwrite)
|
|
535
|
+
- Gator-managed file (has markers): refresh managed block in place
|
|
536
|
+
"""
|
|
537
|
+
entries = [
|
|
538
|
+
("CLAUDE.md", "# Claude Code Entry Point", "claude", "CLAUDE_ROLLBACK.md"),
|
|
539
|
+
("AGENTS.md", "# Codex Entry Point", "agents", "AGENTS_ROLLBACK.md"),
|
|
540
|
+
("GEMINI.md", "# Gemini Entry Point", "gemini", "GEMINI_ROLLBACK.md"),
|
|
541
|
+
]
|
|
542
|
+
|
|
543
|
+
idx = 0
|
|
544
|
+
while idx < len(entries):
|
|
545
|
+
filename, header, agent_type, rollback_name = entries[idx]
|
|
546
|
+
filepath = target / filename
|
|
547
|
+
content = render_entry_content(has_command_post, agent_type)
|
|
548
|
+
managed_block = f"{GATOR_BEGIN}\n{content}\n{GATOR_END}"
|
|
549
|
+
|
|
550
|
+
if filepath.exists():
|
|
551
|
+
existing = filepath.read_text(encoding="utf-8", errors="replace")
|
|
552
|
+
|
|
553
|
+
# Case: Gator-managed file — refresh the managed block
|
|
554
|
+
if GATOR_BEGIN in existing and GATOR_END in existing:
|
|
555
|
+
# Replace between sentinels
|
|
556
|
+
before = existing[:existing.index(GATOR_BEGIN)]
|
|
557
|
+
after = existing[existing.index(GATOR_END) + len(GATOR_END):]
|
|
558
|
+
filepath.write_text(
|
|
559
|
+
f"{before}{managed_block}{after}",
|
|
560
|
+
encoding="utf-8",
|
|
561
|
+
)
|
|
562
|
+
log_step(f"{filename} — Gator block refreshed")
|
|
563
|
+
idx += 1
|
|
564
|
+
continue
|
|
565
|
+
|
|
566
|
+
# Case: has old Gator marker OR recognizable Gator content but no sentinels
|
|
567
|
+
# Detect Gator ownership: markers, or known Gator references
|
|
568
|
+
is_gator_owned = (
|
|
569
|
+
GATOR_MARKER in existing
|
|
570
|
+
or COMMAND_POST_MARKER in existing
|
|
571
|
+
or "gator-init.py" in existing
|
|
572
|
+
or ".gator/constitution.md" in existing
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
if is_gator_owned:
|
|
576
|
+
# Find the split point — where Gator content starts
|
|
577
|
+
marker_pos = None
|
|
578
|
+
for marker in (GATOR_MARKER, COMMAND_POST_MARKER):
|
|
579
|
+
if marker in existing:
|
|
580
|
+
marker_pos = existing.index(marker)
|
|
581
|
+
break
|
|
582
|
+
|
|
583
|
+
if marker_pos is not None:
|
|
584
|
+
pre_gator = existing[:marker_pos].rstrip()
|
|
585
|
+
# Preserve post-Gator content (e.g., ## Pre-Gator Instructions)
|
|
586
|
+
post_gator = ""
|
|
587
|
+
pre_gator_section = "## Pre-Gator Instructions"
|
|
588
|
+
if pre_gator_section in existing:
|
|
589
|
+
post_gator = "\n\n" + existing[existing.index(pre_gator_section):]
|
|
590
|
+
else:
|
|
591
|
+
# No marker — entire file is Gator-managed (standalone old format)
|
|
592
|
+
pre_gator = ""
|
|
593
|
+
# Check for rollback reference
|
|
594
|
+
post_gator = ""
|
|
595
|
+
pre_gator_section = "## Pre-Gator Instructions"
|
|
596
|
+
if pre_gator_section in existing:
|
|
597
|
+
post_gator = "\n\n" + existing[existing.index(pre_gator_section):]
|
|
598
|
+
|
|
599
|
+
if pre_gator:
|
|
600
|
+
filepath.write_text(
|
|
601
|
+
f"{pre_gator}\n\n{managed_block}{post_gator}\n",
|
|
602
|
+
encoding="utf-8",
|
|
603
|
+
)
|
|
604
|
+
else:
|
|
605
|
+
filepath.write_text(
|
|
606
|
+
f"{header}\n\nYou are the primary agent for this project.\n\n{managed_block}{post_gator}\n",
|
|
607
|
+
encoding="utf-8",
|
|
608
|
+
)
|
|
609
|
+
log_step(f"{filename} — upgraded to sentinel format + refreshed")
|
|
610
|
+
idx += 1
|
|
611
|
+
continue
|
|
612
|
+
|
|
613
|
+
# Case: non-Gator file — interactive prompt
|
|
614
|
+
line_count = len(existing.splitlines())
|
|
615
|
+
print()
|
|
616
|
+
print(f" {filename} already exists ({line_count} lines).")
|
|
617
|
+
print()
|
|
618
|
+
print(" Options:")
|
|
619
|
+
print()
|
|
620
|
+
print(f" [1] Backup & replace (recommended) — save as {rollback_name},")
|
|
621
|
+
print(f" create new {filename} with Gator governance + reference to original.")
|
|
622
|
+
print()
|
|
623
|
+
print(f" [2] Append — add Gator lines to the end of the existing file.")
|
|
624
|
+
print()
|
|
625
|
+
print(f" [3] Overwrite — replace entirely (original content lost).")
|
|
626
|
+
if idx > 0:
|
|
627
|
+
print(f" [b] Go back — return to previous entry point.")
|
|
628
|
+
print(f" [x] Cancel installation.")
|
|
629
|
+
print()
|
|
630
|
+
choice = prompt("Choice", f"1/2/3{'/b' if idx > 0 else ''}/x")
|
|
631
|
+
|
|
632
|
+
if choice == "1":
|
|
633
|
+
shutil.copy2(filepath, target / rollback_name)
|
|
634
|
+
log_step(f"Saved original as {rollback_name}")
|
|
635
|
+
filepath.write_text(
|
|
636
|
+
f"{header}\n\nYou are the primary agent for this project.\n\n"
|
|
637
|
+
f"{managed_block}\n\n"
|
|
638
|
+
f"## Pre-Gator Instructions\n\n"
|
|
639
|
+
f"This project had an existing {filename} before Gator was installed.\n"
|
|
640
|
+
f"Those instructions have been preserved in [`{rollback_name}`]({rollback_name}).\n"
|
|
641
|
+
f"**Read that file now** — it may contain skills, custom instructions, personas,\n"
|
|
642
|
+
f"or project-specific rules that should still be followed alongside the\n"
|
|
643
|
+
f"Gator constitution.\n\n"
|
|
644
|
+
f"If any instructions in {rollback_name} conflict with the Gator constitution,\n"
|
|
645
|
+
f"the constitution takes precedence for code governance (charters, commit loop,\n"
|
|
646
|
+
f"enforcer rules). For everything else (skills, personas, custom workflows),\n"
|
|
647
|
+
f"follow {rollback_name}.\n",
|
|
648
|
+
encoding="utf-8",
|
|
649
|
+
)
|
|
650
|
+
log_step(f"Created new {filename} with Gator governance + reference to {rollback_name}")
|
|
651
|
+
elif choice == "2":
|
|
652
|
+
with open(filepath, "a", encoding="utf-8") as f:
|
|
653
|
+
f.write(f"\n{managed_block}\n")
|
|
654
|
+
log_step(f"Appended Gator entry point to {filename}")
|
|
655
|
+
elif choice == "3":
|
|
656
|
+
filepath.write_text(
|
|
657
|
+
f"{header}\n\nYou are the primary agent for this project.\n\n{managed_block}\n",
|
|
658
|
+
encoding="utf-8",
|
|
659
|
+
)
|
|
660
|
+
log_step(f"Replaced {filename} (original content lost)")
|
|
661
|
+
elif choice.lower() == "b" and idx > 0:
|
|
662
|
+
idx -= 1
|
|
663
|
+
continue
|
|
664
|
+
elif choice.lower() == "x":
|
|
665
|
+
print()
|
|
666
|
+
print(" Installation cancelled.")
|
|
667
|
+
print(" To clean up: git checkout dev && git branch -D gator-install")
|
|
668
|
+
sys.exit(0)
|
|
669
|
+
else:
|
|
670
|
+
# New file — create with sentinels
|
|
671
|
+
filepath.write_text(
|
|
672
|
+
f"{header}\n\nYou are the primary agent for this project.\n\n{managed_block}\n",
|
|
673
|
+
encoding="utf-8",
|
|
674
|
+
)
|
|
675
|
+
log_step(f"Created {filename}")
|
|
676
|
+
|
|
677
|
+
idx += 1
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
# ── post-install ───────────────────────────────────────────────────────────
|
|
681
|
+
|
|
682
|
+
def action_install_outbox(target):
|
|
683
|
+
"""Create outbox.md stub."""
|
|
684
|
+
outbox = target / "outbox.md"
|
|
685
|
+
if outbox.exists():
|
|
686
|
+
# Check if it has content
|
|
687
|
+
text = outbox.read_text(encoding="utf-8", errors="replace")
|
|
688
|
+
lines = [l for l in text.splitlines() if l.strip() and not l.startswith("#") and not l.startswith("---")]
|
|
689
|
+
if lines:
|
|
690
|
+
log_step("outbox.md already exists with content — preserved")
|
|
691
|
+
return
|
|
692
|
+
outbox.write_text(
|
|
693
|
+
"# Outbox\n\n"
|
|
694
|
+
"Messages for Gator Command. The Architect triages these during command post sessions.\n"
|
|
695
|
+
"Do not clear this file from the project repo — the command post session clears\n"
|
|
696
|
+
"entries after processing.\n\n---\n\n",
|
|
697
|
+
encoding="utf-8",
|
|
698
|
+
)
|
|
699
|
+
log_step("Created outbox.md")
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
def action_install_thin_link(target):
|
|
703
|
+
"""Create .gator/command-post.md with paths to command post."""
|
|
704
|
+
cp_path = str(COMMAND_POST)
|
|
705
|
+
try:
|
|
706
|
+
rel_path = os.path.relpath(str(COMMAND_POST), str(target))
|
|
707
|
+
except ValueError:
|
|
708
|
+
rel_path = cp_path
|
|
709
|
+
|
|
710
|
+
(target / ".gator" / "command-post.md").write_text(
|
|
711
|
+
f"---\ncommand-post: {rel_path}\n"
|
|
712
|
+
f"command-post-absolute: {cp_path} # machine-local\n"
|
|
713
|
+
f"remote: # optional\nversion: {TODAY}\n---\n\n"
|
|
714
|
+
f"# Gator Command Post\n\nThis repo is governed by [Gator Command]({rel_path}).\n"
|
|
715
|
+
f"The agent should read org standards from the command post at session open.\n"
|
|
716
|
+
f"**Do not modify the command post from this repo.**\n",
|
|
717
|
+
encoding="utf-8",
|
|
718
|
+
)
|
|
719
|
+
log_step("Installed thin link (.gator/command-post.md)")
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
def action_install_product_source(target):
|
|
723
|
+
"""Write .gator/product-source.json."""
|
|
724
|
+
gator_root = str(COMMAND_POST)
|
|
725
|
+
template_dir = ""
|
|
726
|
+
if (COMMAND_POST / "gator-engine" / "templates" / "gator-starter").is_dir():
|
|
727
|
+
template_dir = "gator-engine/templates/gator-starter"
|
|
728
|
+
elif (COMMAND_POST / "gator-command" / "templates" / "gator-starter").is_dir():
|
|
729
|
+
template_dir = "gator-command/templates/gator-starter"
|
|
730
|
+
|
|
731
|
+
import json
|
|
732
|
+
(target / ".gator" / "product-source.json").write_text(
|
|
733
|
+
json.dumps({
|
|
734
|
+
"gator_root": gator_root,
|
|
735
|
+
"template_dir": template_dir,
|
|
736
|
+
"installed": TODAY,
|
|
737
|
+
"updated": TODAY,
|
|
738
|
+
}, indent=2) + "\n",
|
|
739
|
+
encoding="utf-8",
|
|
740
|
+
)
|
|
741
|
+
log_step(f"Wrote product-source.json (source: {gator_root})")
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def action_register(target):
|
|
745
|
+
"""Add repo to registry.md."""
|
|
746
|
+
registry = COMMAND_POST / "gator-command" / "registry.md"
|
|
747
|
+
repo_name = target.name
|
|
748
|
+
|
|
749
|
+
if not registry.parent.is_dir():
|
|
750
|
+
return
|
|
751
|
+
|
|
752
|
+
if not registry.exists():
|
|
753
|
+
registry.write_text(
|
|
754
|
+
"# Registry\n\n"
|
|
755
|
+
"Repos governed by this Gator Command post. Updated by `gatorize.sh` and maintained by the Architect.\n\n"
|
|
756
|
+
"| Repo | Local path | Remote | Registered | Status |\n"
|
|
757
|
+
"|------|-----------|--------|------------|--------|\n",
|
|
758
|
+
encoding="utf-8",
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
text = registry.read_text(encoding="utf-8", errors="replace")
|
|
762
|
+
if str(target) in text:
|
|
763
|
+
log_step(f"{repo_name} already registered.")
|
|
764
|
+
return
|
|
765
|
+
|
|
766
|
+
remote, _ = git("remote", "get-url", "origin", cwd=target)
|
|
767
|
+
if not remote:
|
|
768
|
+
remote = "—"
|
|
769
|
+
|
|
770
|
+
with open(registry, "a", encoding="utf-8") as f:
|
|
771
|
+
f.write(f"| {repo_name} | {target} | {remote} | {TODAY} | current |\n")
|
|
772
|
+
print()
|
|
773
|
+
log_step(f"Registered {repo_name} in registry.")
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
def print_summary(target, scenario, has_command_post):
|
|
777
|
+
"""Print SUCCESS banner."""
|
|
778
|
+
repo_name = target.name
|
|
779
|
+
print()
|
|
780
|
+
print(" ================================================================")
|
|
781
|
+
print(f" Gatorized: {repo_name}")
|
|
782
|
+
print(" ================================================================")
|
|
783
|
+
print()
|
|
784
|
+
|
|
785
|
+
# Git instructions
|
|
786
|
+
branch, _ = git("branch", "--show-current", cwd=target)
|
|
787
|
+
if branch == GATOR_BRANCH:
|
|
788
|
+
print(f" You are on the '{GATOR_BRANCH}' branch. To finalize:")
|
|
789
|
+
print()
|
|
790
|
+
print(f" cd {target}")
|
|
791
|
+
print(f" git add -A && git commit -m 'Gatorize: install .gator/ governance layer'")
|
|
792
|
+
print(f" git checkout dev && git merge {GATOR_BRANCH}")
|
|
793
|
+
print(f" git branch -d {GATOR_BRANCH}")
|
|
794
|
+
|
|
795
|
+
print()
|
|
796
|
+
print(" ================================================================")
|
|
797
|
+
print(" SUCCESS")
|
|
798
|
+
print(" ================================================================")
|
|
799
|
+
print()
|
|
800
|
+
print(" What to do now:")
|
|
801
|
+
print()
|
|
802
|
+
print(f" 1. cd {target}")
|
|
803
|
+
print(f" 2. Launch your AI coding tool (Claude Code, Codex, Gemini, etc.)")
|
|
804
|
+
print(f" 3. Type: gator init")
|
|
805
|
+
print(f" 4. The agent will read the constitution and orient to the project.")
|
|
806
|
+
print(f" Your first git commit fires the governance hooks.")
|
|
807
|
+
print()
|
|
808
|
+
print(f" Enforcement: strict (commit gate active)")
|
|
809
|
+
if has_command_post:
|
|
810
|
+
print(f" Command post: linked")
|
|
811
|
+
else:
|
|
812
|
+
print(f" Command post: standalone")
|
|
813
|
+
print()
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
# ── memex morph ────────────────────────────────────────────────────────────
|
|
818
|
+
|
|
819
|
+
def detect_legacy_memex(target):
|
|
820
|
+
"""Detect what legacy memex structure exists."""
|
|
821
|
+
return {
|
|
822
|
+
"memex": (target / "memex").is_dir(),
|
|
823
|
+
"dot_memex": (target / ".memex").is_dir(),
|
|
824
|
+
"root_constitution": (target / "constitution.md").is_file(),
|
|
825
|
+
"root_constitution_core": (target / "constitution-core.md").is_file(),
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def action_morph_memex(target):
|
|
830
|
+
"""Morph legacy memex structure into .gator/."""
|
|
831
|
+
legacy = detect_legacy_memex(target)
|
|
832
|
+
|
|
833
|
+
print(" Legacy memex structure detected:")
|
|
834
|
+
if legacy["memex"]:
|
|
835
|
+
print(" memex/ (content layer)")
|
|
836
|
+
if legacy["dot_memex"]:
|
|
837
|
+
print(" .memex/ (system layer)")
|
|
838
|
+
if legacy["root_constitution"]:
|
|
839
|
+
print(" constitution.md (root)")
|
|
840
|
+
if legacy["root_constitution_core"]:
|
|
841
|
+
print(" constitution-core.md (root)")
|
|
842
|
+
print()
|
|
843
|
+
print(" Gatorize will morph this into .gator/ structure:")
|
|
844
|
+
print(" 1. Rename legacy dirs to *.pre-gator/")
|
|
845
|
+
print(" 2. Install fresh .gator/ from templates")
|
|
846
|
+
print(" 3. Copy your content into .gator/")
|
|
847
|
+
print(" 4. Archive root constitution files")
|
|
848
|
+
print()
|
|
849
|
+
if not confirm("Proceed with morph?"):
|
|
850
|
+
print(" Cancelled.")
|
|
851
|
+
sys.exit(0)
|
|
852
|
+
|
|
853
|
+
# Step 1: Rename legacy dirs
|
|
854
|
+
if legacy["memex"]:
|
|
855
|
+
pre = target / "memex.pre-gator"
|
|
856
|
+
if pre.exists():
|
|
857
|
+
log_step("memex.pre-gator/ already exists — skipping rename")
|
|
858
|
+
else:
|
|
859
|
+
_, ok = git("mv", "memex", "memex.pre-gator", cwd=target)
|
|
860
|
+
if not ok:
|
|
861
|
+
shutil.move(str(target / "memex"), str(pre))
|
|
862
|
+
log_step("Renamed memex/ -> memex.pre-gator/")
|
|
863
|
+
|
|
864
|
+
if legacy["dot_memex"]:
|
|
865
|
+
pre = target / ".memex.pre-gator"
|
|
866
|
+
if pre.exists():
|
|
867
|
+
log_step(".memex.pre-gator/ already exists — skipping rename")
|
|
868
|
+
else:
|
|
869
|
+
_, ok = git("mv", ".memex", ".memex.pre-gator", cwd=target)
|
|
870
|
+
if not ok:
|
|
871
|
+
shutil.move(str(target / ".memex"), str(pre))
|
|
872
|
+
log_step("Renamed .memex/ -> .memex.pre-gator/")
|
|
873
|
+
|
|
874
|
+
# Step 2: Install .gator/ (or upgrade if it exists)
|
|
875
|
+
if (target / ".gator").is_dir():
|
|
876
|
+
log_step("Existing .gator/ found — upgrading templates.")
|
|
877
|
+
subprocess.run(
|
|
878
|
+
[sys.executable, str(SCRIPTS_DIR / "gator-update.py"),
|
|
879
|
+
"--path", str(target), "--source", str(COMMAND_POST), "--no-policy"],
|
|
880
|
+
cwd=str(target),
|
|
881
|
+
)
|
|
882
|
+
else:
|
|
883
|
+
action_install_gator(target)
|
|
884
|
+
|
|
885
|
+
# Step 3: Migrate content from memex.pre-gator/
|
|
886
|
+
src = target / "memex.pre-gator"
|
|
887
|
+
gator_dir = target / ".gator"
|
|
888
|
+
|
|
889
|
+
if src.is_dir():
|
|
890
|
+
log_step("Migrating content from memex.pre-gator/ into .gator/...")
|
|
891
|
+
|
|
892
|
+
# Direct-copy content files
|
|
893
|
+
for name in ("mission.md", "roadmap.md", "identity.md", "inbox.md",
|
|
894
|
+
"issues.md", "commit_draft.md", "whiteboard.md",
|
|
895
|
+
"friction.md", "audit-tracker.md"):
|
|
896
|
+
f = src / name
|
|
897
|
+
if f.is_file():
|
|
898
|
+
shutil.copy2(f, gator_dir / name)
|
|
899
|
+
log_step(f" Migrated {name}")
|
|
900
|
+
|
|
901
|
+
# Patterns
|
|
902
|
+
if (src / "patterns").is_dir():
|
|
903
|
+
(gator_dir / "patterns").mkdir(exist_ok=True)
|
|
904
|
+
copy_tree_overlay(src / "patterns", gator_dir / "patterns")
|
|
905
|
+
log_step(" Migrated patterns/")
|
|
906
|
+
elif (src / "patterns.md").is_file():
|
|
907
|
+
shutil.copy2(src / "patterns.md", gator_dir / "patterns.md")
|
|
908
|
+
log_step(" Migrated patterns.md")
|
|
909
|
+
|
|
910
|
+
# Active threads -> threads/ (demoted)
|
|
911
|
+
_migrate_md_dir(src / "active-threads", gator_dir / "threads", "active threads -> threads/ (demoted)")
|
|
912
|
+
|
|
913
|
+
# Threads -> threads/ (merge)
|
|
914
|
+
_migrate_md_dir(src / "threads", gator_dir / "threads", "threads")
|
|
915
|
+
|
|
916
|
+
# Artifacts
|
|
917
|
+
_migrate_md_dir(src / "artifacts", gator_dir / "artifacts", "artifacts")
|
|
918
|
+
|
|
919
|
+
# Charters (skip templates)
|
|
920
|
+
if (src / "charters").is_dir():
|
|
921
|
+
count = 0
|
|
922
|
+
for f in sorted((src / "charters").glob("*.md")):
|
|
923
|
+
if f.name in ("_template.md", "README.md"):
|
|
924
|
+
continue
|
|
925
|
+
shutil.copy2(f, gator_dir / "charters" / f.name)
|
|
926
|
+
count += 1
|
|
927
|
+
if count:
|
|
928
|
+
log_step(f" Migrated {count} charters")
|
|
929
|
+
|
|
930
|
+
# Vault
|
|
931
|
+
if (src / "vault").is_dir() and any((src / "vault").iterdir()):
|
|
932
|
+
(gator_dir / "vault").mkdir(exist_ok=True)
|
|
933
|
+
copy_tree_overlay(src / "vault", gator_dir / "vault")
|
|
934
|
+
log_step(" Migrated vault/")
|
|
935
|
+
|
|
936
|
+
# Reference notes (conflict-aware)
|
|
937
|
+
if (src / "reference-notes").is_dir():
|
|
938
|
+
rn_count = 0
|
|
939
|
+
rn_conflict = 0
|
|
940
|
+
for f in sorted((src / "reference-notes").glob("*.md")):
|
|
941
|
+
dest = gator_dir / "reference-notes" / f.name
|
|
942
|
+
if dest.exists():
|
|
943
|
+
# Check if different
|
|
944
|
+
if f.read_bytes() != dest.read_bytes():
|
|
945
|
+
conflict_name = f.stem + "-project.md"
|
|
946
|
+
shutil.copy2(f, gator_dir / "reference-notes" / conflict_name)
|
|
947
|
+
rn_conflict += 1
|
|
948
|
+
else:
|
|
949
|
+
shutil.copy2(f, dest)
|
|
950
|
+
rn_count += 1
|
|
951
|
+
if rn_count:
|
|
952
|
+
log_step(f" Migrated {rn_count} reference notes")
|
|
953
|
+
if rn_conflict:
|
|
954
|
+
log_step(f" Preserved {rn_conflict} conflicting notes with -project suffix")
|
|
955
|
+
|
|
956
|
+
# Migrate from .memex.pre-gator/
|
|
957
|
+
dotsrc = target / ".memex.pre-gator"
|
|
958
|
+
if dotsrc.is_dir():
|
|
959
|
+
log_step("Migrating system layer from .memex.pre-gator/...")
|
|
960
|
+
if (dotsrc / "roles.yaml").is_file():
|
|
961
|
+
shutil.copy2(dotsrc / "roles.yaml", gator_dir / "roles.yaml")
|
|
962
|
+
log_step(" Migrated roles.yaml")
|
|
963
|
+
if (dotsrc / "policies").is_dir():
|
|
964
|
+
(gator_dir / "policies").mkdir(exist_ok=True)
|
|
965
|
+
copy_tree_overlay(dotsrc / "policies", gator_dir / "policies")
|
|
966
|
+
log_step(" Migrated policies/")
|
|
967
|
+
|
|
968
|
+
# Step 4: Archive root constitution files
|
|
969
|
+
if legacy["root_constitution"] and (target / "constitution.md").is_file():
|
|
970
|
+
_, ok = git("mv", "constitution.md", ".gator/legacy-constitution.md", cwd=target)
|
|
971
|
+
if not ok:
|
|
972
|
+
shutil.move(str(target / "constitution.md"),
|
|
973
|
+
str(gator_dir / "legacy-constitution.md"))
|
|
974
|
+
log_step("Archived constitution.md -> .gator/legacy-constitution.md")
|
|
975
|
+
|
|
976
|
+
if legacy["root_constitution_core"] and (target / "constitution-core.md").is_file():
|
|
977
|
+
_, ok = git("mv", "constitution-core.md", ".gator/legacy-constitution-core.md", cwd=target)
|
|
978
|
+
if not ok:
|
|
979
|
+
shutil.move(str(target / "constitution-core.md"),
|
|
980
|
+
str(gator_dir / "legacy-constitution-core.md"))
|
|
981
|
+
log_step("Archived constitution-core.md -> .gator/legacy-constitution-core.md")
|
|
982
|
+
|
|
983
|
+
# Clean up .gitkeep in dirs that now have content
|
|
984
|
+
for d in ("threads", "artifacts"):
|
|
985
|
+
gitkeep = gator_dir / d / ".gitkeep"
|
|
986
|
+
if gitkeep.exists() and any((gator_dir / d).glob("*.md")):
|
|
987
|
+
gitkeep.unlink()
|
|
988
|
+
|
|
989
|
+
write_gator_version(gator_dir, "morph")
|
|
990
|
+
|
|
991
|
+
print()
|
|
992
|
+
log_step("Morph complete. Pre-gator directories preserved for review:")
|
|
993
|
+
if legacy["memex"]:
|
|
994
|
+
log_step(" memex.pre-gator/ — your original content")
|
|
995
|
+
if legacy["dot_memex"]:
|
|
996
|
+
log_step(" .memex.pre-gator/ — your original system layer")
|
|
997
|
+
log_step("Review .gator/, then delete pre-gator dirs when satisfied.")
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
def _migrate_md_dir(src_dir, dest_dir, label):
|
|
1001
|
+
"""Migrate .md files from src to dest, handling name collisions."""
|
|
1002
|
+
if not src_dir.is_dir():
|
|
1003
|
+
return
|
|
1004
|
+
count = 0
|
|
1005
|
+
dest_dir.mkdir(exist_ok=True)
|
|
1006
|
+
for f in sorted(src_dir.glob("*.md")):
|
|
1007
|
+
if f.name in ("_TEMPLATE.md", "_template.md"):
|
|
1008
|
+
continue
|
|
1009
|
+
dest = dest_dir / f.name
|
|
1010
|
+
if dest.exists():
|
|
1011
|
+
# Collision — add -legacy suffix
|
|
1012
|
+
dest = dest_dir / (f.stem + "-legacy.md")
|
|
1013
|
+
shutil.copy2(f, dest)
|
|
1014
|
+
count += 1
|
|
1015
|
+
if count:
|
|
1016
|
+
log_step(f" Migrated {count} {label}")
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
# ── main ───────────────────────────────────────────────────────────────────
|
|
1020
|
+
|
|
1021
|
+
def main():
|
|
1022
|
+
ensure_utf8_stdout()
|
|
1023
|
+
|
|
1024
|
+
parser = argparse.ArgumentParser(
|
|
1025
|
+
description="Install Gator governance into a project repo.",
|
|
1026
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
1027
|
+
)
|
|
1028
|
+
parser.add_argument("target", help="Target directory to gatorize")
|
|
1029
|
+
args = parser.parse_args()
|
|
1030
|
+
|
|
1031
|
+
target = Path(args.target).resolve()
|
|
1032
|
+
|
|
1033
|
+
# Create directory if it doesn't exist
|
|
1034
|
+
if not target.exists():
|
|
1035
|
+
print()
|
|
1036
|
+
print(f" Directory does not exist: {target}")
|
|
1037
|
+
if confirm("Create it and gatorize?"):
|
|
1038
|
+
target.mkdir(parents=True)
|
|
1039
|
+
else:
|
|
1040
|
+
print(" Aborted.")
|
|
1041
|
+
sys.exit(0)
|
|
1042
|
+
|
|
1043
|
+
# Self-gatorize guard
|
|
1044
|
+
if target.resolve() == COMMAND_POST.resolve():
|
|
1045
|
+
print(" Error: Target is the command post itself. Gatorize a project repo, not gator-command.")
|
|
1046
|
+
sys.exit(1)
|
|
1047
|
+
|
|
1048
|
+
# Check templates
|
|
1049
|
+
if not TEMPLATES or not TEMPLATES.is_dir():
|
|
1050
|
+
print(f" Error: Templates not found. Are you running this from the gator repo?")
|
|
1051
|
+
sys.exit(1)
|
|
1052
|
+
|
|
1053
|
+
# Detect scenario
|
|
1054
|
+
scenario = detect_scenario(target)
|
|
1055
|
+
|
|
1056
|
+
print()
|
|
1057
|
+
print(" ================================================================")
|
|
1058
|
+
print(f" gatorize.py — {target.name}")
|
|
1059
|
+
print(" ================================================================")
|
|
1060
|
+
|
|
1061
|
+
# Detect command post — a bare gator clone is NOT a command post.
|
|
1062
|
+
# Only treat as command post if identity.md has been populated
|
|
1063
|
+
# (bracket placeholders mean it's still a bare clone).
|
|
1064
|
+
cp = find_command_post(str(SCRIPTS_DIR))
|
|
1065
|
+
has_command_post = False
|
|
1066
|
+
if cp:
|
|
1067
|
+
identity = Path(str(cp)) / "gator-command" / "identity.md"
|
|
1068
|
+
if identity.exists():
|
|
1069
|
+
text = identity.read_text(encoding="utf-8", errors="replace")
|
|
1070
|
+
# Populated = no bracket placeholders
|
|
1071
|
+
has_command_post = "[Your name]" not in text and "[Your role]" not in text
|
|
1072
|
+
else:
|
|
1073
|
+
has_command_post = False
|
|
1074
|
+
|
|
1075
|
+
# Dispatch
|
|
1076
|
+
if scenario == 1:
|
|
1077
|
+
print(" Scenario 1: Fresh directory (no git)")
|
|
1078
|
+
print()
|
|
1079
|
+
action_git_init(target)
|
|
1080
|
+
action_install_gator(target)
|
|
1081
|
+
elif scenario == 2:
|
|
1082
|
+
print(" Scenario 2: Git repo, clean")
|
|
1083
|
+
print()
|
|
1084
|
+
action_feature_branch(target)
|
|
1085
|
+
action_install_gator(target)
|
|
1086
|
+
elif scenario == 3:
|
|
1087
|
+
gen = detect_generation(target)
|
|
1088
|
+
print(f" Gator is already installed (generation {gen}). Running update...")
|
|
1089
|
+
print()
|
|
1090
|
+
result = subprocess.run(
|
|
1091
|
+
[sys.executable, str(SCRIPTS_DIR / "gator-update.py"),
|
|
1092
|
+
"--path", str(target), "--source", str(COMMAND_POST), "--no-policy"],
|
|
1093
|
+
cwd=str(target),
|
|
1094
|
+
)
|
|
1095
|
+
if result.returncode != 0:
|
|
1096
|
+
print()
|
|
1097
|
+
print(" ================================================================")
|
|
1098
|
+
print(" UPDATE FAILED (exit code {})".format(result.returncode))
|
|
1099
|
+
print(" ================================================================")
|
|
1100
|
+
print()
|
|
1101
|
+
sys.exit(result.returncode)
|
|
1102
|
+
# Fall through to common tail — refreshes entry points, outbox, product-source
|
|
1103
|
+
elif scenario == 4:
|
|
1104
|
+
print(" Scenario 4: Legacy memex structure detected")
|
|
1105
|
+
print()
|
|
1106
|
+
action_feature_branch(target)
|
|
1107
|
+
action_morph_memex(target)
|
|
1108
|
+
elif scenario == 5:
|
|
1109
|
+
print(" Scenario 5: Both memex and gator found")
|
|
1110
|
+
print()
|
|
1111
|
+
legacy = detect_legacy_memex(target)
|
|
1112
|
+
print(" Legacy memex:")
|
|
1113
|
+
if legacy["memex"]:
|
|
1114
|
+
print(" memex/")
|
|
1115
|
+
if legacy["dot_memex"]:
|
|
1116
|
+
print(" .memex/")
|
|
1117
|
+
if legacy["root_constitution"]:
|
|
1118
|
+
print(" constitution.md (root)")
|
|
1119
|
+
print(" Gator:")
|
|
1120
|
+
print(" .gator/")
|
|
1121
|
+
print()
|
|
1122
|
+
print(" Options:")
|
|
1123
|
+
print(" [m] Morph — fold memex content into existing .gator/ (recommended)")
|
|
1124
|
+
print(" [u] Upgrade — refresh .gator/ templates, ignore memex dirs")
|
|
1125
|
+
print(" [x] Cancel")
|
|
1126
|
+
print()
|
|
1127
|
+
choice = prompt("Choice", "m/u/x")
|
|
1128
|
+
if choice.lower() == "m":
|
|
1129
|
+
action_feature_branch(target)
|
|
1130
|
+
action_morph_memex(target)
|
|
1131
|
+
elif choice.lower() == "u":
|
|
1132
|
+
gen = detect_generation(target)
|
|
1133
|
+
print(f" Running update (generation {gen})...")
|
|
1134
|
+
result = subprocess.run(
|
|
1135
|
+
[sys.executable, str(SCRIPTS_DIR / "gator-update.py"),
|
|
1136
|
+
"--path", str(target), "--source", str(COMMAND_POST), "--no-policy"],
|
|
1137
|
+
cwd=str(target),
|
|
1138
|
+
)
|
|
1139
|
+
if result.returncode != 0:
|
|
1140
|
+
print()
|
|
1141
|
+
print(" ================================================================")
|
|
1142
|
+
print(" UPDATE FAILED (exit code {})".format(result.returncode))
|
|
1143
|
+
print(" ================================================================")
|
|
1144
|
+
print()
|
|
1145
|
+
sys.exit(result.returncode)
|
|
1146
|
+
# Fall through to common tail
|
|
1147
|
+
else:
|
|
1148
|
+
print(" Cancelled.")
|
|
1149
|
+
sys.exit(0)
|
|
1150
|
+
|
|
1151
|
+
# Common tail (all scenarios except cancel/failure)
|
|
1152
|
+
ensure_repo_gitignore(target)
|
|
1153
|
+
if has_command_post:
|
|
1154
|
+
action_install_thin_link(target)
|
|
1155
|
+
action_install_entry_points(target, has_command_post)
|
|
1156
|
+
action_install_outbox(target)
|
|
1157
|
+
action_install_product_source(target)
|
|
1158
|
+
action_register(target)
|
|
1159
|
+
print_summary(target, scenario, has_command_post)
|
|
1160
|
+
|
|
1161
|
+
|
|
1162
|
+
if __name__ == "__main__":
|
|
1163
|
+
main()
|