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,185 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# gatorize.sh — Install, upgrade, or morph the Gator knowledge layer into a
|
|
5
|
+
# project repo and connect it to this Gator Command post.
|
|
6
|
+
#
|
|
7
|
+
# Handles five scenarios:
|
|
8
|
+
# 1. Fresh directory (no git) → git init + install
|
|
9
|
+
# 2. Git repo, clean → install
|
|
10
|
+
# 3. Git repo, has .gator/ → upgrade (preserve knowledge)
|
|
11
|
+
# 4. Git repo, has memex structure → morph memex → gator
|
|
12
|
+
# 5. Git repo, has both → warn, let PI choose
|
|
13
|
+
#
|
|
14
|
+
# Every scenario creates a 'gator-install' feature branch. No exceptions.
|
|
15
|
+
#
|
|
16
|
+
# Structure:
|
|
17
|
+
# gatorize.sh — this file (orchestrator: constants, args, dispatch)
|
|
18
|
+
# gatorize-lib.sh — utilities, detection, shared helpers
|
|
19
|
+
# gatorize-actions.sh — scenario actions: install, upgrade, morph, dual
|
|
20
|
+
# gatorize-post.sh — thin link, outbox, entry points, register, summary
|
|
21
|
+
#
|
|
22
|
+
# Procedure: gator-command/procedures/gatorize-permutations.md
|
|
23
|
+
#
|
|
24
|
+
# Usage:
|
|
25
|
+
# bash gator-command/scripts/gatorize.sh /path/to/your/project
|
|
26
|
+
# bash gator-command/scripts/gatorize.sh .
|
|
27
|
+
|
|
28
|
+
# =============================================================================
|
|
29
|
+
# Constants
|
|
30
|
+
# =============================================================================
|
|
31
|
+
|
|
32
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
33
|
+
COMMAND_POST="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
34
|
+
# Templates are siblings of scripts/ — works whether parent is
|
|
35
|
+
# gator-command/ (dev) or gator-engine/ (public distribution)
|
|
36
|
+
TEMPLATES="$(cd "$SCRIPT_DIR/../templates/gator-starter" 2>/dev/null && pwd)"
|
|
37
|
+
if [ -z "$TEMPLATES" ] || [ ! -d "$TEMPLATES" ]; then
|
|
38
|
+
# Fallback: try gator-command/ path (legacy)
|
|
39
|
+
TEMPLATES="$COMMAND_POST/gator-command/templates/gator-starter"
|
|
40
|
+
fi
|
|
41
|
+
GATOR_BRANCH="gator-install"
|
|
42
|
+
TODAY="$(date +%Y-%m-%d)"
|
|
43
|
+
|
|
44
|
+
# Detect whether we are running from a live command post.
|
|
45
|
+
# Uses find_command_post() from gator_core.py as the single canonical predicate.
|
|
46
|
+
HAS_COMMAND_POST="false"
|
|
47
|
+
DETECTED_CP=$(python3 -c "import sys; sys.path.insert(0,'$SCRIPT_DIR'); from gator_core import find_command_post; cp=find_command_post('$SCRIPT_DIR'); print(cp or '')" 2>/dev/null || echo "")
|
|
48
|
+
if [ -n "$DETECTED_CP" ]; then
|
|
49
|
+
HAS_COMMAND_POST="true"
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Template generation — read from gator_core.py (single source of truth).
|
|
53
|
+
GATOR_GEN=$(sed -n 's/^CURRENT_GENERATION\s*=\s*\([0-9]*\)/\1/p' "$SCRIPT_DIR/gator_core.py" 2>/dev/null)
|
|
54
|
+
if [ -z "$GATOR_GEN" ]; then
|
|
55
|
+
GATOR_GEN=2 # fallback if gator_core.py not found
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Entry point markers (used for idempotent detection)
|
|
59
|
+
GATOR_MARKER="# --- Gator Navigation Coding ---"
|
|
60
|
+
COMMAND_POST_MARKER="# --- Gator Command Post ---"
|
|
61
|
+
|
|
62
|
+
# =============================================================================
|
|
63
|
+
# Source libraries
|
|
64
|
+
# =============================================================================
|
|
65
|
+
|
|
66
|
+
source "$SCRIPT_DIR/gatorize-lib.sh"
|
|
67
|
+
source "$SCRIPT_DIR/gatorize-actions.sh"
|
|
68
|
+
source "$SCRIPT_DIR/gatorize-post.sh"
|
|
69
|
+
|
|
70
|
+
# =============================================================================
|
|
71
|
+
# Main
|
|
72
|
+
# =============================================================================
|
|
73
|
+
|
|
74
|
+
# --- Validate args ---
|
|
75
|
+
|
|
76
|
+
if [ $# -eq 0 ]; then
|
|
77
|
+
echo "Usage: bash gator-command/scripts/gatorize.sh <target-directory>"
|
|
78
|
+
echo ""
|
|
79
|
+
echo "Installs, upgrades, or morphs .gator/ into the target project and"
|
|
80
|
+
echo "connects it to this Gator Command post."
|
|
81
|
+
echo ""
|
|
82
|
+
echo "Handles: fresh dirs, existing repos, prior gator installs, and"
|
|
83
|
+
echo "legacy memex structures (.memex/, memex/)."
|
|
84
|
+
echo ""
|
|
85
|
+
echo "Always creates a 'gator-install' feature branch for safety."
|
|
86
|
+
echo ""
|
|
87
|
+
echo "Procedure: gator-command/procedures/gatorize-permutations.md"
|
|
88
|
+
exit 1
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
if [ ! -d "$1" ]; then
|
|
92
|
+
echo ""
|
|
93
|
+
echo " Directory does not exist: $1"
|
|
94
|
+
echo " Gatorize can create it from scratch — git init, .gator/, the works."
|
|
95
|
+
echo ""
|
|
96
|
+
if confirm "Create $1 and gatorize it?" "Y"; then
|
|
97
|
+
mkdir -p "$1"
|
|
98
|
+
echo " Created $1"
|
|
99
|
+
else
|
|
100
|
+
echo " Aborted."
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
TARGET="$(cd "$1" && pwd)"
|
|
106
|
+
|
|
107
|
+
if [ "$TARGET" = "$COMMAND_POST" ]; then
|
|
108
|
+
echo "Error: Target is the command post itself. Gatorize a project repo, not gator-command."
|
|
109
|
+
exit 1
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
if [ ! -d "$TEMPLATES" ]; then
|
|
113
|
+
echo "Error: Templates not found at $TEMPLATES"
|
|
114
|
+
echo "Are you running this from the gator-command repo root?"
|
|
115
|
+
exit 1
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# --- Detect scenario ---
|
|
119
|
+
|
|
120
|
+
SCENARIO=$(detect_scenario "$TARGET")
|
|
121
|
+
|
|
122
|
+
echo ""
|
|
123
|
+
echo " ================================================================"
|
|
124
|
+
echo " gatorize.sh — $(basename "$TARGET")"
|
|
125
|
+
echo " ================================================================"
|
|
126
|
+
|
|
127
|
+
case "$SCENARIO" in
|
|
128
|
+
1) echo " Scenario 1: Fresh directory (no git, no memex, no gator)" ;;
|
|
129
|
+
2) echo " Scenario 2: Git repo, clean (no prior knowledge layer)" ;;
|
|
130
|
+
3)
|
|
131
|
+
DETECTED_GEN=$(detect_gator_generation "$TARGET")
|
|
132
|
+
echo " Gator is already installed (generation $DETECTED_GEN). Running update..."
|
|
133
|
+
echo ""
|
|
134
|
+
# Use the SOURCE repo's gator-update with --source pointing to this gator clone
|
|
135
|
+
python "$SCRIPT_DIR/gator-update.py" --path "$TARGET" --source "$COMMAND_POST" --no-policy
|
|
136
|
+
echo ""
|
|
137
|
+
echo " ================================================================"
|
|
138
|
+
echo " SUCCESS — updated"
|
|
139
|
+
echo " ================================================================"
|
|
140
|
+
echo ""
|
|
141
|
+
echo " Templates and scripts refreshed. Your content is preserved."
|
|
142
|
+
echo ""
|
|
143
|
+
exit 0
|
|
144
|
+
;;
|
|
145
|
+
4) echo " Scenario 4: Git repo, legacy memex structure (morph)" ;;
|
|
146
|
+
5) echo " Scenario 5: Git repo, both memex and gator (dual)" ;;
|
|
147
|
+
esac
|
|
148
|
+
|
|
149
|
+
echo ""
|
|
150
|
+
|
|
151
|
+
# --- Dispatch ---
|
|
152
|
+
|
|
153
|
+
case "$SCENARIO" in
|
|
154
|
+
1)
|
|
155
|
+
action_git_init "$TARGET"
|
|
156
|
+
action_install_gator "$TARGET"
|
|
157
|
+
;;
|
|
158
|
+
2)
|
|
159
|
+
action_feature_branch "$TARGET"
|
|
160
|
+
action_install_gator "$TARGET"
|
|
161
|
+
;;
|
|
162
|
+
3)
|
|
163
|
+
action_feature_branch "$TARGET"
|
|
164
|
+
action_upgrade_gator "$TARGET"
|
|
165
|
+
;;
|
|
166
|
+
4)
|
|
167
|
+
action_feature_branch "$TARGET"
|
|
168
|
+
action_morph_memex "$TARGET"
|
|
169
|
+
;;
|
|
170
|
+
5)
|
|
171
|
+
action_feature_branch "$TARGET"
|
|
172
|
+
action_warn_dual "$TARGET"
|
|
173
|
+
;;
|
|
174
|
+
esac
|
|
175
|
+
|
|
176
|
+
# --- Common tail (all scenarios) ---
|
|
177
|
+
|
|
178
|
+
if [ "$HAS_COMMAND_POST" = "true" ]; then
|
|
179
|
+
action_install_thin_link "$TARGET"
|
|
180
|
+
fi
|
|
181
|
+
action_install_entry_points "$TARGET" "$HAS_COMMAND_POST"
|
|
182
|
+
action_install_outbox "$TARGET"
|
|
183
|
+
action_install_product_source "$TARGET"
|
|
184
|
+
action_register "$TARGET"
|
|
185
|
+
print_summary "$TARGET" "$SCENARIO" "$HAS_COMMAND_POST"
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Render the Memex thread graph into Markdown."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import datetime as dt
|
|
8
|
+
import re
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from generate_wiki import (
|
|
12
|
+
CATEGORY_ORDER,
|
|
13
|
+
IDENTITY_SECTIONS,
|
|
14
|
+
category_threads,
|
|
15
|
+
extract_links,
|
|
16
|
+
load_threads,
|
|
17
|
+
parse_identity,
|
|
18
|
+
section_blocks,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def slugify_heading(text: str) -> str:
|
|
26
|
+
slug = text.strip().lower()
|
|
27
|
+
slug = re.sub(r"[^\w\s-]", "", slug)
|
|
28
|
+
slug = re.sub(r"\s", "-", slug)
|
|
29
|
+
return slug
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def convert_inline(text: str, title_lookup: dict[Path, str], source_dir: Path) -> str:
|
|
33
|
+
def replace_link(match: re.Match[str]) -> str:
|
|
34
|
+
label = match.group(1)
|
|
35
|
+
raw_target = match.group(2)
|
|
36
|
+
if raw_target.endswith(".md"):
|
|
37
|
+
target_path = (source_dir / raw_target).resolve()
|
|
38
|
+
target_title = title_lookup.get(target_path)
|
|
39
|
+
if target_title:
|
|
40
|
+
return f"[{label}](#{slugify_heading(target_title)})"
|
|
41
|
+
return label
|
|
42
|
+
|
|
43
|
+
return LINK_RE.sub(replace_link, text)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def render_markdown_lines(
|
|
47
|
+
lines: list[str], title_lookup: dict[Path, str], source_dir: Path
|
|
48
|
+
) -> list[str]:
|
|
49
|
+
rendered: list[str] = []
|
|
50
|
+
for kind, block in section_blocks(lines):
|
|
51
|
+
if kind == "bullet":
|
|
52
|
+
for line in block:
|
|
53
|
+
rendered.append("- " + convert_inline(line.lstrip()[2:], title_lookup, source_dir))
|
|
54
|
+
else:
|
|
55
|
+
paragraph = " ".join(line.strip() for line in block)
|
|
56
|
+
rendered.append(convert_inline(paragraph, title_lookup, source_dir))
|
|
57
|
+
rendered.append("")
|
|
58
|
+
|
|
59
|
+
if rendered and rendered[-1] == "":
|
|
60
|
+
rendered.pop()
|
|
61
|
+
return rendered
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def render_identity(identity_path: Path, title_lookup: dict[Path, str]) -> list[str]:
|
|
65
|
+
sections = parse_identity(identity_path)
|
|
66
|
+
rendered = ["## About the Operator", ""]
|
|
67
|
+
for section_name in IDENTITY_SECTIONS:
|
|
68
|
+
lines = sections.get(section_name, [])
|
|
69
|
+
if not lines:
|
|
70
|
+
continue
|
|
71
|
+
rendered.append(f"### {section_name}")
|
|
72
|
+
rendered.extend(
|
|
73
|
+
render_markdown_lines(lines, title_lookup=title_lookup, source_dir=identity_path.parent)
|
|
74
|
+
)
|
|
75
|
+
rendered.append("")
|
|
76
|
+
if rendered[-1] == "":
|
|
77
|
+
rendered.pop()
|
|
78
|
+
return rendered
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def render_see_also(thread, title_lookup: dict[Path, str]) -> list[str]:
|
|
82
|
+
items: list[str] = []
|
|
83
|
+
seen: set[Path] = set()
|
|
84
|
+
for link in extract_links(thread.connection_lines, thread.source_path):
|
|
85
|
+
if link.target_path is None or link.target_path in seen:
|
|
86
|
+
continue
|
|
87
|
+
target_title = title_lookup.get(link.target_path)
|
|
88
|
+
if not target_title:
|
|
89
|
+
continue
|
|
90
|
+
seen.add(link.target_path)
|
|
91
|
+
item = f"- [{link.label}](#{slugify_heading(target_title)})"
|
|
92
|
+
if link.annotation:
|
|
93
|
+
item += f" - {link.annotation}"
|
|
94
|
+
items.append(item)
|
|
95
|
+
return items
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def render_thread(thread, title_lookup: dict[Path, str]) -> list[str]:
|
|
99
|
+
lines = [
|
|
100
|
+
f"### {thread.title}",
|
|
101
|
+
"",
|
|
102
|
+
f"*{thread.tier_label} | hits: {thread.hits} | last touched: {thread.last_touched} | tags: {', '.join(thread.tags)}*",
|
|
103
|
+
"",
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
if thread.summary_lines:
|
|
107
|
+
lines.extend(
|
|
108
|
+
render_markdown_lines(
|
|
109
|
+
thread.summary_lines,
|
|
110
|
+
title_lookup=title_lookup,
|
|
111
|
+
source_dir=thread.source_path.parent,
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
else:
|
|
115
|
+
lines.append("*Missing Summary section in source thread.*")
|
|
116
|
+
|
|
117
|
+
see_also = render_see_also(thread, title_lookup)
|
|
118
|
+
if see_also:
|
|
119
|
+
lines.extend(["", "**See also**", ""])
|
|
120
|
+
lines.extend(see_also)
|
|
121
|
+
return lines
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def render_main_page(repo_root: Path, output_path: Path) -> str:
|
|
125
|
+
memex_dir = repo_root / "gator-command"
|
|
126
|
+
identity_path = memex_dir / "identity.md"
|
|
127
|
+
threads = load_threads(memex_dir)
|
|
128
|
+
title_lookup = {thread.source_path.resolve(): thread.title for thread in threads}
|
|
129
|
+
grouped = category_threads(threads)
|
|
130
|
+
|
|
131
|
+
today = dt.date.today().isoformat()
|
|
132
|
+
lines = [
|
|
133
|
+
"# Memex - Personal Knowledge System",
|
|
134
|
+
"",
|
|
135
|
+
f"**Last rendered:** {today} ",
|
|
136
|
+
f"**Threads:** {len(threads)} ",
|
|
137
|
+
f"**Categories:** {len(CATEGORY_ORDER)}",
|
|
138
|
+
"",
|
|
139
|
+
"This page is auto-generated from the Memex thread graph. The Memex is the source of truth; this file is a rendered summary for human navigation.",
|
|
140
|
+
"",
|
|
141
|
+
"**To regenerate:** `python gator-command/scripts/generate_wiki.py && python gator-command/scripts/generate_markdown.py`",
|
|
142
|
+
"",
|
|
143
|
+
"**Memex CLI** (`python gator-command/scripts/memex.py`):",
|
|
144
|
+
"- `memex status` — graph health, inbox count, patterns due, active threads (`--full` for complete state dump)",
|
|
145
|
+
"- `memex search <query>` — full-text search across threads, artifacts, designs, reports",
|
|
146
|
+
"- `memex read <type> <name>` — render thread/artifact/design/report (`--section` for specific sections)",
|
|
147
|
+
"- Add `--format json` for structured agent output, `--role <name>` for role-aware views",
|
|
148
|
+
"",
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
lines.extend(render_identity(identity_path, title_lookup))
|
|
152
|
+
lines.extend(["", "", "## Categories", ""])
|
|
153
|
+
|
|
154
|
+
for key, label in CATEGORY_ORDER:
|
|
155
|
+
titles = [thread.title for thread in grouped.get(key, [])]
|
|
156
|
+
if not titles:
|
|
157
|
+
continue
|
|
158
|
+
lines.append(f"- [{label}](#{slugify_heading(label)}) - {', '.join(titles)}")
|
|
159
|
+
|
|
160
|
+
for key, label in CATEGORY_ORDER:
|
|
161
|
+
category_items = grouped.get(key, [])
|
|
162
|
+
if not category_items:
|
|
163
|
+
continue
|
|
164
|
+
lines.extend(["", "", f"## {label}", ""])
|
|
165
|
+
for thread in category_items:
|
|
166
|
+
lines.extend(render_thread(thread, title_lookup))
|
|
167
|
+
lines.extend(["", ""])
|
|
168
|
+
|
|
169
|
+
while lines and lines[-1] == "":
|
|
170
|
+
lines.pop()
|
|
171
|
+
|
|
172
|
+
lines.extend(
|
|
173
|
+
[
|
|
174
|
+
"",
|
|
175
|
+
"",
|
|
176
|
+
"_This Markdown render was generated mechanically from the Memex thread graph. Summaries are extracted from source threads; no thread prose is synthesized during rendering._",
|
|
177
|
+
"",
|
|
178
|
+
]
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
text = "\n".join(lines)
|
|
182
|
+
output_path.write_text(text, encoding="utf-8")
|
|
183
|
+
return text
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def main() -> int:
|
|
187
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
188
|
+
parser.add_argument(
|
|
189
|
+
"--repo-root",
|
|
190
|
+
default=Path(__file__).resolve().parents[2],
|
|
191
|
+
type=Path,
|
|
192
|
+
help="Path to the repository root.",
|
|
193
|
+
)
|
|
194
|
+
parser.add_argument(
|
|
195
|
+
"--output",
|
|
196
|
+
default=Path("docs/wiki/Main_Page.md"),
|
|
197
|
+
type=Path,
|
|
198
|
+
help="Output path, relative to repo root unless absolute.",
|
|
199
|
+
)
|
|
200
|
+
args = parser.parse_args()
|
|
201
|
+
|
|
202
|
+
repo_root = args.repo_root.resolve()
|
|
203
|
+
output_path = args.output
|
|
204
|
+
if not output_path.is_absolute():
|
|
205
|
+
output_path = repo_root / output_path
|
|
206
|
+
|
|
207
|
+
render_main_page(repo_root=repo_root, output_path=output_path)
|
|
208
|
+
return 0
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
if __name__ == "__main__":
|
|
212
|
+
raise SystemExit(main())
|