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.
Files changed (110) hide show
  1. gator_command/__init__.py +2 -0
  2. gator_command/cli.py +137 -0
  3. gator_command/scripts/crawler.py +633 -0
  4. gator_command/scripts/dashboard/dashboard.css +982 -0
  5. gator_command/scripts/dashboard/dashboard.html +84 -0
  6. gator_command/scripts/dashboard/dashboard.js +419 -0
  7. gator_command/scripts/dashboard/views/audit.js +270 -0
  8. gator_command/scripts/dashboard/views/fleet.js +307 -0
  9. gator_command/scripts/dashboard/views/repo.js +599 -0
  10. gator_command/scripts/dashboard/views/settings.js +173 -0
  11. gator_command/scripts/dashboard/views/updates.js +308 -0
  12. gator_command/scripts/enforcer-prompt.md +22 -0
  13. gator_command/scripts/extract-claude-sessions.py +489 -0
  14. gator_command/scripts/extract-codex-sessions.py +477 -0
  15. gator_command/scripts/extract-gemini-sessions.py +410 -0
  16. gator_command/scripts/gator-audit.py +956 -0
  17. gator_command/scripts/gator-charter-draft.py +919 -0
  18. gator_command/scripts/gator-charter-lint.py +427 -0
  19. gator_command/scripts/gator-charter-verify.py +606 -0
  20. gator_command/scripts/gator-dashboard.py +1271 -0
  21. gator_command/scripts/gator-deploy.py +916 -0
  22. gator_command/scripts/gator-drift.py +569 -0
  23. gator_command/scripts/gator-enforce.py +82 -0
  24. gator_command/scripts/gator-fleet-intel.py +460 -0
  25. gator_command/scripts/gator-fleet-report.py +615 -0
  26. gator_command/scripts/gator-init-command-post.py +315 -0
  27. gator_command/scripts/gator-init.py +434 -0
  28. gator_command/scripts/gator-machine-id.py +153 -0
  29. gator_command/scripts/gator-policy-status.py +631 -0
  30. gator_command/scripts/gator-pulse.py +459 -0
  31. gator_command/scripts/gator-repo-status.py +649 -0
  32. gator_command/scripts/gator-session-common.py +372 -0
  33. gator_command/scripts/gator-session-sink.py +831 -0
  34. gator_command/scripts/gator-sessions.py +1244 -0
  35. gator_command/scripts/gator-update.py +615 -0
  36. gator_command/scripts/gator-version.py +38 -0
  37. gator_command/scripts/gator_core.py +489 -0
  38. gator_command/scripts/gator_remote.py +381 -0
  39. gator_command/scripts/gator_runtime.py +142 -0
  40. gator_command/scripts/gatorize-actions.sh +989 -0
  41. gator_command/scripts/gatorize-lib.sh +166 -0
  42. gator_command/scripts/gatorize-post.sh +394 -0
  43. gator_command/scripts/gatorize.py +1163 -0
  44. gator_command/scripts/gatorize.sh +185 -0
  45. gator_command/scripts/generate_markdown.py +212 -0
  46. gator_command/scripts/generate_wiki.py +424 -0
  47. gator_command/scripts/graph_health.py +780 -0
  48. gator_command/scripts/memex-lint.py +286 -0
  49. gator_command/scripts/memex-lint.sh +205 -0
  50. gator_command/scripts/memex.py +1472 -0
  51. gator_command/scripts/memex_formatters.py +191 -0
  52. gator_command/scripts/memex_state.py +236 -0
  53. gator_command/scripts/spawn.py +650 -0
  54. gator_command/templates/gator-starter/blueprints/README.md +32 -0
  55. gator_command/templates/gator-starter/charterignore +53 -0
  56. gator_command/templates/gator-starter/charters/README.md +178 -0
  57. gator_command/templates/gator-starter/charters/_template.md +31 -0
  58. gator_command/templates/gator-starter/commands/commit.md +33 -0
  59. gator_command/templates/gator-starter/commands/init.md +11 -0
  60. gator_command/templates/gator-starter/commands/update.md +5 -0
  61. gator_command/templates/gator-starter/constitution.md +165 -0
  62. gator_command/templates/gator-starter/field-guides/README.md +25 -0
  63. gator_command/templates/gator-starter/gator-start-up.md +119 -0
  64. gator_command/templates/gator-starter/procedures/charter-alignment.md +83 -0
  65. gator_command/templates/gator-starter/procedures/enforcer-review.md +317 -0
  66. gator_command/templates/gator-starter/procedures/field-guide-generation.md +176 -0
  67. gator_command/templates/gator-starter/procedures/knowledge-capture.md +57 -0
  68. gator_command/templates/gator-starter/procedures/significance-check.md +69 -0
  69. gator_command/templates/gator-starter/reference-notes/concierge-responses.md +535 -0
  70. gator_command/templates/gator-starter/reference-notes/dangerous-patterns.md +91 -0
  71. gator_command/templates/gator-starter/reference-notes/dashboard-operations.md +22 -0
  72. gator_command/templates/gator-starter/reference-notes/enforcer-configuration.md +232 -0
  73. gator_command/templates/gator-starter/reference-notes/example-project.md +289 -0
  74. gator_command/templates/gator-starter/reference-notes/failure-modes-and-self-correction.md +72 -0
  75. gator_command/templates/gator-starter/reference-notes/git-workflow.md +60 -0
  76. gator_command/templates/gator-starter/reference-notes/identity-and-ownership.md +37 -0
  77. gator_command/templates/gator-starter/reference-notes/refactor-approach.md +155 -0
  78. gator_command/templates/gator-starter/reference-notes/what-gator-requires-from-a-model.md +108 -0
  79. gator_command/templates/gator-starter/reference-notes/why-navigation-coding-feels-different.md +99 -0
  80. gator_command/templates/gator-starter/reference-notes/workflow-profiles.md +155 -0
  81. gator_command/templates/gator-starter/scripts/__pycache__/enforcer-review.cpython-313.pyc +0 -0
  82. gator_command/templates/gator-starter/scripts/__pycache__/gator-approve.cpython-313.pyc +0 -0
  83. gator_command/templates/gator-starter/scripts/__pycache__/gator-init.cpython-313.pyc +0 -0
  84. gator_command/templates/gator-starter/scripts/__pycache__/gator-pre-commit.cpython-313.pyc +0 -0
  85. gator_command/templates/gator-starter/scripts/__pycache__/gator-update.cpython-313.pyc +0 -0
  86. gator_command/templates/gator-starter/scripts/enforcer-prompt.md +55 -0
  87. gator_command/templates/gator-starter/scripts/enforcer-review.py +1551 -0
  88. gator_command/templates/gator-starter/scripts/gator-approve.py +139 -0
  89. gator_command/templates/gator-starter/scripts/gator-enforce.py +82 -0
  90. gator_command/templates/gator-starter/scripts/gator-init.py +434 -0
  91. gator_command/templates/gator-starter/scripts/gator-pre-commit.py +2670 -0
  92. gator_command/templates/gator-starter/scripts/gator-pulse.py +459 -0
  93. gator_command/templates/gator-starter/scripts/gator-update.py +615 -0
  94. gator_command/templates/gator-starter/scripts/gator-version.py +38 -0
  95. gator_command/templates/gator-starter/scripts/gator_core.py +487 -0
  96. gator_command/templates/gator-starter/scripts/hooks/__pycache__/commit-msgcpython-313.pyc +0 -0
  97. gator_command/templates/gator-starter/scripts/hooks/__pycache__/post-commitcpython-313.pyc +0 -0
  98. gator_command/templates/gator-starter/scripts/hooks/__pycache__/pre-commitcpython-313.pyc +0 -0
  99. gator_command/templates/gator-starter/scripts/hooks/commit-msg +5 -0
  100. gator_command/templates/gator-starter/scripts/hooks/post-commit +7 -0
  101. gator_command/templates/gator-starter/scripts/hooks/pre-commit +5 -0
  102. gator_command/templates/gator-starter/sessions/.gitignore +7 -0
  103. gator_command/templates/gator-starter/vault/.gitkeep +0 -0
  104. gator_command/templates/gator-starter/whiteboard.md +5 -0
  105. gator_command-1.0.0.dist-info/METADATA +122 -0
  106. gator_command-1.0.0.dist-info/RECORD +110 -0
  107. gator_command-1.0.0.dist-info/WHEEL +5 -0
  108. gator_command-1.0.0.dist-info/entry_points.txt +2 -0
  109. gator_command-1.0.0.dist-info/licenses/LICENSE +21 -0
  110. gator_command-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,489 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ gator_core.py — Shared infrastructure for Gator Command scripts.
