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,139 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ gator-approve.py — Architect-only override approval for blocked commits.
4
+
5
+ When the pre-commit hook blocks a commit, it writes an override request
6
+ to .gator/override-request.json with a unique block ID. The Architect reviews
7
+ the findings, then runs this script to approve the override.
8
+
9
+ The agent must NOT run this script. The constitution forbids it.
10
+ Unauthorized self-approval is an auditable governance violation.
11
+
12
+ Usage:
13
+ python .gator/scripts/gator-approve.py
14
+ python .gator/scripts/gator-approve.py --reason "trivial whitespace change"
15
+ python .gator/scripts/gator-approve.py --reason "bug fix" --name "Architect"
16
+
17
+ The script reads the pending override request, confirms with the Architect,
18
+ and writes .gator/override-approved.json. The next git commit attempt
19
+ will accept the override if the block IDs match and the approval is
20
+ newer than the request.
21
+ """
22
+
23
+ import io
24
+ import json
25
+ import sys
26
+ import time
27
+ from pathlib import Path
28
+
29
+
30
+ def _ensure_utf8_stdout():
31
+ """Ensure stdout uses UTF-8 encoding (needed on Windows)."""
32
+ if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
33
+ sys.stdout = io.TextIOWrapper(
34
+ sys.stdout.buffer, encoding="utf-8", errors="replace"
35
+ )
36
+
37
+
38
+ def find_gator_dir():
39
+ """Walk up from cwd to find .gator/."""
40
+ d = Path.cwd().resolve()
41
+ for _ in range(10):
42
+ if (d / ".gator").is_dir():
43
+ return d / ".gator"
44
+ d = d.parent
45
+ return None
46
+
47
+
48
+ def main():
49
+ _ensure_utf8_stdout()
50
+
51
+ gator_dir = find_gator_dir()
52
+ if not gator_dir:
53
+ print(" Error: no .gator/ found.", file=sys.stderr)
54
+ sys.exit(1)
55
+
56
+ request_file = gator_dir / "override-request.json"
57
+ if not request_file.exists():
58
+ print(" No pending override request.")
59
+ print(" Override requests are created when the pre-commit hook blocks a commit.")
60
+ sys.exit(1)
61
+
62
+ request = json.loads(request_file.read_text(encoding="utf-8"))
63
+ block_id = request.get("block_id", "unknown")
64
+ failure_type = request.get("failure_type", "unknown")
65
+ files = request.get("files", [])
66
+ override_type = request.get("override_type", "charter-skip")
67
+ timestamp = request.get("timestamp", "unknown")
68
+
69
+ print()
70
+ print(" gator override approval")
71
+ print()
72
+ print(f" Block ID: {block_id}")
73
+ print(f" Failure: {failure_type}")
74
+ print(f" Override: {override_type}")
75
+ print(f" Blocked at: {timestamp}")
76
+ if files:
77
+ print(f" Files: {', '.join(files[:5])}")
78
+ print()
79
+
80
+ # Parse --reason and --name from args
81
+ reason = ""
82
+ pi_name = ""
83
+ args = sys.argv[1:]
84
+ for i, arg in enumerate(args):
85
+ if arg == "--reason" and i + 1 < len(args):
86
+ reason = args[i + 1]
87
+ elif arg == "--name" and i + 1 < len(args):
88
+ pi_name = args[i + 1]
89
+
90
+ # Prompt for missing values
91
+ if not reason:
92
+ try:
93
+ reason = input(" Reason for override (required): ").strip()
94
+ except (EOFError, KeyboardInterrupt):
95
+ print("\n Cancelled.")
96
+ sys.exit(1)
97
+
98
+ if not reason:
99
+ print(" Error: reason is required for override approval.")
100
+ sys.exit(1)
101
+
102
+ if not pi_name:
103
+ try:
104
+ pi_name = input(" Your name (Architect): ").strip()
105
+ except (EOFError, KeyboardInterrupt):
106
+ print("\n Cancelled.")
107
+ sys.exit(1)
108
+
109
+ if not pi_name:
110
+ print(" Error: Architect name is required.")
111
+ sys.exit(1)
112
+
113
+ # Write approval
114
+ approval = {
115
+ "block_id": block_id,
116
+ "override_type": override_type,
117
+ "approved_by": pi_name,
118
+ "reason": reason,
119
+ "approved_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
120
+ "request_timestamp": timestamp,
121
+ }
122
+
123
+ approved_file = gator_dir / "override-approved.json"
124
+ approved_file.write_text(
125
+ json.dumps(approval, indent=2) + "\n",
126
+ encoding="utf-8",
127
+ )
128
+
129
+ print()
130
+ print(f" \u2713 Override approved by {pi_name}")
131
+ print(f" Block ID: {block_id}")
132
+ print(f" Reason: {reason}")
133
+ print()
134
+ print(" Now retry: git commit")
135
+ print()
136
+
137
+
138
+ if __name__ == "__main__":
139
+ main()
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ gator-enforce.py — Set the enforcement level for a Gator-governed repo.
4
+
5
+ Usage:
6
+ python gator-enforce.py --level strict|warn|off [--repo /path/to/repo]
7
+
8
+ Levels:
9
+ strict — CRITICAL/HIGH findings block commits (default)
10
+ warn — all findings reported but nothing blocks
11
+ off — governance checks skipped, trailers still written
12
+
13
+ Writes to .gator/config.json in the target repo.
14
+ """
15
+
16
+ import argparse
17
+ import json
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ VALID_LEVELS = {"strict", "warn", "off"}
22
+
23
+
24
+ def find_gator_dir(repo_path=None):
25
+ """Find .gator/ directory from the given path or cwd."""
26
+ start = Path(repo_path) if repo_path else Path.cwd()
27
+ start = start.resolve()
28
+
29
+ if (start / ".gator").is_dir():
30
+ return start / ".gator"
31
+ for parent in start.parents:
32
+ if (parent / ".gator").is_dir():
33
+ return parent / ".gator"
34
+ return None
35
+
36
+
37
+ def main():
38
+ parser = argparse.ArgumentParser(description="Set Gator enforcement level")
39
+ parser.add_argument("--level", required=True, choices=sorted(VALID_LEVELS),
40
+ help="Enforcement level: strict, warn, or off")
41
+ parser.add_argument("--repo", default=None,
42
+ help="Path to the repo (default: current directory)")
43
+ args = parser.parse_args()
44
+
45
+ gator_dir = find_gator_dir(args.repo)
46
+ if not gator_dir:
47
+ print("Error: No .gator/ directory found.", file=sys.stderr)
48
+ print("Are you in a Gator-governed repo?", file=sys.stderr)
49
+ sys.exit(1)
50
+
51
+ config_path = gator_dir / "config.json"
52
+
53
+ # Read existing config or start fresh
54
+ config = {}
55
+ if config_path.exists():
56
+ try:
57
+ config = json.loads(config_path.read_text(encoding="utf-8"))
58
+ except (json.JSONDecodeError, OSError):
59
+ config = {}
60
+
61
+ old_level = config.get("enforcement_level", "strict")
62
+ config["enforcement_level"] = args.level
63
+
64
+ config_path.write_text(
65
+ json.dumps(config, indent=2) + "\n", encoding="utf-8"
66
+ )
67
+
68
+ if old_level == args.level:
69
+ print(f" Enforcement level unchanged: {args.level}")
70
+ else:
71
+ print(f" Enforcement level: {old_level} -> {args.level}")
72
+
73
+ if args.level == "strict":
74
+ print(" CRITICAL/HIGH findings will block commits.")
75
+ elif args.level == "warn":
76
+ print(" Findings will be reported but commits will not be blocked.")
77
+ else:
78
+ print(" Governance checks disabled. Trailers still written.")
79
+
80
+
81
+ if __name__ == "__main__":
82
+ main()
@@ -0,0 +1,434 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ gator init — Branded boot sequence for Gator-governed repos.
4
+
5
+ Detects .gator/ in the current directory (or a specified path), reads
6
+ the knowledge layer, and prints a formatted status display. Designed
7
+ to run at session open in any AI CLI (Claude Code, Codex, Gemini).
8
+
9
+ Usage:
10
+ python .gator/scripts/gator-init.py
11
+ python .gator/scripts/gator-init.py --path /some/repo
12
+ python .gator/scripts/gator-init.py --json
13
+
14
+ @reads: .gator/ directory structure, constitution.md, charters/, threads/, command-post.md
15
+ @writes: .git/hooks/ (self-heal only when hooks are missing or stale)
16
+ """
17
+
18
+ import argparse
19
+ import json
20
+ import re
21
+ import sys
22
+ from pathlib import Path
23
+
24
+ from gator_core import (
25
+ get_version, find_gator_root, ensure_utf8_stdout, GATOR_MARK_LINES,
26
+ import_sibling,
27
+ )
28
+
29
+ VERSION = get_version()
30
+
31
+ TAGLINE = "oriented. the terrain is mapped."
32
+
33
+
34
+ # --- Detection and counting ---
35
+
36
+ def count_constitution_rules(gator_dir):
37
+ """Count enforceable rules in the constitution.
38
+
39
+ Counts numbered list items, table rows with content, and bold-prefixed
40
+ rules. This is a heuristic — not a parser.
41
+
42
+ @reads: .gator/constitution.md
43
+ """
44
+ constitution = gator_dir / "constitution.md"
45
+ if not constitution.exists():
46
+ return 0
47
+
48
+ text = constitution.read_text(encoding="utf-8", errors="replace")
49
+ count = 0
50
+
51
+ for line in text.splitlines():
52
+ stripped = line.strip()
53
+ # Numbered steps (1. **Before changing code**: ...)
54
+ if re.match(r'^\d+\.?\s+\*\*', stripped):
55
+ count += 1
56
+ # Bold-prefixed rules (- **BRANCHING**: ...)
57
+ elif re.match(r'^-\s+\*\*[A-Z]', stripped):
58
+ count += 1
59
+ # Standalone bold imperatives (**The agent always asks before committing**)
60
+ elif re.match(r'^\*\*The (agent|Architect|PI|enforcer)', stripped):
61
+ count += 1
62
+
63
+ return count
64
+
65
+
66
+ def count_charters(gator_dir):
67
+ """Count charter files and estimate mapping coverage.
68
+
69
+ @reads: .gator/charters/
70
+ """
71
+ charters_dir = gator_dir / "charters"
72
+ if not charters_dir.is_dir():
73
+ return 0, 0, 0.0
74
+
75
+ skip = {"_template.md", "README.md", "INDEX.md"}
76
+ charter_files = [
77
+ f for f in charters_dir.iterdir()
78
+ if f.suffix == ".md" and f.name not in skip
79
+ ]
80
+
81
+ # Count function entries across all charters (lines starting with ### )
82
+ function_count = 0
83
+ for cf in charter_files:
84
+ text = cf.read_text(encoding="utf-8", errors="replace")
85
+ for line in text.splitlines():
86
+ if line.strip().startswith("### ") and "(" in line:
87
+ function_count += 1
88
+
89
+ # Estimate total functions in the repo (count def/func/function declarations)
90
+ repo_root = gator_dir.parent
91
+ total_functions = 0
92
+ for ext in ("*.py", "*.gd", "*.js", "*.ts", "*.go", "*.rs", "*.java"):
93
+ for code_file in repo_root.rglob(ext):
94
+ # Skip .gator/ itself
95
+ if ".gator" in str(code_file):
96
+ continue
97
+ try:
98
+ text = code_file.read_text(encoding="utf-8", errors="replace")
99
+ for line in text.splitlines():
100
+ stripped = line.strip()
101
+ if (stripped.startswith("def ") or
102
+ stripped.startswith("func ") or
103
+ stripped.startswith("function ") or
104
+ re.match(r'^(pub\s+)?(fn|func)\s+', stripped) or
105
+ re.match(r'^(export\s+)?(async\s+)?function\s+', stripped)):
106
+ total_functions += 1
107
+ except (OSError, UnicodeDecodeError):
108
+ continue
109
+
110
+ coverage = (function_count / total_functions * 100) if total_functions > 0 else 0.0
111
+
112
+ return len(charter_files), function_count, coverage
113
+
114
+
115
+ def count_working_set(gator_dir):
116
+ """Count threads and their total line count.
117
+
118
+ @reads: .gator/threads/, .gator/active-threads/ (if present)
119
+ """
120
+ total_threads = 0
121
+ total_lines = 0
122
+
123
+ for subdir_name in ("active-threads", "threads"):
124
+ subdir = gator_dir / subdir_name
125
+ if not subdir.is_dir():
126
+ continue
127
+ for f in subdir.iterdir():
128
+ if f.suffix == ".md" and f.name != ".gitkeep":
129
+ total_threads += 1
130
+ try:
131
+ total_lines += len(f.read_text(encoding="utf-8", errors="replace").splitlines())
132
+ except (OSError, UnicodeDecodeError):
133
+ pass
134
+
135
+ return total_threads, total_lines
136
+
137
+
138
+ def detect_enforcer(gator_dir):
139
+ """Detect enforcer configuration.
140
+
141
+ @reads: .gator/scripts/enforcer-prompt.md, .gator/scripts/enforcer-review.py
142
+ """
143
+ has_prompt = (gator_dir / "scripts" / "enforcer-prompt.md").exists()
144
+ has_script = (gator_dir / "scripts" / "enforcer-review.py").exists()
145
+
146
+ if has_prompt and has_script:
147
+ return "ready"
148
+ elif has_prompt or has_script:
149
+ return "partial"
150
+ else:
151
+ return "not configured"
152
+
153
+
154
+ def count_field_guides(gator_dir):
155
+ """Count field guide languages from pattern files.
156
+
157
+ Field guides use two files per language: {lang}-patterns.md and
158
+ {lang}-tutorial.md. Count languages from pattern files.
159
+
160
+ @reads: .gator/field-guides/
161
+ """
162
+ guides_dir = gator_dir / "field-guides"
163
+ if not guides_dir.is_dir():
164
+ return 0, []
165
+
166
+ guides = [
167
+ f.name.removesuffix("-patterns.md")
168
+ for f in guides_dir.iterdir()
169
+ if f.name.endswith("-patterns.md")
170
+ ]
171
+ return len(guides), sorted(guides)
172
+
173
+
174
+ def detect_command_post(gator_dir):
175
+ """Check for command-post thin link.
176
+
177
+ @reads: .gator/command-post.md
178
+ """
179
+ cp_file = gator_dir / "command-post.md"
180
+ if not cp_file.exists():
181
+ return None
182
+
183
+ text = cp_file.read_text(encoding="utf-8", errors="replace")
184
+ for line in text.splitlines():
185
+ if line.startswith("command-post:"):
186
+ return line.split(":", 1)[1].strip()
187
+ return "linked"
188
+
189
+
190
+ def read_version(gator_dir):
191
+ """Read generation from .gator-version.
192
+
193
+ @reads: .gator/.gator-version
194
+ """
195
+ version_file = gator_dir / ".gator-version"
196
+ if not version_file.exists():
197
+ return None
198
+
199
+ text = version_file.read_text(encoding="utf-8", errors="replace")
200
+ info = {}
201
+ for line in text.splitlines():
202
+ if ":" in line:
203
+ key, val = line.split(":", 1)
204
+ info[key.strip()] = val.strip()
205
+ return info
206
+
207
+
208
+ def ensure_git_hooks(repo_root, gator_dir):
209
+ """Self-heal git hooks at session start.
210
+
211
+ Uses gator-update's hook planning and installation logic so there is
212
+ exactly one definition of what "correctly installed hooks" means.
213
+ """
214
+ try:
215
+ gator_update = import_sibling("gator-update")
216
+ except ImportError as e:
217
+ return {
218
+ "status": "error",
219
+ "detail": f"hook repair unavailable: {e}",
220
+ "adds": 0,
221
+ "updates": 0,
222
+ }
223
+ if gator_update is None:
224
+ return {
225
+ "status": "error",
226
+ "detail": "gator-update.py not found",
227
+ "adds": 0,
228
+ "updates": 0,
229
+ }
230
+
231
+ if not (repo_root / ".git").exists():
232
+ return {
233
+ "status": "unavailable",
234
+ "detail": "no .git directory",
235
+ "adds": 0,
236
+ "updates": 0,
237
+ }
238
+
239
+ # Check that the hook target script exists — without it, hooks can't work
240
+ # even if they're installed, and plan_hook_updates returns [] (masking the problem).
241
+ gator_script = gator_dir / "scripts" / "gator-pre-commit.py"
242
+ if not gator_script.exists():
243
+ return {
244
+ "status": "degraded",
245
+ "detail": "gator-pre-commit.py missing",
246
+ "adds": 0,
247
+ "updates": 0,
248
+ }
249
+
250
+ plan = gator_update.plan_hook_updates(gator_dir, repo_root)
251
+ adds = sum(1 for _, action in plan if action == "add")
252
+ updates = sum(1 for _, action in plan if action == "update")
253
+
254
+ if adds or updates:
255
+ installed = gator_update.install_git_hooks(gator_dir, repo_root)
256
+ if updates:
257
+ detail = f"refreshed ({installed} hooks)"
258
+ status = "refreshed"
259
+ else:
260
+ detail = f"installed ({installed} hooks)"
261
+ status = "installed"
262
+ else:
263
+ detail = "ok"
264
+ status = "ok"
265
+
266
+ return {
267
+ "status": status,
268
+ "detail": detail,
269
+ "adds": adds,
270
+ "updates": updates,
271
+ }
272
+
273
+
274
+ # --- Output formatting ---
275
+
276
+ def format_check(label, value, width=16):
277
+ """Format a single status line."""
278
+ return f" \u2713 {label:<{width}}{value}"
279
+
280
+
281
+ def print_boot_sequence(repo_root, gator_dir, hook_status):
282
+ """Print the branded boot sequence.
283
+
284
+ @reads: all detection functions above
285
+ @writes: stdout
286
+ """
287
+ repo_name = repo_root.name
288
+
289
+ # Header
290
+ print()
291
+ for line in GATOR_MARK_LINES:
292
+ print(f" {line}")
293
+ print()
294
+ print(f" {VERSION} · navigation coding, governed")
295
+ print()
296
+ print(f" $ gator init")
297
+ print()
298
+
299
+ # Detection
300
+ print(f" found .gator/")
301
+ print()
302
+
303
+ # Constitution
304
+ rule_count = count_constitution_rules(gator_dir)
305
+ print(format_check("constitution", f"{rule_count} rules in force"))
306
+
307
+ # Charters
308
+ charter_count, func_count, coverage = count_charters(gator_dir)
309
+ if charter_count > 0:
310
+ detail = f"{charter_count} modules \u00b7 {func_count} functions documented"
311
+ print(format_check("charters", detail))
312
+ else:
313
+ print(format_check("charters", "empty \u00b7 ready for bootstrap"))
314
+
315
+ # Working set
316
+ thread_count, line_count = count_working_set(gator_dir)
317
+ if thread_count > 0:
318
+ print(format_check("working set", f"{thread_count} threads \u00b7 {line_count:,} lines"))
319
+ else:
320
+ print(format_check("working set", "empty \u00b7 threads emerge from work"))
321
+
322
+ # Field guides (only show when present)
323
+ guide_count, guide_langs = count_field_guides(gator_dir)
324
+ if guide_count > 0:
325
+ lang_list = ", ".join(guide_langs)
326
+ print(format_check("field guides", f"{guide_count} languages ({lang_list})"))
327
+
328
+ # Enforcer
329
+ enforcer_status = detect_enforcer(gator_dir)
330
+ print(format_check("enforcer", enforcer_status))
331
+
332
+ # Git hooks
333
+ print(format_check("hooks", hook_status["detail"]))
334
+
335
+ # Command post
336
+ cp = detect_command_post(gator_dir)
337
+ if cp:
338
+ print(format_check("command post", "linked"))
339
+
340
+ # Tagline
341
+ print()
342
+ print(f" {TAGLINE}")
343
+ print(f" \u25b8")
344
+ print()
345
+
346
+
347
+ def print_json(repo_root, gator_dir, hook_status):
348
+ """Print status as JSON for programmatic consumption."""
349
+ rule_count = count_constitution_rules(gator_dir)
350
+ charter_count, func_count, coverage = count_charters(gator_dir)
351
+ thread_count, line_count = count_working_set(gator_dir)
352
+ guide_count, guide_langs = count_field_guides(gator_dir)
353
+ enforcer_status = detect_enforcer(gator_dir)
354
+ cp = detect_command_post(gator_dir)
355
+ version_info = read_version(gator_dir)
356
+
357
+ data = {
358
+ "version": VERSION,
359
+ "repo": repo_root.name,
360
+ "repo_path": str(repo_root),
361
+ "constitution_rules": rule_count,
362
+ "charters": {
363
+ "modules": charter_count,
364
+ "functions_mapped": func_count,
365
+ "coverage_pct": round(coverage, 1),
366
+ },
367
+ "working_set": {
368
+ "threads": thread_count,
369
+ "lines": line_count,
370
+ },
371
+ "field_guides": {
372
+ "count": guide_count,
373
+ "languages": guide_langs,
374
+ },
375
+ "enforcer": enforcer_status,
376
+ "hooks": hook_status,
377
+ "command_post": cp,
378
+ "gator_version": version_info,
379
+ }
380
+
381
+ print(json.dumps(data, indent=2))
382
+
383
+
384
+ def print_not_found():
385
+ """Print message when .gator/ is not found."""
386
+ print()
387
+ for line in GATOR_MARK_LINES:
388
+ print(f" {line}")
389
+ print()
390
+ print(f" {VERSION} · navigation coding, governed")
391
+ print()
392
+ print(" no .gator/ found.")
393
+ print()
394
+ print(" to gatorize this repo:")
395
+ print(" bash gator-command/scripts/gatorize.sh .")
396
+ print()
397
+
398
+
399
+ # --- Entry point ---
400
+
401
+ def main():
402
+ ensure_utf8_stdout()
403
+
404
+ parser = argparse.ArgumentParser(
405
+ description="Gator init — branded boot sequence for governed repos."
406
+ )
407
+ parser.add_argument(
408
+ "--path", "-p",
409
+ help="Path to search for .gator/ (default: current directory)",
410
+ )
411
+ parser.add_argument(
412
+ "--json", "-j",
413
+ action="store_true",
414
+ help="Output as JSON instead of formatted display",
415
+ )
416
+ args = parser.parse_args()
417
+
418
+ repo_root = find_gator_root(args.path)
419
+
420
+ if not repo_root:
421
+ print_not_found()
422
+ sys.exit(1)
423
+
424
+ gator_dir = repo_root / ".gator"
425
+ hook_status = ensure_git_hooks(repo_root, gator_dir)
426
+
427
+ if args.json:
428
+ print_json(repo_root, gator_dir, hook_status)
429
+ else:
430
+ print_boot_sequence(repo_root, gator_dir, hook_status)
431
+
432
+
433
+ if __name__ == "__main__":
434
+ main()