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,427 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ gator-charter-lint — validate charter files against Charter Schema v1.
4
+
5
+ Usage:
6
+ python gator-charter-lint.py [--json] [--charter PATH] [--charters-dir DIR]
7
+
8
+ Structural validation only — checks that required sections exist and function
9
+ entries are well-formed. Does not check content quality, file path resolution,
10
+ or function name existence.
11
+
12
+ @reads: .gator/charters/*.md, gator-command/charters/*.md
13
+ """
14
+
15
+ import argparse
16
+ import json
17
+ import re
18
+ import sys
19
+ from dataclasses import dataclass, field, asdict
20
+ from pathlib import Path
21
+
22
+ SCRIPTS_DIR = Path(__file__).resolve().parent
23
+ sys.path.insert(0, str(SCRIPTS_DIR))
24
+
25
+ try:
26
+ from gator_core import ensure_utf8_stdout
27
+ except ImportError:
28
+ def ensure_utf8_stdout():
29
+ pass
30
+
31
+ SKIP_FILES = {"_template.md", "README.md"}
32
+
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # Data structures
36
+ # ---------------------------------------------------------------------------
37
+
38
+ @dataclass
39
+ class Finding:
40
+ check: str
41
+ severity: str # error, warn, info
42
+ line: int
43
+ message: str
44
+
45
+
46
+ @dataclass
47
+ class FunctionEntry:
48
+ name: str
49
+ line: int
50
+ has_file_line: bool = False
51
+ has_description: bool = False
52
+ has_annotations: bool = False
53
+
54
+
55
+ @dataclass
56
+ class CharterDoc:
57
+ path: str = ""
58
+ lines: list = field(default_factory=list)
59
+ title: str = ""
60
+ title_line: int = 0
61
+ has_covers: bool = False
62
+ covers_line: int = 0
63
+ sections: dict = field(default_factory=dict) # heading -> line number
64
+ separators: list = field(default_factory=list) # line numbers of ---
65
+ functions: list = field(default_factory=list) # list of FunctionEntry
66
+ is_index: bool = False
67
+ is_cross_cutting: bool = False
68
+ has_dispatch_table: bool = False
69
+
70
+
71
+ # ---------------------------------------------------------------------------
72
+ # Parser
73
+ # ---------------------------------------------------------------------------
74
+
75
+ def parse_charter(path):
76
+ """Parse a charter markdown file into a CharterDoc structure."""
77
+ text = Path(path).read_text(encoding="utf-8", errors="replace")
78
+ lines = text.splitlines()
79
+ doc = CharterDoc(path=str(path), lines=lines)
80
+
81
+ # Detect file type from first heading
82
+ for i, line in enumerate(lines):
83
+ stripped = line.strip()
84
+ if not stripped:
85
+ continue
86
+ if stripped.startswith("# Charter Index"):
87
+ doc.is_index = True
88
+ doc.title = stripped
89
+ doc.title_line = i + 1
90
+ break
91
+ if stripped.startswith("# Charter:"):
92
+ doc.title = stripped
93
+ doc.title_line = i + 1
94
+ break
95
+ if stripped.startswith("# "):
96
+ doc.title = stripped
97
+ doc.title_line = i + 1
98
+ break
99
+
100
+ # Scan for structural elements
101
+ non_blank_count = 0
102
+ current_section = None
103
+ current_func = None
104
+ in_table = False
105
+
106
+ ANNOTATION_PREFIXES = (
107
+ "@reads:", "@writes:", "Models:", "Filesystem:",
108
+ "Session R:", "Session W:",
109
+ "←", "<-", "→", "->", "!",
110
+ )
111
+
112
+ for i, line in enumerate(lines):
113
+ stripped = line.strip()
114
+ lineno = i + 1
115
+
116
+ # Track non-blank lines for covers detection
117
+ if stripped:
118
+ non_blank_count += 1
119
+
120
+ # Covers line
121
+ if "**Covers**:" in stripped or "**Covers**" in stripped:
122
+ doc.has_covers = True
123
+ doc.covers_line = lineno
124
+
125
+ # Separators
126
+ if stripped == "---":
127
+ doc.separators.append(lineno)
128
+
129
+ # Tables (for dispatch table detection)
130
+ if stripped.startswith("|") and "|" in stripped[1:]:
131
+ in_table = True
132
+ if "changing" in stripped.lower() or "charter" in stripped.lower():
133
+ doc.has_dispatch_table = True
134
+ elif in_table and not stripped.startswith("|"):
135
+ in_table = False
136
+
137
+ # Section headings (## level)
138
+ if stripped.startswith("## ") and not stripped.startswith("### "):
139
+ section_name = stripped[3:].strip()
140
+ doc.sections[section_name] = lineno
141
+ current_section = section_name
142
+ current_func = None
143
+
144
+ # Detect cross-cutting patterns
145
+ if "TRIPWIRE" in section_name or "Pattern" in section_name:
146
+ doc.is_cross_cutting = True
147
+ continue
148
+
149
+ # Function entries (### level)
150
+ if stripped.startswith("### "):
151
+ func_name = stripped[4:].strip()
152
+ current_func = FunctionEntry(name=func_name, line=lineno)
153
+ doc.functions.append(current_func)
154
+ continue
155
+
156
+ # Parse function entry content
157
+ if current_func and stripped:
158
+ if stripped.startswith("File:") or stripped.startswith("File :"):
159
+ current_func.has_file_line = True
160
+ elif any(stripped.startswith(p) for p in ANNOTATION_PREFIXES):
161
+ current_func.has_annotations = True
162
+ elif not current_func.has_description:
163
+ # First non-annotation, non-File line is description
164
+ # (File: line may be absent for bash/single-file charters)
165
+ current_func.has_description = True
166
+
167
+ return doc
168
+
169
+
170
+ # ---------------------------------------------------------------------------
171
+ # Validation
172
+ # ---------------------------------------------------------------------------
173
+
174
+ def _get_covers_text(doc):
175
+ """Extract the covers line text from the doc."""
176
+ if not doc.has_covers:
177
+ return ""
178
+ idx = doc.covers_line - 1
179
+ if 0 <= idx < len(doc.lines):
180
+ return doc.lines[idx]
181
+ return ""
182
+
183
+
184
+ def validate_charter(doc):
185
+ """Validate a parsed CharterDoc. Returns list of Finding."""
186
+ findings = []
187
+
188
+ if doc.is_index:
189
+ return _validate_index(doc)
190
+
191
+ # Title
192
+ if not doc.title.startswith("# Charter:"):
193
+ findings.append(Finding(
194
+ "title-format", "error", doc.title_line or 1,
195
+ f"First heading should be '# Charter: [Name]', got: '{doc.title}'"
196
+ ))
197
+
198
+ # Covers — not required for cross-cutting charters (they cover patterns, not files)
199
+ if not doc.has_covers and not doc.is_cross_cutting:
200
+ findings.append(Finding(
201
+ "covers-present", "error", 1,
202
+ "**Covers**: line missing (should appear within first 5 non-blank lines)"
203
+ ))
204
+
205
+ # Required sections
206
+ owns_found = False
207
+ does_not_own_found = False
208
+
209
+ for section, lineno in doc.sections.items():
210
+ if section == "Owns":
211
+ owns_found = True
212
+ if section == "Does Not Own":
213
+ does_not_own_found = True
214
+
215
+ if not owns_found:
216
+ findings.append(Finding(
217
+ "owns-section", "error", 1,
218
+ "Missing required section: ## Owns"
219
+ ))
220
+
221
+ if not does_not_own_found:
222
+ findings.append(Finding(
223
+ "does-not-own-section", "error", 1,
224
+ "Missing required section: ## Does Not Own"
225
+ ))
226
+
227
+ # Separator before functions
228
+ if doc.functions and not doc.separators:
229
+ findings.append(Finding(
230
+ "separator-before-functions", "error", doc.functions[0].line,
231
+ "Function entries found but no --- separator before them"
232
+ ))
233
+
234
+ # Conditional sections (required if functions exist)
235
+ if doc.functions:
236
+ has_before_changing = any(
237
+ s.startswith("Before Changing") for s in doc.sections
238
+ )
239
+ has_connections = "Connections" in doc.sections
240
+
241
+ if not has_before_changing:
242
+ findings.append(Finding(
243
+ "before-changing-section", "warn", 1,
244
+ "Missing '## Before Changing This Module' — recommended when function entries exist"
245
+ ))
246
+
247
+ if not has_connections:
248
+ findings.append(Finding(
249
+ "connections-section", "warn", 1,
250
+ "Missing '## Connections' — recommended when function entries exist"
251
+ ))
252
+
253
+ # Function entry validation
254
+ # Count how many covered files there are (multi-file charters need File: lines more)
255
+ multi_file = doc.has_covers and ("," in _get_covers_text(doc) or "`.gator" not in _get_covers_text(doc) and "`gator" not in _get_covers_text(doc))
256
+
257
+ for func in doc.functions:
258
+ if not func.has_file_line:
259
+ # Error for multi-file charters (ambiguous which file), warn for single-file
260
+ severity = "warn"
261
+ findings.append(Finding(
262
+ "file-line", severity, func.line,
263
+ f"Function entry '{func.name}' missing 'File:' line"
264
+ ))
265
+
266
+ if func.has_file_line and not func.has_description:
267
+ findings.append(Finding(
268
+ "description-line", "warn", func.line,
269
+ f"Function entry '{func.name}' has no description after File: line"
270
+ ))
271
+
272
+ if not func.has_annotations:
273
+ findings.append(Finding(
274
+ "no-annotations", "info", func.line,
275
+ f"Function entry '{func.name}' has no caller/callee/tripwire annotations"
276
+ ))
277
+
278
+ return findings
279
+
280
+
281
+ def _validate_index(doc):
282
+ """Validate an INDEX.md file."""
283
+ findings = []
284
+
285
+ if not doc.title.startswith("# Charter Index"):
286
+ findings.append(Finding(
287
+ "index-title", "error", doc.title_line or 1,
288
+ f"INDEX.md should start with '# Charter Index', got: '{doc.title}'"
289
+ ))
290
+
291
+ if not doc.has_dispatch_table:
292
+ findings.append(Finding(
293
+ "dispatch-table", "warn", 1,
294
+ "INDEX.md should contain a dispatch table mapping code paths to charters"
295
+ ))
296
+
297
+ return findings
298
+
299
+
300
+ # ---------------------------------------------------------------------------
301
+ # Report
302
+ # ---------------------------------------------------------------------------
303
+
304
+ def print_report(results):
305
+ """Print human-readable report."""
306
+ total_errors = 0
307
+ total_warnings = 0
308
+ total_info = 0
309
+
310
+ for path, findings in results.items():
311
+ errors = [f for f in findings if f.severity == "error"]
312
+ warnings = [f for f in findings if f.severity == "warn"]
313
+ infos = [f for f in findings if f.severity == "info"]
314
+
315
+ total_errors += len(errors)
316
+ total_warnings += len(warnings)
317
+ total_info += len(infos)
318
+
319
+ if not findings:
320
+ print(f" ✓ {Path(path).name}")
321
+ continue
322
+
323
+ status = "✗" if errors else "⚠" if warnings else "·"
324
+ print(f" {status} {Path(path).name}")
325
+
326
+ for f in findings:
327
+ icon = {"error": "✗", "warn": "⚠", "info": "·"}.get(f.severity, " ")
328
+ print(f" {icon} L{f.line}: [{f.check}] {f.message}")
329
+
330
+ print()
331
+ print(f" {len(results)} files checked: "
332
+ f"{total_errors} errors, {total_warnings} warnings, {total_info} info")
333
+
334
+
335
+ def print_json_report(results):
336
+ """Print JSON report."""
337
+ output = {}
338
+ for path, findings in results.items():
339
+ output[path] = [asdict(f) for f in findings]
340
+ print(json.dumps(output, indent=2))
341
+
342
+
343
+ # ---------------------------------------------------------------------------
344
+ # CLI
345
+ # ---------------------------------------------------------------------------
346
+
347
+ def find_charter_dirs():
348
+ """Find charter directories from the current working directory."""
349
+ dirs = []
350
+ cwd = Path.cwd()
351
+
352
+ # Check .gator/charters/ (per-repo governance)
353
+ gator_charters = cwd / ".gator" / "charters"
354
+ if gator_charters.is_dir():
355
+ dirs.append(gator_charters)
356
+
357
+ # Check gator-command/charters/ (command-post product charters)
358
+ gc_charters = cwd / "gator-command" / "charters"
359
+ if gc_charters.is_dir():
360
+ dirs.append(gc_charters)
361
+
362
+ return dirs
363
+
364
+
365
+ def collect_files(charter_path=None, charters_dir=None):
366
+ """Collect charter files to validate."""
367
+ files = []
368
+
369
+ if charter_path:
370
+ files.append(Path(charter_path))
371
+ elif charters_dir:
372
+ d = Path(charters_dir)
373
+ if d.is_dir():
374
+ files.extend(sorted(f for f in d.glob("*.md") if f.name not in SKIP_FILES))
375
+ else:
376
+ for d in find_charter_dirs():
377
+ files.extend(sorted(f for f in d.glob("*.md") if f.name not in SKIP_FILES))
378
+
379
+ return files
380
+
381
+
382
+ def main():
383
+ ensure_utf8_stdout()
384
+
385
+ parser = argparse.ArgumentParser(
386
+ description="Validate charter files against Charter Schema v1."
387
+ )
388
+ parser.add_argument("--json", "-j", action="store_true", help="JSON output")
389
+ parser.add_argument("--charter", metavar="PATH", help="Validate a single charter file")
390
+ parser.add_argument("--charters-dir", metavar="DIR", help="Validate all charters in directory")
391
+
392
+ args = parser.parse_args()
393
+
394
+ files = collect_files(charter_path=args.charter, charters_dir=args.charters_dir)
395
+
396
+ if not files:
397
+ print(" No charter files found.")
398
+ return
399
+
400
+ print()
401
+ print(f" charter-lint: checking {len(files)} files")
402
+ print()
403
+
404
+ results = {}
405
+ has_errors = False
406
+
407
+ for f in files:
408
+ try:
409
+ doc = parse_charter(f)
410
+ findings = validate_charter(doc)
411
+ results[str(f)] = findings
412
+ if any(fd.severity == "error" for fd in findings):
413
+ has_errors = True
414
+ except Exception as e:
415
+ results[str(f)] = [Finding("parse-error", "error", 0, str(e))]
416
+ has_errors = True
417
+
418
+ if args.json:
419
+ print_json_report(results)
420
+ else:
421
+ print_report(results)
422
+
423
+ sys.exit(1 if has_errors else 0)
424
+
425
+
426
+ if __name__ == "__main__":
427
+ main()