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,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())
|