4
+
5
+ Centralizes utilities that were previously duplicated across 6+ scripts:
6
+ version resolution, command-post discovery, registry parsing, path
7
+ normalization, git helpers, and stdout setup.
8
+
9
+ Usage (automatic — scripts/ is on sys.path when any sibling runs):
10
+ from gator_core import get_version, find_command_post, parse_registry, git
11
+
12
+ Not a standalone script — imported by other gator-* scripts.
13
+
14
+ @reads: filesystem, git tags, registry.md, .gator/command-post.md
15
+ @writes: nothing (pure library)
16
+ """
17
+
18
+ import io
19
+ import json
20
+ import re
21
+ import subprocess
22
+ import sys
23
+ from pathlib import Path
24
+
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # Version
28
+ # ---------------------------------------------------------------------------
29
+
30
+ def _read_version_file(start=None):
31
+ """Read VERSION file by walking up from start (or this script's dir)."""
32
+ d = start or Path(__file__).resolve().parent
33
+ for _ in range(5):
34
+ vf = d / "VERSION"
35
+ if vf.is_file():
36
+ text = vf.read_text(encoding="utf-8", errors="replace").strip()
37
+ if text:
38
+ return f"v{text}" if not text.startswith("v") else text
39
+ d = d.parent
40
+ return None
41
+
42
+
43
+ def get_version(cwd=None):
44
+ """Get the current version from git tags, falling back to VERSION file.
45
+
46
+ Resolution order:
47
+ 1. git describe --tags --always (exact tag or tag+offset)
48
+ 2. VERSION file (for deployed repos without full git history)
49
+ 3. git rev-parse --short HEAD (bare hash)
50
+ 4. "dev"
51
+ """
52
+ if cwd is None:
53
+ cwd = Path(__file__).resolve().parent
54
+ for _ in range(5):
55
+ if (cwd / ".git").is_dir():
56
+ break
57
+ cwd = cwd.parent
58
+
59
+ try:
60
+ result = subprocess.run(
61
+ ["git", "describe", "--tags", "--always"],
62
+ capture_output=True, text=True, cwd=cwd, timeout=5,
63
+ )
64
+ if result.returncode == 0 and result.stdout.strip():
65
+ version = result.stdout.strip()
66
+ if version.startswith("v"):
67
+ return version
68
+ except (OSError, subprocess.TimeoutExpired):
69
+ pass
70
+
71
+ # No tagged version from git — try VERSION file
72
+ from_file = _read_version_file(cwd)
73
+ if from_file:
74
+ return from_file
75
+
76
+ try:
77
+ result = subprocess.run(
78
+ ["git", "rev-parse", "--short", "HEAD"],
79
+ capture_output=True, text=True, cwd=cwd, timeout=5,
80
+ )
81
+ if result.returncode == 0 and result.stdout.strip():
82
+ return f"dev-g{result.stdout.strip()}"
83
+ except (OSError, subprocess.TimeoutExpired):
84
+ pass
85
+
86
+ return "dev"
87
+
88
+
89
+ def get_version_short(cwd=None):
90
+ """Get just the tag version, without commit count or hash.
91
+
92
+ Returns "v0.1.0" whether on the tag or after it.
93
+ Falls back to "dev" if no tags.
94
+ """
95
+ version = get_version(cwd)
96
+ if version.startswith("v"):
97
+ return version.split("-")[0]
98
+ return version
99
+
100
+
101
+ # ---------------------------------------------------------------------------
102
+ # Path helpers
103
+ # ---------------------------------------------------------------------------
104
+
105
+ def normalize_path(raw_path):
106
+ """Normalize MSYS2/Git Bash paths (/c/Users/...) to native Windows.
107
+
108
+ Converts /c/Users/... to C:/Users/... for cross-platform compatibility.
109
+ No-op on paths that don't match the MSYS2 pattern.
110
+ """
111
+ if re.match(r'^/[a-zA-Z]/', raw_path):
112
+ raw_path = raw_path[1].upper() + ":" + raw_path[2:]
113
+ return raw_path
114
+
115
+
116
+ def find_command_post(start_path=None):
117
+ """Find the gator-command repo root.
118
+
119
+ Walks up from start_path looking for a directory containing
120
+ gator-command/mission.md. Used by command-post scripts (fleet-report,
121
+ drift, audit, init-command-post).
122
+ """
123
+ path = Path(start_path) if start_path else Path.cwd()
124
+ path = path.resolve()
125
+
126
+ if (path / "gator-command" / "mission.md").exists():
127
+ return path
128
+ if (path / "mission.md").exists() and (path / "active-threads").is_dir():
129
+ return path.parent
130
+ for parent in path.parents:
131
+ if (parent / "gator-command" / "mission.md").exists():
132
+ return parent
133
+ return None
134
+
135
+
136
+ def find_gator_root(start_path=None):
137
+ """Walk up from start_path looking for .gator/ directory.
138
+
139
+ Used by per-repo scripts (init, update) to find the governed repo root.
140
+ """
141
+ path = Path(start_path) if start_path else Path.cwd()
142
+ path = path.resolve()
143
+ if (path / ".gator").is_dir():
144
+ return path
145
+ for parent in path.parents:
146
+ if (parent / ".gator").is_dir():
147
+ return parent
148
+ return None
149
+
150
+
151
+ def resolve_thin_link(gator_dir):
152
+ """Read the command-post thin link and resolve the path.
153
+
154
+ Used by per-repo scripts (update) to find the command post from
155
+ a governed repo's .gator/command-post.md.
156
+ """
157
+ cp_file = gator_dir / "command-post.md"
158
+ if not cp_file.exists():
159
+ return None
160
+
161
+ text = cp_file.read_text(encoding="utf-8", errors="replace")
162
+
163
+ # Try command-post-absolute first (most reliable on same machine)
164
+ for line in text.splitlines():
165
+ if line.startswith("command-post-absolute:"):
166
+ raw = line.split(":", 1)[1].strip().split("#")[0].strip()
167
+ if raw:
168
+ resolved = Path(normalize_path(raw))
169
+ if resolved.is_dir():
170
+ return resolved
171
+
172
+ # Fall back to command-post (relative or absolute)
173
+ for line in text.splitlines():
174
+ if line.startswith("command-post:") and not line.startswith("command-post-absolute:"):
175
+ raw = line.split(":", 1)[1].strip()
176
+ if raw:
177
+ # Try as relative from repo root
178
+ resolved = (gator_dir.parent / raw).resolve()
179
+ if resolved.is_dir():
180
+ return resolved
181
+ # Try as absolute
182
+ resolved = Path(normalize_path(raw))
183
+ if resolved.is_dir():
184
+ return resolved
185
+
186
+ return None
187
+
188
+
189
+ # ---------------------------------------------------------------------------
190
+ # Product source and repo topology
191
+ # ---------------------------------------------------------------------------
192
+
193
+ def read_product_source(gator_dir):
194
+ """Read .gator/product-source.json. Returns dict or None if missing/corrupt."""
195
+ ps_file = Path(gator_dir) / "product-source.json"
196
+ if not ps_file.exists():
197
+ return None
198
+ try:
199
+ data = json.loads(ps_file.read_text(encoding="utf-8"))
200
+ return data if isinstance(data, dict) else None
201
+ except (json.JSONDecodeError, OSError):
202
+ return None
203
+
204
+
205
+ def resolve_template_source(gator_dir, source_override=None):
206
+ """Resolve the template source directory for product updates.
207
+
208
+ Resolution order:
209
+ 1. Explicit --source override
210
+ 2. .gator/product-source.json (gator_root + template_dir)
211
+ 3. Thin link -> command post -> templates (legacy fallback)
212
+ 4. None (caller should fail with clear message)
213
+
214
+ Returns (template_path, gator_root) or (None, None).
215
+ """
216
+ # 1. Explicit override
217
+ if source_override:
218
+ root = Path(source_override).resolve()
219
+ for tpl_rel in ("gator-engine/templates/gator-starter",
220
+ "gator-command/templates/gator-starter",
221
+ "templates/gator-starter"):
222
+ tpl = root / tpl_rel
223
+ if tpl.is_dir():
224
+ return tpl, root
225
+ return None, None
226
+
227
+ gator_dir = Path(gator_dir)
228
+
229
+ # 2. product-source.json
230
+ ps = read_product_source(gator_dir)
231
+ if ps:
232
+ root = Path(ps.get("gator_root", ""))
233
+ tpl_rel = ps.get("template_dir", "")
234
+ if root.is_dir() and tpl_rel:
235
+ tpl = root / tpl_rel
236
+ if tpl.is_dir():
237
+ return tpl, root
238
+
239
+ # 3. Thin link fallback
240
+ cp = resolve_thin_link(gator_dir)
241
+ if cp:
242
+ for tpl_rel in ("gator-command/templates/gator-starter",
243
+ "gator-engine/templates/gator-starter",
244
+ "templates/gator-starter"):
245
+ tpl = Path(cp) / tpl_rel
246
+ if tpl.is_dir():
247
+ return tpl, Path(cp)
248
+
249
+ return None, None
250
+
251
+
252
+ def get_repo_topology(gator_dir):
253
+ """Derive the governance topology of a repo.
254
+
255
+ Returns one of:
256
+ "policy-synced" — active thin link, repo receives org policy
257
+ "standalone" — no thin link, no policy artifacts, template updates only
258
+ "inconsistent" — partial artifacts remain, needs repair
259
+ """
260
+ gator_dir = Path(gator_dir)
261
+ cp_file = gator_dir / "command-post.md"
262
+ gov_source = gator_dir / "governance-source.json"
263
+ policy_link = gator_dir / "policy-link.json"
264
+ policy_cache = gator_dir / "policy-cache"
265
+
266
+ has_thin_link = cp_file.exists()
267
+ has_policy_content = policy_link.exists() or (
268
+ policy_cache.is_dir() and any(policy_cache.iterdir())
269
+ )
270
+ has_gov_source = gov_source.exists()
271
+
272
+ # Thin link present and resolvable → policy-synced
273
+ if has_thin_link:
274
+ cp = resolve_thin_link(gator_dir)
275
+ if cp:
276
+ return "policy-synced"
277
+ # Thin link exists but points nowhere
278
+ return "inconsistent"
279
+
280
+ # No thin link — check for orphaned policy artifacts
281
+ if has_policy_content or has_gov_source:
282
+ return "inconsistent"
283
+
284
+ return "standalone"
285
+
286
+
287
+ def clear_policy_artifacts(gator_dir):
288
+ """Remove all policy-source artifacts to make a repo standalone.
289
+
290
+ Clears: command-post.md, governance-source.json, policy-link.json, policy-cache/.
291
+ """
292
+ import shutil
293
+ gator_dir = Path(gator_dir)
294
+
295
+ for name in ("command-post.md", "governance-source.json", "policy-link.json"):
296
+ f = gator_dir / name
297
+ if f.exists():
298
+ f.unlink()
299
+
300
+ cache = gator_dir / "policy-cache"
301
+ if cache.is_dir():
302
+ shutil.rmtree(cache)
303
+
304
+
305
+ # ---------------------------------------------------------------------------
306
+ # Registry
307
+ # ---------------------------------------------------------------------------
308
+
309
+ def parse_registry(command_post):
310
+ """Parse registry.md into a list of repo entries.
311
+
312
+ Each entry is a dict with: name, path, remote, registered, status.
313
+ """
314
+ registry_file = command_post / "gator-command" / "registry.md"
315
+ if not registry_file.exists():
316
+ return []
317
+
318
+ repos = []
319
+ text = registry_file.read_text(encoding="utf-8", errors="replace")
320
+ in_table = False
321
+
322
+ for line in text.splitlines():
323
+ stripped = line.strip()
324
+ if stripped.startswith("| Repo"):
325
+ in_table = True
326
+ continue
327
+ if stripped.startswith("|---"):
328
+ continue
329
+ if in_table and stripped.startswith("|"):
330
+ cells = [c.strip() for c in stripped.split("|")[1:-1]]
331
+ if len(cells) >= 5:
332
+ repos.append({
333
+ "name": cells[0],
334
+ "path": cells[1],
335
+ "remote": cells[2],
336
+ "registered": cells[3],
337
+ "status": cells[4],
338
+ })
339
+
340
+ return repos
341
+
342
+
343
+ # ---------------------------------------------------------------------------
344
+ # Git helpers
345
+ # ---------------------------------------------------------------------------
346
+
347
+ def git(*args, cwd=None):
348
+ """Run a git command, return (stdout, success).
349
+
350
+ Returns a tuple so callers can distinguish 'git returned nothing'
351
+ from 'git failed'. Silent emptiness is the wrong failure mode for
352
+ a product whose claim is trustworthy governance telemetry.
353
+ """
354
+ try:
355
+ result = subprocess.run(
356
+ ["git"] + list(args),
357
+ capture_output=True, text=True, cwd=cwd, timeout=10,
358
+ )
359
+ return result.stdout.strip(), result.returncode == 0
360
+ except (OSError, subprocess.TimeoutExpired):
361
+ return "", False
362
+
363
+
364
+ # ---------------------------------------------------------------------------
365
+ # Stdout setup
366
+ # ---------------------------------------------------------------------------
367
+
368
+ def ensure_utf8_stdout():
369
+ """Ensure stdout uses UTF-8 encoding (needed on Windows)."""
370
+ if sys.stdout.encoding != "utf-8":
371
+ sys.stdout = io.TextIOWrapper(
372
+ sys.stdout.buffer, encoding="utf-8", errors="replace"
373
+ )
374
+
375
+
376
+ # ---------------------------------------------------------------------------
377
+ # Script import helper
378
+ # ---------------------------------------------------------------------------
379
+
380
+ def import_sibling(name):
381
+ """Import a sibling script by filename (handles hyphens).
382
+
383
+ Example: import_sibling("gator-fleet-report") imports gator-fleet-report.py
384
+ from the same directory as the calling script.
385
+
386
+ Returns the module on success, None if the file doesn't exist.
387
+ Raises ImportError with diagnostic context if the file exists but
388
+ fails to load — callers should catch this if they want resilience.
389
+ """
390
+ import importlib.util
391
+
392
+ scripts_dir = Path(__file__).resolve().parent
393
+ path = scripts_dir / f"{name}.py"
394
+ if not path.exists():
395
+ return None
396
+
397
+ spec = importlib.util.spec_from_file_location(
398
+ name.replace("-", "_"), path
399
+ )
400
+ mod = importlib.util.module_from_spec(spec)
401
+ try:
402
+ spec.loader.exec_module(mod)
403
+ except Exception as e:
404
+ raise ImportError(
405
+ f"Failed to load {path.name}: {type(e).__name__}: {e}"
406
+ ) from e
407
+ return mod
408
+
409
+
410
+ # ---------------------------------------------------------------------------
411
+ # Charter surface resolution
412
+ # ---------------------------------------------------------------------------
413
+
414
+ def resolve_charter_surface(repo_root=None):
415
+ """Return the authoritative charter surface for this repo.
416
+
417
+ Two modes:
418
+ - source-command-post: the gator-command development repo.
419
+ Charters at gator-command/charters/, cross-cutting is
420
+ scripts-cross-cutting.md.
421
+ - governed-repo: all other repos (fleet repos, deployed command posts).
422
+ Charters at .gator/charters/, cross-cutting is cross-cutting.md.
423
+
424
+ Returns dict with:
425
+ mode: "source-command-post" | "governed-repo"
426
+ charter_dir: Path to authoritative charter directory
427
+ cross_cutting: filename of the cross-cutting charter (or None)
428
+ index_file: Path to INDEX.md (or None if absent)
429
+ """
430
+ if repo_root is None:
431
+ repo_root = Path.cwd()
432
+ else:
433
+ repo_root = Path(repo_root)
434
+
435
+ # Detect source-command-post mode: gator-command/charters/ exists AND
436
+ # scripts exist (at src/gator_command/scripts/ or legacy gator-command/scripts/).
437
+ cp_charters = repo_root / "gator-command" / "charters"
438
+ cp_scripts = repo_root / "src" / "gator_command" / "scripts"
439
+ if not cp_scripts.is_dir():
440
+ cp_scripts = repo_root / "gator-command" / "scripts"
441
+ if cp_charters.is_dir() and cp_scripts.is_dir():
442
+ cross_cutting = None
443
+ for f in cp_charters.iterdir():
444
+ if "cross-cutting" in f.name and f.suffix == ".md":
445
+ cross_cutting = f.name
446
+ break
447
+ index_file = cp_charters / "INDEX.md"
448
+ return {
449
+ "mode": "source-command-post",
450
+ "charter_dir": cp_charters,
451
+ "cross_cutting": cross_cutting,
452
+ "index_file": index_file if index_file.is_file() else None,
453
+ }
454
+
455
+ # Default: governed-repo mode
456
+ charter_dir = repo_root / ".gator" / "charters"
457
+ cross_cutting = None
458
+ if charter_dir.is_dir():
459
+ for f in charter_dir.iterdir():
460
+ if "cross-cutting" in f.name and f.suffix == ".md":
461
+ cross_cutting = f.name
462
+ break
463
+ index_file = charter_dir / "INDEX.md"
464
+ return {
465
+ "mode": "governed-repo",
466
+ "charter_dir": charter_dir,
467
+ "cross_cutting": cross_cutting,
468
+ "index_file": index_file if index_file.is_file() else None,
469
+ }
470
+
471
+
472
+ # ---------------------------------------------------------------------------
473
+ # Branding
474
+ # ---------------------------------------------------------------------------
475
+
476
+ # Template generation — single source of truth for Python scripts.
477
+ # gatorize.sh reads this value at runtime via grep.
478
+ # Gen 0: pre-gatorize.sh installs (no command-post.md, no .gator-version)
479
+ # Gen 1: first gatorize.sh installs (has command-post.md, no .gator-version)
480
+ # Gen 2: current (has .gator-version, overlay-not-replace upgrade logic)
481
+ CURRENT_GENERATION = 2
482
+
483
+ GATOR_MARK_LINES = [
484
+ r" .... . ..... ... .... ",
485
+ r" / ___| / \|_ _|/ _ \| _ \ .... ",
486
+ r"| | _ / _ \ | | | | | | |_) | ./( o )\_______",
487
+ r"| |_/ \/ /_\ \| | | |_| | _ < _/vVvVvVvV",
488
+ r" \____/m/ \m\_| \___/|m| \m\ \__.---------",
489
+ ]