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,487 @@
|
|
|
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/ AND
|
|
436
|
+
# gator-command/scripts/ both exist. This is the development repo.
|
|
437
|
+
cp_charters = repo_root / "gator-command" / "charters"
|
|
438
|
+
cp_scripts = repo_root / "gator-command" / "scripts"
|
|
439
|
+
if cp_charters.is_dir() and cp_scripts.is_dir():
|
|
440
|
+
cross_cutting = None
|
|
441
|
+
for f in cp_charters.iterdir():
|
|
442
|
+
if "cross-cutting" in f.name and f.suffix == ".md":
|
|
443
|
+
cross_cutting = f.name
|
|
444
|
+
break
|
|
445
|
+
index_file = cp_charters / "INDEX.md"
|
|
446
|
+
return {
|
|
447
|
+
"mode": "source-command-post",
|
|
448
|
+
"charter_dir": cp_charters,
|
|
449
|
+
"cross_cutting": cross_cutting,
|
|
450
|
+
"index_file": index_file if index_file.is_file() else None,
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
# Default: governed-repo mode
|
|
454
|
+
charter_dir = repo_root / ".gator" / "charters"
|
|
455
|
+
cross_cutting = None
|
|
456
|
+
if charter_dir.is_dir():
|
|
457
|
+
for f in charter_dir.iterdir():
|
|
458
|
+
if "cross-cutting" in f.name and f.suffix == ".md":
|
|
459
|
+
cross_cutting = f.name
|
|
460
|
+
break
|
|
461
|
+
index_file = charter_dir / "INDEX.md"
|
|
462
|
+
return {
|
|
463
|
+
"mode": "governed-repo",
|
|
464
|
+
"charter_dir": charter_dir,
|
|
465
|
+
"cross_cutting": cross_cutting,
|
|
466
|
+
"index_file": index_file if index_file.is_file() else None,
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
# ---------------------------------------------------------------------------
|
|
471
|
+
# Branding
|
|
472
|
+
# ---------------------------------------------------------------------------
|
|
473
|
+
|
|
474
|
+
# Template generation — single source of truth for Python scripts.
|
|
475
|
+
# gatorize.sh reads this value at runtime via grep.
|
|
476
|
+
# Gen 0: pre-gatorize.sh installs (no command-post.md, no .gator-version)
|
|
477
|
+
# Gen 1: first gatorize.sh installs (has command-post.md, no .gator-version)
|
|
478
|
+
# Gen 2: current (has .gator-version, overlay-not-replace upgrade logic)
|
|
479
|
+
CURRENT_GENERATION = 2
|
|
480
|
+
|
|
481
|
+
GATOR_MARK_LINES = [
|
|
482
|
+
r" .... . ..... ... .... ",
|
|
483
|
+
r" / ___| / \|_ _|/ _ \| _ \ .... ",
|
|
484
|
+
r"| | _ / _ \ | | | | | | |_) | ./( o )\_______",
|
|
485
|
+
r"| |_/ \/ /_\ \| | | |_| | _ < _/vVvVvVvV",
|
|
486
|
+
r" \____/m/ \m\_| \___/|m| \m\ \__.---------",
|
|
487
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Gator post-commit hook — resets commit_draft.md after successful commit.
|
|
3
|
+
# Installed by gatorize.sh. Do not edit manually.
|
|
4
|
+
#
|
|
5
|
+
# Cleanup now runs in Python so the shell wrapper stays minimal.
|
|
6
|
+
|
|
7
|
+
python .gator/scripts/gator-pre-commit.py --phase cleanup
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Rolling active session ledgers are local working artifacts.
|
|
2
|
+
# They are NOT committed — only session snippets and summaries are git-tracked.
|
|
3
|
+
_active/
|
|
4
|
+
|
|
5
|
+
# NOTE: .gator/session-snippets/ is NOT gitignored. Snippets are durable
|
|
6
|
+
# commit-linked artifacts that MUST be committed. They provide the traceability
|
|
7
|
+
# bridge between session history and git history.
|
|
File without changes
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gator-command
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Git-native governance for AI-assisted engineering
|
|
5
|
+
Author-email: Cumberland Laboratories <support@cumberlandlaboratories.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/cumberland-laboratories/gator
|
|
8
|
+
Project-URL: Repository, https://github.com/cumberland-laboratories/gator
|
|
9
|
+
Keywords: governance,ai,git,charters,audit,compliance
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
19
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# Gator Command
|
|
26
|
+
|
|
27
|
+
The command post for [Gator](https://github.com/cumberland-laboratories/gator) — AI-assisted engineering governance.
|
|
28
|
+
|
|
29
|
+
## How to Use This Repo
|
|
30
|
+
|
|
31
|
+
**Open it in an AI coding assistant.** Claude Code, Codex, Gemini CLI, Cursor — whichever you use. The agent reads the constitution and acts as your concierge: it knows the architecture, the configuration options, the procedures, and the fleet state. Ask it what you need.
|
|
32
|
+
|
|
33
|
+
Gator is designed to be operated *with* an AI agent, not studied as documentation. The governance layer is markdown that agents read natively. The concierge layer routes your questions to the right procedures and reference material. The agent is the interface.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
You: "gator init"
|
|
37
|
+
You: "Gatorize my project at ~/code/my-app"
|
|
38
|
+
You: "Set up an enforcer using GPT as the reviewer"
|
|
39
|
+
You: "Check if the charters still match the code"
|
|
40
|
+
You: "What's the status of the fleet?"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## What Gator Does
|
|
44
|
+
|
|
45
|
+
Gator governs AI-assisted coding. It keeps human comprehension synchronized with AI-generated code through constitutions, module charters, deterministic commit gates, and cross-model enforcement.
|
|
46
|
+
|
|
47
|
+
### Use Cases
|
|
48
|
+
|
|
49
|
+
**Solo developer with one repo** — You want the AI agent to maintain a map of your codebase (charters), enforce that the map stays current (commit gate), and preserve session context across conversations (knowledge layer). Install Gator, bootstrap charters, and the loop runs from there.
|
|
50
|
+
|
|
51
|
+
**Solo developer with multiple repos** — Same as above, plus: this command post becomes your cross-repo control plane. One org policy propagated to every repo. Fleet status, drift detection, and audit dashboards from one place.
|
|
52
|
+
|
|
53
|
+
**Team with shared repos** — Gator's knowledge layer travels with the repo via git. When a teammate (human or AI) opens the repo, they get the full architectural context — mission, charters, threads, decisions. The enforcer provides independent review from a different model than the one writing code.
|
|
54
|
+
|
|
55
|
+
**Compliance and audit** — Every commit carries `Gator-*` trailers (change type, significance, charter status, agent identity). Session summaries provide durable evidence of what was decided and who was supervising. Fleet-wide audit reports aggregate governance health across repos.
|
|
56
|
+
|
|
57
|
+
### Configurations
|
|
58
|
+
|
|
59
|
+
Gator is model-neutral and modular. The PI chooses the configuration that fits their stack:
|
|
60
|
+
|
|
61
|
+
| Decision | Options |
|
|
62
|
+
|----------|---------|
|
|
63
|
+
| **Primary agent** | Claude Code, Codex CLI, Gemini CLI, Cursor, any markdown-aware AI tool |
|
|
64
|
+
| **Enforcer model** | Anthropic Sonnet, OpenAI GPT, Google Gemini, local via Ollama, or none |
|
|
65
|
+
| **Review cadence** | Light (lint only), Standard (lint + model review on significant changes), Rigorous (pre-code + post-code review) |
|
|
66
|
+
| **Fleet scope** | Single repo (no command post needed), multi-repo (this command post), remote repos (bare cache scanning) |
|
|
67
|
+
| **Session archaeology** | Off, local spool, committed summaries (git-tracked audit trail) |
|
|
68
|
+
|
|
69
|
+
The concierge agent walks you through these choices. You don't need to read configuration docs — tell the agent what you want and it configures the system.
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Gatorize an existing project repo
|
|
75
|
+
bash gator-command/scripts/gatorize.sh /path/to/your/project
|
|
76
|
+
|
|
77
|
+
# Then open that project in your AI assistant — the concierge bootstrap begins
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
For fleet operations (from within this command post):
|
|
81
|
+
```bash
|
|
82
|
+
# Fleet status
|
|
83
|
+
python gator-command/scripts/gator-fleet-report.py
|
|
84
|
+
|
|
85
|
+
# Policy drift across all governed repos
|
|
86
|
+
python gator-command/scripts/gator-drift.py
|
|
87
|
+
|
|
88
|
+
# Audit dashboard
|
|
89
|
+
python gator-command/scripts/gator-audit.py
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Architecture at a Glance
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
This repo (command post):
|
|
96
|
+
constitution.md Governance rules for this command post
|
|
97
|
+
gator-command/ Knowledge graph + machinery + fleet tooling
|
|
98
|
+
scripts/ CLI tooling (gatorize, fleet-report, drift, audit)
|
|
99
|
+
templates/ Starter kit deployed to project repos
|
|
100
|
+
procedures/ Standards and workflows
|
|
101
|
+
reference-notes/ Concierge knowledge (the agent reads these)
|
|
102
|
+
|
|
103
|
+
Each governed repo:
|
|
104
|
+
.gator/
|
|
105
|
+
constitution.md Per-repo governance rules
|
|
106
|
+
charters/ Module maps (the intelligent codebase layer)
|
|
107
|
+
scripts/ Hooks + enforcer
|
|
108
|
+
field-guides/ Language pattern references (optional)
|
|
109
|
+
command-post.md Thin link back to this command post
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Multi-Model Architecture
|
|
113
|
+
|
|
114
|
+
The constitutions are the interface contract. Any model that reads markdown and follows instructions can operate the system.
|
|
115
|
+
|
|
116
|
+
- **Primary agent**: Reads charters, writes code, updates the knowledge layer
|
|
117
|
+
- **Enforcer**: Different model, read-only, audits the agent's work against charters
|
|
118
|
+
- **The architecture outlives any individual model** — switch tools mid-project without losing context
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT — [Cumberland Laboratories](https://github.com/cumberland-laboratories)
|