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,372 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ gator-session-common.py — Shared utilities for session archaeology scripts.
4
+
5
+ Contains the machine identity resolver, redaction engine, and
6
+ intelligence extraction logic used by all vendor-specific extraction
7
+ scripts (Claude, Codex, Gemini, Cursor, future).
8
+
9
+ This module is the foundation of the Gator Session Schema — the
10
+ normalized format all vendors map to.
11
+
12
+ Import:
13
+ from gator_session_common import get_machine_identity, redact, extract_intelligence
14
+
15
+ @reads: ~/.gator/machine-id
16
+ @does-not-own: vendor-specific extraction (each script handles its own format)
17
+ """
18
+
19
+ import os
20
+ import platform
21
+ import re
22
+ from pathlib import Path
23
+
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Machine Identity
27
+ # ---------------------------------------------------------------------------
28
+
29
+ GATOR_USER_DIR = Path.home() / ".gator"
30
+ MACHINE_ID_FILE = GATOR_USER_DIR / "machine-id"
31
+
32
+
33
+ def get_machine_identity():
34
+ """Get stable machine identity for session summaries.
35
+
36
+ Returns dict with id, hostname, label. Creates machine-id file
37
+ on first call if it doesn't exist.
38
+
39
+ Uses gator-machine-id.py's storage format for compatibility.
40
+ """
41
+ if MACHINE_ID_FILE.exists():
42
+ data = {}
43
+ try:
44
+ for line in MACHINE_ID_FILE.read_text(encoding="utf-8").splitlines():
45
+ line = line.strip()
46
+ if ":" in line and not line.startswith("#"):
47
+ key, _, value = line.partition(":")
48
+ data[key.strip()] = value.strip()
49
+ except OSError:
50
+ pass
51
+
52
+ if "id" in data:
53
+ return {
54
+ "id": data["id"],
55
+ "hostname": data.get("hostname", platform.node()),
56
+ "label": data.get("label", data.get("hostname", platform.node())),
57
+ }
58
+
59
+ # No machine-id file — create one
60
+ import uuid
61
+ from datetime import date
62
+
63
+ GATOR_USER_DIR.mkdir(parents=True, exist_ok=True)
64
+ mid = str(uuid.uuid4())
65
+ hostname = platform.node()
66
+
67
+ lines = [
68
+ f"id: {mid}",
69
+ f"hostname: {hostname}",
70
+ f"label: {hostname}",
71
+ f"created: {date.today()}",
72
+ ]
73
+ MACHINE_ID_FILE.write_text("\n".join(lines) + "\n", encoding="utf-8")
74
+
75
+ return {"id": mid, "hostname": hostname, "label": hostname}
76
+
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # Redaction Engine
80
+ # ---------------------------------------------------------------------------
81
+
82
+ # Patterns applied to ALL content (summaries and transcripts)
83
+ REDACT_ALWAYS = [
84
+ # API keys
85
+ (r'sk-[A-Za-z0-9]{20,}', '[REDACTED-API-KEY]'),
86
+ (r'AKIA[A-Z0-9]{16}', '[REDACTED-AWS-KEY]'),
87
+ # Passwords in assignments
88
+ (r'(?i)(password|passwd|pwd)\s*=\s*["\'][^"\']+["\']',
89
+ r'\1 = "[REDACTED]"'),
90
+ # API key/secret assignments
91
+ (r'(?i)(api[_-]?key|secret[_-]?key|access[_-]?token)\s*=\s*["\'][A-Za-z0-9+/=_-]{16,}["\']',
92
+ r'\1 = "[REDACTED]"'),
93
+ # Private key blocks
94
+ (r'-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]*?-----END[A-Z ]*PRIVATE KEY-----',
95
+ '[REDACTED-PRIVATE-KEY]'),
96
+ # Bearer tokens
97
+ (r'(?i)Bearer\s+[A-Za-z0-9_.-]{20,}',
98
+ 'Bearer [REDACTED-TOKEN]'),
99
+ # Connection strings with embedded passwords
100
+ (r'(?i)(mysql|postgres|mongodb|redis)://[^:]+:[^@]+@',
101
+ r'\1://[REDACTED]@'),
102
+ ]
103
+
104
+ # Patterns applied only to SUMMARIES (transcripts keep the original)
105
+ REDACT_SUMMARY = [
106
+ # Full user paths → relative
107
+ (r'[A-Z]:\\Users\\[^\\]+\\', '~/'),
108
+ (r'/home/[^/]+/', '~/'),
109
+ (r'/Users/[^/]+/', '~/'),
110
+ ]
111
+
112
+
113
+ def redact(text, summary_mode=False):
114
+ """Apply redaction patterns to text.
115
+
116
+ Args:
117
+ text: Content to redact
118
+ summary_mode: If True, also apply summary-only redactions
119
+ (path anonymization)
120
+
121
+ Always redacts: API keys, passwords, private keys, tokens,
122
+ connection strings.
123
+
124
+ Summary-only redacts: full user paths → ~/
125
+ """
126
+ if not text:
127
+ return text
128
+
129
+ # Always redact secrets
130
+ for pattern, replacement in REDACT_ALWAYS:
131
+ text = re.sub(pattern, replacement, text)
132
+
133
+ # Summary-only redactions
134
+ if summary_mode:
135
+ for pattern, replacement in REDACT_SUMMARY:
136
+ text = re.sub(pattern, replacement, text)
137
+
138
+ return text
139
+
140
+
141
+ # ---------------------------------------------------------------------------
142
+ # Intelligence Extraction (shared across vendors)
143
+ # ---------------------------------------------------------------------------
144
+
145
+ # Decision-language signals in Architect messages
146
+ DECISION_SIGNALS = [
147
+ "let's ", "let us ", "we should ", "go with ",
148
+ "approved", "yes, ", "do it", "proceed",
149
+ "i want ", "i think ", "i'd like ",
150
+ "decision:", "decided:",
151
+ ]
152
+
153
+
154
+ def extract_intelligence(turns):
155
+ """Extract decisions, files changed, charters updated, and session goal.
156
+
157
+ Works with the normalized turn format (role, content, tool_calls).
158
+ Vendor-agnostic — all extraction scripts produce turns in this shape.
159
+
160
+ Args:
161
+ turns: list of dicts with role, content, tool_calls, timestamp
162
+
163
+ Returns dict with goal, decisions, files_changed, charters_updated.
164
+ """
165
+ files_changed = set()
166
+ charters_updated = set()
167
+ decisions = []
168
+ goal = ""
169
+
170
+ for t in turns:
171
+ raw_content = t.get("content", "")
172
+ content = raw_content.strip() if isinstance(raw_content, str) else ""
173
+ role = t.get("role", "")
174
+
175
+ # Goal = first substantive Architect message
176
+ if role == "user" and content and not goal and len(content) > 10:
177
+ goal = redact(content[:200], summary_mode=True)
178
+
179
+ # Files from content (backtick-quoted or double-quoted paths)
180
+ if content:
181
+ for match in re.finditer(r'[`"]([a-zA-Z0-9_./-]+\.[a-zA-Z]{1,10})[`"]', content):
182
+ filepath = match.group(1)
183
+ if ".gator/charters/" in filepath:
184
+ charters_updated.add(filepath.split("/")[-1])
185
+ elif not filepath.startswith("http"):
186
+ files_changed.add(filepath)
187
+
188
+ # Files from tool calls
189
+ for tc in t.get("tool_calls", []):
190
+ tool = tc.get("tool", "")
191
+ if tool:
192
+ files_changed.add(f"[{tool} operation]")
193
+
194
+ # Decisions from Architect messages
195
+ if role == "user" and content:
196
+ lower = content.lower()
197
+ if any(sig in lower for sig in DECISION_SIGNALS):
198
+ decisions.append({
199
+ "timestamp": t.get("timestamp", ""),
200
+ "text": redact(content[:120], summary_mode=True),
201
+ })
202
+
203
+ return {
204
+ "goal": goal,
205
+ "files_changed": sorted(files_changed)[:20],
206
+ "charters_updated": sorted(charters_updated),
207
+ "decisions": decisions[:10],
208
+ }
209
+
210
+
211
+ # ---------------------------------------------------------------------------
212
+ # Session Identity (composite key, no collisions)
213
+ # ---------------------------------------------------------------------------
214
+
215
+ def make_row_key(metadata):
216
+ """Generate the canonical unique row key for a session.
217
+
218
+ Hashes session_id + source_path to handle the real case where
219
+ two different files share the same internal session ID (Gemini).
220
+ This key is used everywhere: transcript paths, spool filenames,
221
+ export state, manifest rows.
222
+ """
223
+ import hashlib
224
+ session_id = metadata.get("session_id", "unknown")
225
+ source_path = metadata.get("source_path", "")
226
+ key = f"{session_id}|{source_path}"
227
+ return hashlib.sha256(key.encode()).hexdigest()[:16]
228
+
229
+
230
+ def make_transcript_path(metadata):
231
+ """Generate a collision-free transcript path.
232
+
233
+ Uses the row_key (hash of session_id + source_path) to guarantee
234
+ uniqueness even for Gemini's genuine duplicate session IDs.
235
+ Format: <date>-<repo>-<vendor>-<row_key>.md
236
+ """
237
+ date = metadata.get("start", "")[:10]
238
+ repo = metadata.get("repo", "unknown")
239
+ vendor = metadata.get("vendor", "unknown")
240
+ rk = make_row_key(metadata)
241
+ transcript_dir = GATOR_USER_DIR / "session-transcripts"
242
+ return transcript_dir / f"{date}-{repo}-{vendor}-{rk}.md"
243
+
244
+
245
+ # ---------------------------------------------------------------------------
246
+ # Summary Formatting (shared across vendors — canonical implementation)
247
+ # ---------------------------------------------------------------------------
248
+
249
+ def format_summary_frontmatter(metadata, machine):
250
+ """Generate standardized YAML frontmatter for session summaries.
251
+
252
+ This is the ONLY implementation. Vendor extractors must call this,
253
+ not hand-roll their own frontmatter.
254
+ """
255
+ transcript_path = make_transcript_path(metadata)
256
+
257
+ return [
258
+ f"schema: gator-session-summary-v1",
259
+ f"session-id: {metadata.get('session_id', '')}",
260
+ f"date: {metadata.get('start', '')[:10]}",
261
+ f"start: {metadata.get('start', '')}",
262
+ f"end: {metadata.get('end', '')}",
263
+ f"repo: {metadata.get('repo', 'unknown')}",
264
+ f"architect: {metadata.get('architect', metadata.get('pi', 'unknown'))}",
265
+ f"agent: {metadata.get('agent', 'unknown')}",
266
+ f"vendor: {metadata.get('vendor', 'unknown')}",
267
+ f"machine-id: {machine.get('id', '')}",
268
+ f"machine-label: {machine.get('label', '')}",
269
+ f"transcript: {transcript_path}",
270
+ f"turns: {metadata.get('user_turns', 0)} user, {metadata.get('assistant_turns', 0)} assistant",
271
+ f"tools: {', '.join(metadata.get('tools_used', []))}",
272
+ f"branch: {metadata.get('branch', 'unknown')}",
273
+ ]
274
+
275
+
276
+ def format_summary_markdown(turns, metadata):
277
+ """Generate complete summary markdown (frontmatter + body).
278
+
279
+ This is the canonical summary formatter. All vendor extractors
280
+ should call this instead of implementing their own.
281
+ """
282
+ machine = get_machine_identity()
283
+ intelligence = extract_intelligence(turns)
284
+ transcript_path = make_transcript_path(metadata)
285
+
286
+ lines = []
287
+
288
+ # Frontmatter
289
+ lines.append("---")
290
+ lines.extend(format_summary_frontmatter(metadata, machine))
291
+ lines.append("---")
292
+ lines.append("")
293
+
294
+ # Body
295
+ lines.append(f"# Session Summary — {metadata.get('start', '')[:10]} {metadata.get('repo', 'unknown')} ({metadata.get('agent', metadata.get('vendor', ''))})")
296
+ lines.append("")
297
+
298
+ lines.append("## Goal")
299
+ lines.append("")
300
+ lines.append(intelligence["goal"] or "*No goal extracted*")
301
+ lines.append("")
302
+
303
+ lines.append("## Decisions")
304
+ lines.append("")
305
+ if intelligence["decisions"]:
306
+ for d in intelligence["decisions"]:
307
+ ts = d["timestamp"][:19] if d["timestamp"] else "?"
308
+ lines.append(f"- [{ts}] {d['text']}")
309
+ else:
310
+ lines.append("*No decisions extracted*")
311
+ lines.append("")
312
+
313
+ lines.append("## Files Changed")
314
+ lines.append("")
315
+ if intelligence["files_changed"]:
316
+ for f in intelligence["files_changed"]:
317
+ lines.append(f"- {f}")
318
+ else:
319
+ lines.append("*No file changes extracted*")
320
+ lines.append("")
321
+
322
+ if intelligence["charters_updated"]:
323
+ lines.append("## Charters Updated")
324
+ lines.append("")
325
+ for c in intelligence["charters_updated"]:
326
+ lines.append(f"- {c}")
327
+ lines.append("")
328
+
329
+ lines.append("## Evidence Location")
330
+ lines.append("")
331
+ lines.append(f"- **Machine**: {machine.get('label', '?')} (`{machine.get('id', '?')}`)")
332
+ lines.append(f"- **Transcript**: `{transcript_path}`")
333
+ lines.append(f"- **Raw source**: vendor-specific local storage")
334
+ lines.append("")
335
+
336
+ return "\n".join(lines)
337
+
338
+
339
+ def format_session_summary_dict(turns, metadata):
340
+ """Generate structured summary dict for JSON output.
341
+
342
+ Canonical implementation — all vendors use this.
343
+ """
344
+ machine = get_machine_identity()
345
+ intelligence = extract_intelligence(turns)
346
+ transcript_path = make_transcript_path(metadata)
347
+
348
+ return {
349
+ "schema": "gator-session-summary-v1",
350
+ "session_id": metadata.get("session_id", ""),
351
+ "row_key": make_row_key(metadata),
352
+ "date": metadata.get("start", "")[:10],
353
+ "start": metadata.get("start", ""),
354
+ "end": metadata.get("end", ""),
355
+ "repo": metadata.get("repo", "unknown"),
356
+ "architect": metadata.get("architect", metadata.get("pi", "unknown")),
357
+ "agent": metadata.get("agent", "unknown"),
358
+ "vendor": metadata.get("vendor", "unknown"),
359
+ "machine_id": machine.get("id", ""),
360
+ "machine_label": machine.get("label", ""),
361
+ "transcript": str(transcript_path),
362
+ "turns": metadata.get("total_turns", 0),
363
+ "user_turns": metadata.get("user_turns", 0),
364
+ "assistant_turns": metadata.get("assistant_turns", 0),
365
+ "tools": metadata.get("tools_used", []),
366
+ "branches": metadata.get("branches", [metadata.get("branch", "")]),
367
+ "model": metadata.get("model", ""),
368
+ "goal": intelligence["goal"],
369
+ "decisions": intelligence["decisions"],
370
+ "files_changed": intelligence["files_changed"],
371
+ "charters_updated": intelligence["charters_updated"],
372
+ }