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,286 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ memex-lint.py — Mechanical invariant checker for the .gator/ knowledge layer.
4
+
5
+ Read-only. Reports findings, changes nothing. Cross-platform (no bash).
6
+
7
+ Checks:
8
+ 1. Budget — always-loaded files under 400 lines total
9
+ 2. Thread size — active threads over 60 lines flagged for split
10
+ 3. Frontmatter — required fields (last-touched, category, hits)
11
+ 4. Summary — every thread has ## Summary
12
+ 5. Cross-references — markdown links resolve to existing files
13
+ 6. Orphans — threads with no inbound references
14
+ 7. Active thread count — target 5-8
15
+
16
+ Usage:
17
+ python memex-lint.py [--path /repo] [--json]
18
+
19
+ @reads: .gator/ knowledge layer (or gator-command/ for source repo)
20
+ @writes: stdout only
21
+ """
22
+
23
+ import argparse
24
+ import json as json_mod
25
+ import re
26
+ import sys
27
+ from datetime import datetime
28
+ from pathlib import Path
29
+
30
+ SCRIPTS_DIR = Path(__file__).resolve().parent
31
+ sys.path.insert(0, str(SCRIPTS_DIR))
32
+ from gator_core import find_gator_root, ensure_utf8_stdout # noqa: E402
33
+
34
+ BUDGET = 400
35
+ SPLIT_THRESHOLD = 60
36
+ ACTIVE_TARGET = (3, 8)
37
+ SKIP_FILES = {"_TEMPLATE.md", "_template.md", "README.md"}
38
+
39
+
40
+ # ── output helpers ─────────────────────────────────────────────────────────
41
+
42
+ errors = 0
43
+ warnings = 0
44
+ findings = []
45
+
46
+
47
+ def error(msg):
48
+ global errors
49
+ errors += 1
50
+ findings.append({"level": "error", "message": msg})
51
+ print(f" ERROR: {msg}")
52
+
53
+
54
+ def warn(msg):
55
+ global warnings
56
+ warnings += 1
57
+ findings.append({"level": "warn", "message": msg})
58
+ print(f" WARN: {msg}")
59
+
60
+
61
+ def ok(msg):
62
+ findings.append({"level": "ok", "message": msg})
63
+ print(f" OK: {msg}")
64
+
65
+
66
+ # ── checks ─────────────────────────────────────────────────────────────────
67
+
68
+ def count_lines(path):
69
+ try:
70
+ return len(path.read_text(encoding="utf-8", errors="replace").splitlines())
71
+ except OSError:
72
+ return 0
73
+
74
+
75
+ def get_threads(memex_dir):
76
+ """Collect all thread files from active-threads/ and threads/."""
77
+ threads = []
78
+ for subdir in ("active-threads", "threads"):
79
+ d = memex_dir / subdir
80
+ if d.is_dir():
81
+ for f in sorted(d.glob("*.md")):
82
+ if f.name not in SKIP_FILES:
83
+ threads.append(f)
84
+ return threads
85
+
86
+
87
+ def check_budget(memex_dir):
88
+ """Check 1: always-loaded files under budget."""
89
+ print("-- Budget Check --")
90
+ budget_files = []
91
+
92
+ for name in ("identity.md", "inbox.md"):
93
+ f = memex_dir / name
94
+ if f.exists():
95
+ budget_files.append(f)
96
+
97
+ at_dir = memex_dir / "active-threads"
98
+ if at_dir.is_dir():
99
+ for f in sorted(at_dir.glob("*.md")):
100
+ if f.name not in SKIP_FILES:
101
+ budget_files.append(f)
102
+
103
+ patterns_dir = memex_dir / "patterns"
104
+ if patterns_dir.is_dir():
105
+ for f in sorted(patterns_dir.glob("*.md")):
106
+ if f.name not in SKIP_FILES:
107
+ budget_files.append(f)
108
+ elif (memex_dir / "patterns.md").exists():
109
+ budget_files.append(memex_dir / "patterns.md")
110
+
111
+ total = sum(count_lines(f) for f in budget_files)
112
+ if total > BUDGET:
113
+ error(f"Always-loaded budget: {total} / {BUDGET} lines (OVER by {total - BUDGET})")
114
+ else:
115
+ ok(f"Always-loaded budget: {total} / {BUDGET} lines ({BUDGET - total} headroom)")
116
+ print()
117
+
118
+
119
+ def check_thread_size(memex_dir):
120
+ """Check 2: active threads over 60 lines."""
121
+ print("-- Thread Size Check (active threads > 60 lines) --")
122
+ at_dir = memex_dir / "active-threads"
123
+ if not at_dir.is_dir():
124
+ ok("No active-threads/ directory")
125
+ print()
126
+ return
127
+
128
+ for f in sorted(at_dir.glob("*.md")):
129
+ if f.name in SKIP_FILES:
130
+ continue
131
+ lines = count_lines(f)
132
+ if lines > SPLIT_THRESHOLD:
133
+ warn(f"{f.name}: {lines} lines -- evaluate for split")
134
+ else:
135
+ ok(f"{f.name}: {lines} lines")
136
+ print()
137
+
138
+
139
+ def check_frontmatter(threads):
140
+ """Check 3: required frontmatter fields."""
141
+ print("-- Frontmatter Check --")
142
+ required = ["last-touched"] # category, hits, tags are optional in modern Gator
143
+
144
+ for f in threads:
145
+ text = f.read_text(encoding="utf-8", errors="replace")
146
+ for field in required:
147
+ if f"\n{field}:" not in text and not text.startswith(f"{field}:"):
148
+ error(f"{f.name}: missing {field}")
149
+
150
+ ok("Frontmatter check complete")
151
+ print()
152
+
153
+
154
+ def check_summaries(threads):
155
+ """Check 4: every thread has ## Summary."""
156
+ print("-- Summary Check --")
157
+ for f in threads:
158
+ text = f.read_text(encoding="utf-8", errors="replace")
159
+ if "## Summary" not in text:
160
+ error(f"{f.name}: missing ## Summary section")
161
+
162
+ ok("Summary check complete")
163
+ print()
164
+
165
+
166
+ def check_crossrefs(threads, memex_dir):
167
+ """Check 5: markdown links resolve."""
168
+ print("-- Cross-Reference Integrity --")
169
+ link_pattern = re.compile(r'\[([^\]]+)\]\(([^)]+\.md)\)')
170
+
171
+ for f in threads:
172
+ text = f.read_text(encoding="utf-8", errors="replace")
173
+ for match in link_pattern.finditer(text):
174
+ link_path = match.group(2)
175
+ if link_path.startswith("http"):
176
+ continue
177
+ resolved = (f.parent / link_path).resolve()
178
+ if not resolved.is_file():
179
+ error(f"{f.name}: broken link -> {link_path}")
180
+
181
+ ok("Cross-reference check complete")
182
+ print()
183
+
184
+
185
+ def check_orphans(threads, memex_dir):
186
+ """Check 6: threads with no inbound references."""
187
+ print("-- Orphan Check (threads with no inbound references) --")
188
+
189
+ # Build reference map
190
+ all_text = {}
191
+ for f in threads:
192
+ all_text[f] = f.read_text(encoding="utf-8", errors="replace")
193
+
194
+ for f in threads:
195
+ name = f.name
196
+ inbound = sum(1 for other, text in all_text.items()
197
+ if other != f and name in text)
198
+ if inbound == 0:
199
+ warn(f"{name}: no inbound references (potential orphan)")
200
+
201
+ print()
202
+
203
+
204
+ def check_active_count(memex_dir):
205
+ """Check 7: active thread count in target range."""
206
+ print("-- Active Thread Count --")
207
+ at_dir = memex_dir / "active-threads"
208
+ if not at_dir.is_dir():
209
+ ok("No active-threads/ directory")
210
+ print()
211
+ return
212
+
213
+ count = sum(1 for f in at_dir.glob("*.md") if f.name not in SKIP_FILES)
214
+ lo, hi = ACTIVE_TARGET
215
+ if count > hi:
216
+ error(f"Active threads: {count} (max {hi})")
217
+ elif count < lo:
218
+ warn(f"Active threads: {count} (unusually low)")
219
+ else:
220
+ ok(f"Active threads: {count} (target: {lo}-{hi})")
221
+ print()
222
+
223
+
224
+ # ── main ───────────────────────────────────────────────────────────────────
225
+
226
+ def main():
227
+ ensure_utf8_stdout()
228
+
229
+ parser = argparse.ArgumentParser(description="Gator knowledge layer lint")
230
+ parser.add_argument("--path", "-p", help="Path to repo (default: cwd)")
231
+ parser.add_argument("--json", "-j", action="store_true", help="JSON output")
232
+ args = parser.parse_args()
233
+
234
+ repo_root = find_gator_root(args.path)
235
+ if not repo_root:
236
+ print("Error: no .gator/ found.", file=sys.stderr)
237
+ sys.exit(1)
238
+
239
+ # Detect knowledge layer location
240
+ # gator-command source repo uses gator-command/, fleet repos use .gator/
241
+ gc_dir = repo_root / "gator-command"
242
+ gator_dir = repo_root / ".gator"
243
+ if gc_dir.is_dir() and (gc_dir / "active-threads").is_dir():
244
+ memex_dir = gc_dir
245
+ else:
246
+ memex_dir = gator_dir
247
+
248
+ today = datetime.now().strftime("%Y-%m-%d")
249
+ print(f"==============================")
250
+ print(f" Gator Lint -- {today}")
251
+ print(f" {memex_dir}")
252
+ print(f"==============================")
253
+ print()
254
+
255
+ threads = get_threads(memex_dir)
256
+
257
+ check_budget(memex_dir)
258
+ check_thread_size(memex_dir)
259
+ check_frontmatter(threads)
260
+ check_summaries(threads)
261
+ check_crossrefs(threads, memex_dir)
262
+ check_orphans(threads, memex_dir)
263
+ check_active_count(memex_dir)
264
+
265
+ print("==============================")
266
+ if errors == 0 and warnings == 0:
267
+ print("All checks passed.")
268
+ elif errors == 0:
269
+ print(f"{warnings} warning(s), 0 errors.")
270
+ else:
271
+ print(f"{errors} error(s), {warnings} warning(s).")
272
+ print("==============================")
273
+
274
+ if args.json:
275
+ print()
276
+ print(json_mod.dumps({
277
+ "errors": errors,
278
+ "warnings": warnings,
279
+ "findings": findings,
280
+ }, indent=2))
281
+
282
+ sys.exit(errors)
283
+
284
+
285
+ if __name__ == "__main__":
286
+ main()
@@ -0,0 +1,205 @@
1
+ #!/bin/bash
2
+ # memex-lint.sh — Mechanical invariant checker for the Memex
3
+ # Read-only. Reports findings, changes nothing.
4
+ # Usage: bash scripts/memex-lint.sh
5
+
6
+ MEMEX_DIR="$(cd "$(dirname "$0")/.." && pwd)"
7
+ REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
8
+ ERRORS=0
9
+ WARNINGS=0
10
+
11
+ RED='\033[0;31m'
12
+ YELLOW='\033[0;33m'
13
+ GREEN='\033[0;32m'
14
+ NC='\033[0m'
15
+
16
+ error() { echo -e "${RED}ERROR:${NC} $1"; ((ERRORS++)); }
17
+ warn() { echo -e "${YELLOW}WARN:${NC} $1"; ((WARNINGS++)); }
18
+ ok() { echo -e "${GREEN}OK:${NC} $1"; }
19
+
20
+ echo "=============================="
21
+ echo " Memex Lint — $(date +%Y-%m-%d)"
22
+ echo "=============================="
23
+ echo ""
24
+
25
+ # ── 1. Budget Check ──────────────────────────────────────────────
26
+ echo "── Budget Check ──"
27
+
28
+ # Budget includes: identity.md + active-threads (excluding _TEMPLATE.md) + patterns/*.md (excluding README.md) + inbox.md
29
+ BUDGET_FILES=()
30
+ BUDGET_FILES+=("$MEMEX_DIR/identity.md")
31
+ BUDGET_FILES+=("$MEMEX_DIR/inbox.md")
32
+
33
+ for f in "$MEMEX_DIR"/active-threads/*.md; do
34
+ [[ "$(basename "$f")" == "_TEMPLATE.md" ]] && continue
35
+ BUDGET_FILES+=("$f")
36
+ done
37
+
38
+ for f in "$MEMEX_DIR"/patterns/*.md; do
39
+ [[ "$(basename "$f")" == "README.md" ]] && continue
40
+ BUDGET_FILES+=("$f")
41
+ done
42
+
43
+ TOTAL_LINES=0
44
+ for f in "${BUDGET_FILES[@]}"; do
45
+ lines=$(wc -l < "$f" 2>/dev/null || echo 0)
46
+ TOTAL_LINES=$((TOTAL_LINES + lines))
47
+ done
48
+
49
+ BUDGET=400
50
+ if [ "$TOTAL_LINES" -gt "$BUDGET" ]; then
51
+ error "Always-loaded budget: $TOTAL_LINES / $BUDGET lines (OVER by $((TOTAL_LINES - BUDGET)))"
52
+ else
53
+ ok "Always-loaded budget: $TOTAL_LINES / $BUDGET lines ($((BUDGET - TOTAL_LINES)) headroom)"
54
+ fi
55
+
56
+ # Constitution line count (informational, not budgeted)
57
+ CONST_LINES=$(wc -l < "$REPO_ROOT/constitution.md" 2>/dev/null || echo 0)
58
+ echo " Constitution: $CONST_LINES lines (budget-exempt)"
59
+ echo ""
60
+
61
+ # ── 2. Thread Size Check ─────────────────────────────────────────
62
+ echo "── Thread Size Check (active threads > 60 lines) ──"
63
+
64
+ SPLIT_THRESHOLD=60
65
+ for f in "$MEMEX_DIR"/active-threads/*.md; do
66
+ [[ "$(basename "$f")" == "_TEMPLATE.md" ]] && continue
67
+ lines=$(wc -l < "$f")
68
+ name=$(basename "$f")
69
+ if [ "$lines" -gt "$SPLIT_THRESHOLD" ]; then
70
+ warn "$name: $lines lines — evaluate for split or compression"
71
+ else
72
+ ok "$name: $lines lines"
73
+ fi
74
+ done
75
+ echo ""
76
+
77
+ # ── 3. Frontmatter Check ─────────────────────────────────────────
78
+ echo "── Frontmatter Check ──"
79
+
80
+ ALL_THREADS=()
81
+ for f in "$MEMEX_DIR"/active-threads/*.md "$MEMEX_DIR"/threads/*.md; do
82
+ [[ "$(basename "$f")" == "_TEMPLATE.md" ]] && continue
83
+ [ -f "$f" ] && ALL_THREADS+=("$f")
84
+ done
85
+
86
+ for f in "${ALL_THREADS[@]}"; do
87
+ name=$(basename "$f")
88
+
89
+ if ! grep -q "^last-touched:" "$f"; then
90
+ error "$name: missing last-touched"
91
+ fi
92
+ if ! grep -q "^category:" "$f"; then
93
+ error "$name: missing category"
94
+ fi
95
+ if ! grep -q "^hits:" "$f"; then
96
+ error "$name: missing hits"
97
+ fi
98
+ if ! grep -q "^tags:" "$f"; then
99
+ error "$name: missing tags"
100
+ fi
101
+ done
102
+
103
+ # Check for valid categories
104
+ VALID_CATEGORIES="mathematics|cognition|systems|ventures|economics|civic"
105
+ for f in "${ALL_THREADS[@]}"; do
106
+ name=$(basename "$f")
107
+ cat_line=$(grep "^category:" "$f" 2>/dev/null || echo "")
108
+ if [ -n "$cat_line" ]; then
109
+ cat_value=$(echo "$cat_line" | sed 's/^category: *//')
110
+ if ! echo "$cat_value" | grep -qE "^($VALID_CATEGORIES)$"; then
111
+ error "$name: invalid category '$cat_value'"
112
+ fi
113
+ fi
114
+ done
115
+
116
+ ok "Frontmatter check complete"
117
+ echo ""
118
+
119
+ # ── 4. Summary Check ─────────────────────────────────────────────
120
+ echo "── Summary Check ──"
121
+
122
+ for f in "${ALL_THREADS[@]}"; do
123
+ name=$(basename "$f")
124
+ if ! grep -q "^## Summary" "$f"; then
125
+ error "$name: missing ## Summary section"
126
+ fi
127
+ done
128
+
129
+ ok "Summary check complete"
130
+ echo ""
131
+
132
+ # ── 5. Cross-Reference Integrity ─────────────────────────────────
133
+ echo "── Cross-Reference Integrity ──"
134
+
135
+ for f in "${ALL_THREADS[@]}"; do
136
+ name=$(basename "$f")
137
+ dir=$(dirname "$f")
138
+
139
+ # Extract markdown links: [text](path.md)
140
+ grep -oE '\[([^]]+)\]\(([^)]+\.md)\)' "$f" 2>/dev/null | while read -r match; do
141
+ link_path=$(echo "$match" | grep -oE '\(([^)]+)\)' | tr -d '()')
142
+
143
+ # Skip external URLs
144
+ [[ "$link_path" == http* ]] && continue
145
+
146
+ # Resolve relative path
147
+ resolved="$dir/$link_path"
148
+ resolved=$(cd "$dir" 2>/dev/null && realpath -m "$link_path" 2>/dev/null || echo "$resolved")
149
+
150
+ if [ ! -f "$resolved" ]; then
151
+ error "$name: broken link → $link_path"
152
+ fi
153
+ done
154
+ done
155
+
156
+ ok "Cross-reference check complete"
157
+ echo ""
158
+
159
+ # ── 6. Orphan Check ──────────────────────────────────────────────
160
+ echo "── Orphan Check (threads with no inbound references) ──"
161
+
162
+ for f in "${ALL_THREADS[@]}"; do
163
+ name=$(basename "$f")
164
+ name_no_ext="${name%.md}"
165
+
166
+ # Search all threads for references to this file
167
+ inbound=$(grep -rl "$name" "$MEMEX_DIR"/active-threads/ "$MEMEX_DIR"/threads/ 2>/dev/null | grep -v "$f" | grep -v "_TEMPLATE.md" | wc -l)
168
+
169
+ if [ "$inbound" -eq 0 ]; then
170
+ warn "$name: no inbound references (potential orphan)"
171
+ fi
172
+ done
173
+
174
+ echo ""
175
+
176
+ # ── 7. Active Thread Count ───────────────────────────────────────
177
+ echo "── Active Thread Count ──"
178
+
179
+ ACTIVE_COUNT=0
180
+ for f in "$MEMEX_DIR"/active-threads/*.md; do
181
+ [[ "$(basename "$f")" == "_TEMPLATE.md" ]] && continue
182
+ ((ACTIVE_COUNT++))
183
+ done
184
+
185
+ if [ "$ACTIVE_COUNT" -gt 8 ]; then
186
+ error "Active threads: $ACTIVE_COUNT (max 8)"
187
+ elif [ "$ACTIVE_COUNT" -lt 3 ]; then
188
+ warn "Active threads: $ACTIVE_COUNT (unusually low)"
189
+ else
190
+ ok "Active threads: $ACTIVE_COUNT (target: 5-8)"
191
+ fi
192
+ echo ""
193
+
194
+ # ── Summary ──────────────────────────────────────────────────────
195
+ echo "=============================="
196
+ if [ "$ERRORS" -eq 0 ] && [ "$WARNINGS" -eq 0 ]; then
197
+ echo -e "${GREEN}All checks passed.${NC}"
198
+ elif [ "$ERRORS" -eq 0 ]; then
199
+ echo -e "${YELLOW}$WARNINGS warning(s), 0 errors.${NC}"
200
+ else
201
+ echo -e "${RED}$ERRORS error(s), $WARNINGS warning(s).${NC}"
202
+ fi
203
+ echo "=============================="
204
+
205
+ exit $ERRORS