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,424 @@
1
+ #!/usr/bin/env python3
2
+ """Render the Memex thread graph into MediaWiki markup."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import datetime as dt
8
+ import re
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+
12
+
13
+ CATEGORY_ORDER = [
14
+ ("mathematics", "Mathematics"),
15
+ ("cognition", "Cognition"),
16
+ ("systems", "Systems"),
17
+ ("ventures", "Ventures"),
18
+ ("economics", "Economics"),
19
+ ("civic", "Civic"),
20
+ ]
21
+
22
+ IDENTITY_SECTIONS = [
23
+ "Background",
24
+ "Intellectual Disposition",
25
+ "Working Style",
26
+ "Civic Engagement",
27
+ "Interests",
28
+ ]
29
+
30
+ THREAD_DIRECTORIES = [
31
+ ("active-threads", "Active thread"),
32
+ ("threads", "Reference thread"),
33
+ ]
34
+
35
+ LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
36
+
37
+
38
+ @dataclass
39
+ class Link:
40
+ label: str
41
+ annotation: str
42
+ target_path: Path | None
43
+
44
+
45
+ @dataclass
46
+ class Thread:
47
+ source_path: Path
48
+ tier_label: str
49
+ category: str
50
+ hits: int
51
+ last_touched: str
52
+ tags: list[str]
53
+ title: str
54
+ summary_lines: list[str]
55
+ connection_lines: list[str]
56
+ graph: str = "user" # graph namespace: "design", "user", or custom
57
+
58
+
59
+ def parse_frontmatter(text: str) -> tuple[dict[str, str], str]:
60
+ if not text.startswith("---\n"):
61
+ return {}, text
62
+
63
+ parts = text.split("---\n", 2)
64
+ if len(parts) < 3:
65
+ return {}, text
66
+
67
+ _, raw_frontmatter, body = parts
68
+ frontmatter: dict[str, str] = {}
69
+ for line in raw_frontmatter.splitlines():
70
+ if ":" not in line:
71
+ continue
72
+ key, value = line.split(":", 1)
73
+ frontmatter[key.strip()] = value.strip()
74
+ return frontmatter, body
75
+
76
+
77
+ def parse_sections(body: str) -> tuple[str | None, dict[str, list[str]]]:
78
+ h1: str | None = None
79
+ sections: dict[str, list[str]] = {}
80
+ current_section: str | None = None
81
+
82
+ for line in body.splitlines():
83
+ if line.startswith("# ") and h1 is None:
84
+ h1 = line[2:].strip()
85
+ continue
86
+ if line.startswith("## "):
87
+ current_section = line[3:].strip()
88
+ sections[current_section] = []
89
+ continue
90
+ if current_section is not None:
91
+ sections[current_section].append(line)
92
+
93
+ return h1, sections
94
+
95
+
96
+ def parse_tags(raw_tags: str) -> list[str]:
97
+ raw_tags = raw_tags.strip()
98
+ if raw_tags.startswith("[") and raw_tags.endswith("]"):
99
+ raw_tags = raw_tags[1:-1]
100
+ if not raw_tags:
101
+ return []
102
+ return [item.strip() for item in raw_tags.split(",") if item.strip()]
103
+
104
+
105
+ def fallback_title(path: Path) -> str:
106
+ return path.stem.replace("-", " ").replace("_", " ").title()
107
+
108
+
109
+ def parse_iso_date(value: str) -> dt.date:
110
+ return dt.date.fromisoformat(value)
111
+
112
+
113
+ def load_thread(path: Path, tier_label: str) -> Thread:
114
+ text = path.read_text(encoding="utf-8")
115
+ frontmatter, body = parse_frontmatter(text)
116
+ h1, sections = parse_sections(body)
117
+
118
+ title = frontmatter.get("title") or h1 or fallback_title(path)
119
+ summary_lines = sections.get("Summary", [])
120
+ connection_lines = sections.get("Connections", [])
121
+
122
+ return Thread(
123
+ source_path=path,
124
+ tier_label=tier_label,
125
+ category=frontmatter.get("category", "uncategorized"),
126
+ hits=int(frontmatter.get("hits", "0")),
127
+ last_touched=frontmatter.get("last-touched", frontmatter.get("created", "unknown")),
128
+ tags=parse_tags(frontmatter.get("tags", "")),
129
+ title=title,
130
+ summary_lines=summary_lines,
131
+ connection_lines=connection_lines,
132
+ graph=frontmatter.get("graph", "user"),
133
+ )
134
+
135
+
136
+ def load_threads(memex_dir: Path) -> list[Thread]:
137
+ threads: list[Thread] = []
138
+ for folder_name, tier_label in THREAD_DIRECTORIES:
139
+ folder = memex_dir / folder_name
140
+ for path in sorted(folder.glob("*.md")):
141
+ if path.name == "_TEMPLATE.md":
142
+ continue
143
+ threads.append(load_thread(path, tier_label))
144
+ return threads
145
+
146
+
147
+ def section_blocks(lines: list[str]) -> list[tuple[str, list[str]]]:
148
+ blocks: list[tuple[str, list[str]]] = []
149
+ current: list[str] = []
150
+ current_kind: str | None = None
151
+
152
+ for raw_line in lines:
153
+ line = raw_line.rstrip()
154
+ if not line.strip():
155
+ if current:
156
+ blocks.append((current_kind or "paragraph", current))
157
+ current = []
158
+ current_kind = None
159
+ continue
160
+
161
+ kind = "bullet" if line.lstrip().startswith("- ") else "paragraph"
162
+ if current and kind != current_kind:
163
+ blocks.append((current_kind or "paragraph", current))
164
+ current = []
165
+ current.append(line)
166
+ current_kind = kind
167
+
168
+ if current:
169
+ blocks.append((current_kind or "paragraph", current))
170
+
171
+ return blocks
172
+
173
+
174
+ def convert_inline(text: str, title_lookup: dict[Path, str], source_dir: Path) -> str:
175
+ def replace_link(match: re.Match[str]) -> str:
176
+ label = match.group(1)
177
+ raw_target = match.group(2)
178
+ if raw_target.endswith(".md"):
179
+ target_path = (source_dir / raw_target).resolve()
180
+ target_title = title_lookup.get(target_path)
181
+ if target_title:
182
+ return f"[[#{target_title}|{label}]]"
183
+ return label
184
+
185
+ text = LINK_RE.sub(replace_link, text)
186
+ text = re.sub(r"`([^`]+)`", r"<code>\1</code>", text)
187
+ text = re.sub(r"\*\*([^*]+)\*\*", r"'''\1'''", text)
188
+ text = re.sub(r"(?<!\*)\*([^*]+)\*(?!\*)", r"''\1''", text)
189
+ return text
190
+
191
+
192
+ def render_markdown_lines(
193
+ lines: list[str], title_lookup: dict[Path, str], source_dir: Path
194
+ ) -> list[str]:
195
+ rendered: list[str] = []
196
+ for kind, block in section_blocks(lines):
197
+ if kind == "bullet":
198
+ for line in block:
199
+ content = line.lstrip()[2:]
200
+ rendered.append("* " + convert_inline(content, title_lookup, source_dir))
201
+ else:
202
+ paragraph = " ".join(line.strip() for line in block)
203
+ rendered.append(convert_inline(paragraph, title_lookup, source_dir))
204
+ rendered.append("")
205
+
206
+ if rendered and rendered[-1] == "":
207
+ rendered.pop()
208
+ return rendered
209
+
210
+
211
+ def extract_links(lines: list[str], source_path: Path) -> list[Link]:
212
+ links: list[Link] = []
213
+ for raw_line in lines:
214
+ line = raw_line.strip()
215
+ if not line:
216
+ continue
217
+ match = LINK_RE.search(line)
218
+ if not match:
219
+ continue
220
+
221
+ label = match.group(1)
222
+ raw_target = match.group(2)
223
+ annotation = ""
224
+ if " - " in line:
225
+ annotation = line.split(" - ", 1)[1].strip()
226
+ elif " — " in line:
227
+ annotation = line.split(" — ", 1)[1].strip()
228
+
229
+ target_path = None
230
+ if raw_target.endswith(".md"):
231
+ target_path = (source_path.parent / raw_target).resolve()
232
+
233
+ links.append(Link(label=label, annotation=annotation, target_path=target_path))
234
+ return links
235
+
236
+
237
+ def render_see_also(thread: Thread, title_lookup: dict[Path, str]) -> str | None:
238
+ rendered_links: list[str] = []
239
+ seen: set[Path] = set()
240
+ for link in extract_links(thread.connection_lines, thread.source_path):
241
+ if link.target_path is None:
242
+ continue
243
+ if link.target_path in seen:
244
+ continue
245
+ target_title = title_lookup.get(link.target_path)
246
+ if not target_title:
247
+ continue
248
+ seen.add(link.target_path)
249
+ item = f"[[#{target_title}|{link.label}]]"
250
+ if link.annotation:
251
+ item += f" ({link.annotation})"
252
+ rendered_links.append(item)
253
+
254
+ if not rendered_links:
255
+ return None
256
+ return "'''See also:''' " + ", ".join(rendered_links)
257
+
258
+
259
+ def parse_identity(identity_path: Path) -> dict[str, list[str]]:
260
+ sections: dict[str, list[str]] = {}
261
+ current: str | None = None
262
+ for line in identity_path.read_text(encoding="utf-8").splitlines():
263
+ if line.startswith("## "):
264
+ current = line[3:].strip()
265
+ sections[current] = []
266
+ continue
267
+ if current is not None:
268
+ sections[current].append(line)
269
+ return sections
270
+
271
+
272
+ def render_identity(identity_path: Path, title_lookup: dict[Path, str]) -> list[str]:
273
+ sections = parse_identity(identity_path)
274
+ rendered = ["== About the Operator ==", ""]
275
+ for section_name in IDENTITY_SECTIONS:
276
+ lines = sections.get(section_name, [])
277
+ if not lines:
278
+ continue
279
+ rendered.append(f"=== {section_name} ===")
280
+ rendered.extend(
281
+ render_markdown_lines(lines, title_lookup=title_lookup, source_dir=identity_path.parent)
282
+ )
283
+ rendered.append("")
284
+ if rendered[-1] == "":
285
+ rendered.pop()
286
+ return rendered
287
+
288
+
289
+ def category_threads(threads: list[Thread]) -> dict[str, list[Thread]]:
290
+ grouped: dict[str, list[Thread]] = {key: [] for key, _ in CATEGORY_ORDER}
291
+ for thread in threads:
292
+ grouped.setdefault(thread.category, []).append(thread)
293
+
294
+ for key in grouped:
295
+ grouped[key] = sorted(
296
+ grouped[key],
297
+ key=lambda thread: (
298
+ -thread.hits,
299
+ -parse_iso_date(thread.last_touched).toordinal(),
300
+ thread.title.lower(),
301
+ ),
302
+ )
303
+ return grouped
304
+
305
+
306
+ def render_thread(
307
+ thread: Thread, title_lookup: dict[Path, str]
308
+ ) -> list[str]:
309
+ lines = [
310
+ f"=== {thread.title} ===",
311
+ f"''{thread.tier_label} | hits: {thread.hits} | last touched: {thread.last_touched} | tags: {', '.join(thread.tags)}''",
312
+ "",
313
+ ]
314
+
315
+ if thread.summary_lines:
316
+ lines.extend(
317
+ render_markdown_lines(
318
+ thread.summary_lines,
319
+ title_lookup=title_lookup,
320
+ source_dir=thread.source_path.parent,
321
+ )
322
+ )
323
+ else:
324
+ lines.append("''Missing Summary section in source thread.''")
325
+
326
+ see_also = render_see_also(thread, title_lookup)
327
+ if see_also:
328
+ lines.extend(["", see_also])
329
+ return lines
330
+
331
+
332
+ def render_main_page(repo_root: Path, output_path: Path) -> str:
333
+ memex_dir = repo_root / "gator-command"
334
+ identity_path = memex_dir / "identity.md"
335
+ threads = load_threads(memex_dir)
336
+ title_lookup = {thread.source_path.resolve(): thread.title for thread in threads}
337
+ grouped = category_threads(threads)
338
+
339
+ today = dt.date.today().isoformat()
340
+ lines = [
341
+ "{{DISPLAYTITLE:Memex - Personal Knowledge System}}",
342
+ f"'''Last rendered:''' {today} | '''Threads:''' {len(threads)} | '''Categories:''' {len(CATEGORY_ORDER)}",
343
+ "",
344
+ "This wiki is auto-generated from the Memex thread graph. The Memex is the source of truth; this page is a rendered summary for human navigation.",
345
+ "",
346
+ "'''To regenerate:''' <code>python gator-command/scripts/generate_wiki.py && python gator-command/scripts/generate_markdown.py</code>",
347
+ "",
348
+ "'''Memex CLI''' (<code>python gator-command/scripts/memex.py</code>):",
349
+ "* <code>memex status</code> — graph health, inbox count, patterns due, active threads (<code>--full</code> for complete state dump)",
350
+ "* <code>memex search <query></code> — full-text search across threads, artifacts, designs, reports",
351
+ "* <code>memex read <type> <name></code> — render thread/artifact/design/report (<code>--section</code> for specific sections)",
352
+ "* Add <code>--format json</code> for structured agent output, <code>--role <name></code> for role-aware views",
353
+ "",
354
+ ]
355
+
356
+ lines.extend(render_identity(identity_path, title_lookup))
357
+ lines.extend(["", "", "== Categories ==", ""])
358
+
359
+ for key, label in CATEGORY_ORDER:
360
+ titles = [thread.title for thread in grouped.get(key, [])]
361
+ if not titles:
362
+ continue
363
+ lines.append(f"* [[#{label}|{label}]] - {', '.join(titles)}")
364
+
365
+ lines.extend(["", "----"])
366
+
367
+ for key, label in CATEGORY_ORDER:
368
+ category_items = grouped.get(key, [])
369
+ if not category_items:
370
+ continue
371
+ lines.extend(["", f"== {label} ==", ""])
372
+ for index, thread in enumerate(category_items):
373
+ lines.extend(render_thread(thread, title_lookup))
374
+ if index != len(category_items) - 1:
375
+ lines.append("")
376
+ lines.extend(["", "----"])
377
+
378
+ if lines[-1] == "----":
379
+ lines.pop()
380
+
381
+ lines.extend(
382
+ [
383
+ "",
384
+ "",
385
+ "''This wiki was generated mechanically from the Memex thread graph. Summaries are extracted from source threads; no thread prose is synthesized during rendering.''",
386
+ "",
387
+ "[[Category:Memex]]",
388
+ "[[Category:Auto-generated]]",
389
+ "",
390
+ ]
391
+ )
392
+
393
+ text = "\n".join(lines)
394
+ output_path.write_text(text, encoding="utf-8")
395
+ return text
396
+
397
+
398
+ def main() -> int:
399
+ parser = argparse.ArgumentParser(description=__doc__)
400
+ parser.add_argument(
401
+ "--repo-root",
402
+ default=Path(__file__).resolve().parents[2],
403
+ type=Path,
404
+ help="Path to the repository root.",
405
+ )
406
+ parser.add_argument(
407
+ "--output",
408
+ default=Path("docs/wiki/Main_Page.wiki"),
409
+ type=Path,
410
+ help="Output path, relative to repo root unless absolute.",
411
+ )
412
+ args = parser.parse_args()
413
+
414
+ repo_root = args.repo_root.resolve()
415
+ output_path = args.output
416
+ if not output_path.is_absolute():
417
+ output_path = repo_root / output_path
418
+
419
+ render_main_page(repo_root=repo_root, output_path=output_path)
420
+ return 0
421
+
422
+
423
+ if __name__ == "__main__":
424
+ raise SystemExit(main())