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,650 @@
1
+ #!/usr/bin/env python3
2
+ """Spawn — create a new Memex repo from seed threads.
3
+
4
+ Knowledge-layer operation: selects threads, follows artifact/vault references,
5
+ copies the portable Memex skeleton, and mechanically repairs the subgraph.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ import shutil
12
+ import subprocess
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ SCRIPTS_DIR = Path(__file__).resolve().parent
17
+ REPO_ROOT = SCRIPTS_DIR.parent.parent.parent
18
+
19
+ sys.path.insert(0, str(SCRIPTS_DIR))
20
+ from generate_wiki import parse_frontmatter, parse_sections
21
+
22
+
23
+ # ── Constants ────────────────────────────────────────────────────────────────
24
+
25
+ # File extensions considered textual (safe to copy from vault)
26
+ TEXT_EXTENSIONS = {
27
+ ".md", ".txt", ".csv", ".json", ".yaml", ".yml",
28
+ ".py", ".sh", ".tex", ".bib", ".r", ".jl", ".toml",
29
+ }
30
+
31
+ # Markdown link pattern: [text](path)
32
+ LINK_RE = re.compile(r"\[([^\]]*)\]\(([^)]+)\)")
33
+
34
+
35
+ # ── Dependency resolution ────────────────────────────────────────────────────
36
+
37
+ def resolve_seed_threads(memex_dir: Path, names: list[str]) -> list[Path]:
38
+ """Resolve thread names to file paths. Fails fast if any not found."""
39
+ from memex_state import resolve_thread, list_thread_names
40
+
41
+ paths = []
42
+ for name in names:
43
+ name = name.strip()
44
+ if not name:
45
+ continue
46
+ path = resolve_thread(memex_dir, name)
47
+ if not path:
48
+ candidates = list_thread_names(memex_dir)
49
+ print(f"Thread not found: '{name}'")
50
+ if candidates:
51
+ print(f"Available: {', '.join(candidates)}")
52
+ raise SystemExit(1)
53
+ paths.append(path)
54
+ return paths
55
+
56
+
57
+ def collect_dependencies(memex_dir: Path, seed_paths: list[Path]) -> dict:
58
+ """Collect artifacts and vault files referenced by seed threads."""
59
+ artifacts = []
60
+ vault_files = []
61
+
62
+ artifacts_dir = memex_dir / "artifacts"
63
+ vault_dir = memex_dir / "vault"
64
+
65
+ for thread_path in seed_paths:
66
+ text = thread_path.read_text(encoding="utf-8")
67
+
68
+ # Find inline artifact references (links containing artifacts/)
69
+ for match in LINK_RE.finditer(text):
70
+ link_target = match.group(2)
71
+ if "artifacts/" in link_target:
72
+ # Resolve relative to thread's directory
73
+ resolved = (thread_path.parent / link_target).resolve()
74
+ if resolved.exists() and resolved not in artifacts:
75
+ artifacts.append(resolved)
76
+
77
+ # For each artifact, check for vault references via source: frontmatter
78
+ for art_path in artifacts:
79
+ text = art_path.read_text(encoding="utf-8")
80
+ fm, _ = parse_frontmatter(text)
81
+ source = fm.get("source", "")
82
+ if source and "vault/" in source:
83
+ # source is relative to repo root
84
+ vault_path = REPO_ROOT / source
85
+ if not vault_path.exists():
86
+ # Try relative to memex dir
87
+ vault_path = memex_dir / source.replace("gator-command/", "", 1)
88
+ if vault_path.exists() and vault_path.suffix.lower() in TEXT_EXTENSIONS:
89
+ if vault_path not in vault_files:
90
+ vault_files.append(vault_path)
91
+
92
+ # Also check seed threads for direct vault references
93
+ for thread_path in seed_paths:
94
+ text = thread_path.read_text(encoding="utf-8")
95
+ for match in LINK_RE.finditer(text):
96
+ link_target = match.group(2)
97
+ if "vault/" in link_target:
98
+ resolved = (thread_path.parent / link_target).resolve()
99
+ if not resolved.exists():
100
+ resolved = REPO_ROOT / link_target.lstrip("./")
101
+ if not resolved.exists():
102
+ resolved = memex_dir / link_target.replace("gator-command/", "", 1)
103
+ if resolved.exists() and resolved.suffix.lower() in TEXT_EXTENSIONS:
104
+ if resolved not in vault_files:
105
+ vault_files.append(resolved)
106
+
107
+ # Also check for source: lines outside frontmatter (like -> Source: path)
108
+ for line in text.splitlines():
109
+ if line.strip().startswith("-> Source:") or line.strip().startswith("→ Source:"):
110
+ # Extract backtick-quoted path or bare path
111
+ path_match = re.search(r"`([^`]+)`", line)
112
+ if path_match:
113
+ source_ref = path_match.group(1)
114
+ elif ":" in line:
115
+ source_ref = line.split(":", 1)[1].strip()
116
+ else:
117
+ continue
118
+ if "vault/" in source_ref:
119
+ vault_path = REPO_ROOT / source_ref
120
+ if not vault_path.exists():
121
+ vault_path = memex_dir / source_ref.replace("gator-command/", "", 1)
122
+ if vault_path.exists() and vault_path.suffix.lower() in TEXT_EXTENSIONS:
123
+ if vault_path not in vault_files:
124
+ vault_files.append(vault_path)
125
+
126
+ return {
127
+ "threads": seed_paths,
128
+ "artifacts": artifacts,
129
+ "vault_files": vault_files,
130
+ }
131
+
132
+
133
+ # ── Skeleton creation ────────────────────────────────────────────────────────
134
+
135
+ GENERIC_CONSTITUTION = """\
136
+ # {project_name} — Constitution
137
+
138
+ A Memex for {project_name} — tracking ideas, experiments, and progress across sessions.
139
+
140
+ ## What This Is
141
+
142
+ This repo is a persistence layer — it makes the LLM feel like it was there yesterday. The Memex is a small-world network of cross-referenced threads, not a hierarchical index. Navigate by entering any node and following links.
143
+
144
+ ## Roles
145
+
146
+ | Role | What it does | Who runs it |
147
+ |------|-------------|-------------|
148
+ | **Chat agent** | Talks to the human. Reads and writes the Memex in-session — updates threads, creates artifacts, compresses and rotates. | Claude Code (primary session) |
149
+ | **Enforcer** | Audits the Memex (read-only) and produces reports and documentation renders. Does not edit Memex files. Must be a different model. | Different model (e.g., Sonnet checking Opus's work) |
150
+
151
+ ## Memex Structure
152
+
153
+ ```
154
+ gator-command/ ← machinery (portable, upstream-maintained, don't modify)
155
+ roles.yaml ← role definitions
156
+ policies/ ← concierge wisdom and operational guidelines
157
+ procedures/ ← core Memex operational procedures
158
+ scripts/ ← CLI and tooling
159
+ gator-command/ ← knowledge graph (navigable, compressed, cross-referenced)
160
+ mission.md ← what we're building and why (always loaded)
161
+ roadmap.md ← feature roadmap (always loaded)
162
+ issues.md ← active bugs, blockers, known fragilities (always loaded)
163
+ identity.md ← stable traits, background, persistent interests (always loaded)
164
+ inbox.md ← zero-friction capture (always checked at session open, then cleared)
165
+ active-threads/ ← current topics, 5-8 files (always loaded, compression-budgeted)
166
+ threads/ ← lightweight reference threads (NOT always loaded, navigated via links)
167
+ patterns/ ← recurring rhythms (always loaded)
168
+ artifacts/ ← deep records, synopses, reference material (NOT always loaded)
169
+ vault/ ← external source files (gitignored, referenced by artifacts)
170
+ procedures/ ← project-specific workflows (organic, PI-owned)
171
+ reference-notes/ ← cognitive aids (consulted situationally)
172
+ commit_draft.md ← session change log, used as commit message source (cleared after commit)
173
+ blueprints/ ← subsystem documentation (how each piece works, kept current)
174
+ docs/ ← project documentation
175
+ reports/ ← enforcer audits, design reviews
176
+ wiki/ ← rendered output
177
+ ```
178
+
179
+ **Navigation**: No central index — the graph is the index. Enter through active threads, follow cross-references.
180
+
181
+ **Compression rule**: Always-loaded files should stay under 400 lines total. Depth lives in threads/, artifacts/, and cross-references.
182
+
183
+ **Graph connectivity**: Every operation must preserve all cross-references.
184
+
185
+ ## Operating Levels
186
+
187
+ Every message from the human operates at one of two levels:
188
+
189
+ | Level | Signal | What the agent does |
190
+ |-------|--------|--------------------|
191
+ | **Content** | `*c` prefix (or no prefix in user mode) | Use the Memex as infrastructure — read threads, capture ideas, update hits, create artifacts. The system is furniture. |
192
+ | **Meta** | `*m` prefix (or no prefix in designer mode) | Operate *on* the Memex itself — modify the constitution, restructure, write procedures. The system is the object of work. |
193
+
194
+ ### Operating Mode
195
+
196
+ The default level is set by `operating-mode:` in `identity.md` frontmatter:
197
+
198
+ | Mode | Default level | Override |
199
+ |------|--------------|----------|
200
+ | `designer` | Meta | `*c` prefix switches to content |
201
+ | `user` | Content | `*m` prefix switches to meta |
202
+
203
+ ## Session Opening
204
+
205
+ **Bootstrap detection**: Before running the normal procedure, check these three signals:
206
+ 1. `gator-command/identity.md` still contains bracket placeholders (e.g., `[Your role`)
207
+ 2. `gator-command/inbox.md` is empty (no captured thoughts)
208
+ 3. `gator-command/active-threads/` contains ≤ 2 threads
209
+
210
+ If all three are true, this is a **first session**. Skip the normal session-opening procedure entirely. Simply greet the human and ask what's on their mind. Everything you need to read, read silently.
211
+
212
+ **Normal sessions**: Follow the session-opening procedure: → [session-lifecycle.md](gator-command/procedures/session-lifecycle.md). Prefer the CLI path (`python gator-command/scripts/memex.py status --full --role <your-role> --format json`). Your role is specified in your entry point file.
213
+
214
+ ## Thread Lifecycle
215
+
216
+ Threads move between three tiers based on activity. Full lifecycle: → [thread-lifecycle.md](gator-command/procedures/thread-lifecycle.md)
217
+
218
+ New threads should follow the template: → [_TEMPLATE.md](gator-command/active-threads/_TEMPLATE.md)
219
+
220
+ Key rules:
221
+ - Active threads that exceed **60 lines** must be evaluated for splitting.
222
+ - Every thread must carry a `## Summary` (2–4 sentences).
223
+ - Demotion moves threads intact — no compression, no information loss.
224
+ - Cross-references annotate *why* the link exists, not just that it does.
225
+
226
+ ## Conventions
227
+
228
+ - **BRANCHING**: Default working branch is `dev`. Merges to `main` are human-authorized.
229
+ - **ARTIFACTS**: Deep records go in `gator-command/artifacts/` with date prefixes (`YYYY-MM-DD-short-title.md`).
230
+ - **VAULT**: External source files in `gator-command/vault/`, gitignored.
231
+ - **PROCEDURES**: Core procedures in `gator-command/procedures/`. Project-specific workflows in `gator-command/procedures/`.
232
+ - **REFERENCE NOTES**: Cognitive aids in `gator-command/reference-notes/`.
233
+ - **CHANGELOG**: Use `git log`. No separate changelog file.
234
+ - **COMMIT DRAFT**: Maintain `gator-command/commit_draft.md`. Append changes during the session.
235
+ - **ENFORCER INDEPENDENCE**: The enforcer must be a different model than the chat agent.
236
+ - **INBOX**: Capture and organization are different operations.
237
+ - **CLIP**: Verbatim exchange capture: `[save]` or `[clip]`. Procedure: → [clip-to-artifact.md](gator-command/procedures/clip-to-artifact.md)
238
+ """
239
+
240
+ BLANK_IDENTITY = """\
241
+ ---
242
+ operating-mode: user
243
+ ---
244
+
245
+ # Identity
246
+
247
+ [Your role, background, how you think, what you're working on. The agent uses this to calibrate collaboration style.]
248
+ """
249
+
250
+ BLANK_MISSION = """\
251
+ # Mission
252
+
253
+ ## What We're Building
254
+
255
+ [What this project is and why it exists.]
256
+
257
+ ## What Success Looks Like
258
+
259
+ [How you'll know it's working.]
260
+ """
261
+
262
+ BLANK_ROADMAP = """\
263
+ # Feature Roadmap
264
+
265
+ Last updated: {date}
266
+
267
+ ## Active
268
+
269
+ | # | Feature | Status | Next step |
270
+ |---|---------|--------|-----------|
271
+
272
+ ## Connections
273
+
274
+ → [mission.md](mission.md) — what success looks like
275
+ """
276
+
277
+ BLANK_ISSUES = """\
278
+ # Issues
279
+
280
+ ## Open
281
+
282
+ (none yet)
283
+
284
+ ## Working
285
+
286
+ (none yet)
287
+
288
+ ## Resolved
289
+
290
+ (none yet)
291
+ """
292
+
293
+ BLANK_INBOX = """\
294
+ # Inbox
295
+
296
+ Drop anything here. No formatting needed. The chat agent triages at session open.
297
+
298
+ ---
299
+ """
300
+
301
+
302
+ def create_skeleton(target_dir: Path, project_name: str, source_repo: Path) -> None:
303
+ """Create the portable Memex skeleton in target_dir."""
304
+ import datetime as dt
305
+ today = dt.date.today().isoformat()
306
+
307
+ target_dir.mkdir(parents=True, exist_ok=False)
308
+
309
+ # Directory structure
310
+ dirs = [
311
+ "gator-command/active-threads",
312
+ "gator-command/threads",
313
+ "gator-command/artifacts",
314
+ "gator-command/vault",
315
+ "gator-command/procedures",
316
+ "gator-command/reference-notes",
317
+ "gator-command/patterns",
318
+ "gator-command/scripts",
319
+ "gator-command/procedures",
320
+ "gator-command/policies",
321
+ "blueprints",
322
+ "docs/reports",
323
+ "docs/wiki",
324
+ ]
325
+ for d in dirs:
326
+ (target_dir / d).mkdir(parents=True, exist_ok=True)
327
+
328
+ # Constitution
329
+ (target_dir / "constitution.md").write_text(
330
+ GENERIC_CONSTITUTION.format(project_name=project_name),
331
+ encoding="utf-8",
332
+ )
333
+
334
+ # Entry point files
335
+ for ep in ["CLAUDE.md", "AGENTS.md", "GEMINI.md"]:
336
+ src = source_repo / ep
337
+ if src.exists():
338
+ shutil.copy2(src, target_dir / ep)
339
+
340
+ # Roles
341
+ roles_src = source_repo / "gator-command" / "roles.yaml"
342
+ if roles_src.exists():
343
+ shutil.copy2(roles_src, target_dir / "gator-command" / "roles.yaml")
344
+
345
+ # Scripts
346
+ for script in (source_repo / "gator-command" / "scripts").glob("*.py"):
347
+ shutil.copy2(script, target_dir / "gator-command" / "scripts" / script.name)
348
+
349
+ # Core procedures
350
+ for proc in (source_repo / "gator-command" / "procedures").glob("*.md"):
351
+ shutil.copy2(proc, target_dir / "gator-command" / "procedures" / proc.name)
352
+
353
+ # Policies
354
+ for policy in (source_repo / "gator-command" / "policies").glob("*.md"):
355
+ shutil.copy2(policy, target_dir / "gator-command" / "policies" / policy.name)
356
+
357
+ # Templates
358
+ for template_path in [
359
+ "gator-command/active-threads/_TEMPLATE.md",
360
+ "gator-command/artifacts/_TEMPLATE.md",
361
+ ]:
362
+ src = source_repo / template_path
363
+ if src.exists():
364
+ shutil.copy2(src, target_dir / template_path)
365
+
366
+ blueprint_template = source_repo / "blueprints" / "_TEMPLATE.md"
367
+ if blueprint_template.exists():
368
+ shutil.copy2(blueprint_template, target_dir / "blueprints" / "_TEMPLATE.md")
369
+
370
+ # Blank top-level memex files
371
+ (target_dir / "gator-command" / "identity.md").write_text(BLANK_IDENTITY, encoding="utf-8")
372
+ (target_dir / "gator-command" / "mission.md").write_text(BLANK_MISSION, encoding="utf-8")
373
+ (target_dir / "gator-command" / "roadmap.md").write_text(
374
+ BLANK_ROADMAP.format(date=today), encoding="utf-8",
375
+ )
376
+ (target_dir / "gator-command" / "issues.md").write_text(BLANK_ISSUES, encoding="utf-8")
377
+ (target_dir / "gator-command" / "inbox.md").write_text(BLANK_INBOX, encoding="utf-8")
378
+ (target_dir / "gator-command" / "commit_draft.md").write_text("", encoding="utf-8")
379
+
380
+ # Gitignore for vault
381
+ (target_dir / "gator-command" / "vault" / ".gitkeep").write_text("", encoding="utf-8")
382
+ gitignore = target_dir / ".gitignore"
383
+ gitignore.write_text("gator-command/vault/**\n!gator-command/vault/.gitkeep\n", encoding="utf-8")
384
+
385
+
386
+ # ── Copy seed content ────────────────────────────────────────────────────────
387
+
388
+ def copy_seed_content(target_dir: Path, deps: dict, memex_dir: Path) -> list[str]:
389
+ """Copy seed threads, artifacts, and vault files into the new repo."""
390
+ copied = []
391
+
392
+ for thread_path in deps["threads"]:
393
+ dest = target_dir / "gator-command" / "active-threads" / thread_path.name
394
+ shutil.copy2(thread_path, dest)
395
+ copied.append(f"gator-command/active-threads/{thread_path.name}")
396
+
397
+ for art_path in deps["artifacts"]:
398
+ dest = target_dir / "gator-command" / "artifacts" / art_path.name
399
+ shutil.copy2(art_path, dest)
400
+ copied.append(f"gator-command/artifacts/{art_path.name}")
401
+
402
+ for vault_path in deps["vault_files"]:
403
+ # Preserve subdirectory structure under vault/
404
+ try:
405
+ rel = vault_path.relative_to(memex_dir / "vault")
406
+ except ValueError:
407
+ rel = Path(vault_path.name)
408
+ dest = target_dir / "gator-command" / "vault" / rel
409
+ dest.parent.mkdir(parents=True, exist_ok=True)
410
+ shutil.copy2(vault_path, dest)
411
+ copied.append(f"gator-command/vault/{rel}")
412
+
413
+ return copied
414
+
415
+
416
+ # ── Mechanical graph repair ──────────────────────────────────────────────────
417
+
418
+ def repair_graph(target_dir: Path, copied_files: list[str]) -> list[str]:
419
+ """Mechanically repair cross-references in copied threads and artifacts.
420
+
421
+ - Remove connections to threads that don't exist in the new repo
422
+ - Fix relative paths for connections that do exist
423
+ - Leave HTML comments noting what was pruned
424
+ Returns list of repair descriptions.
425
+ """
426
+ repairs = []
427
+
428
+ # Build index of files that exist in the new repo
429
+ existing_stems = set()
430
+ existing_files = {} # stem -> relative path from active-threads
431
+ for f in copied_files:
432
+ p = Path(f)
433
+ existing_stems.add(p.stem)
434
+ existing_files[p.stem] = p.name
435
+
436
+ # Also include templates and top-level files
437
+ for md in (target_dir / "gator-command").rglob("*.md"):
438
+ existing_stems.add(md.stem)
439
+
440
+ # Process each copied thread and artifact
441
+ for rel_path in copied_files:
442
+ if not rel_path.endswith(".md"):
443
+ continue
444
+
445
+ file_path = target_dir / rel_path
446
+ text = file_path.read_text(encoding="utf-8")
447
+ original = text
448
+ file_dir = file_path.parent
449
+
450
+ # Fix connections section
451
+ new_lines = []
452
+ in_connections = False
453
+ for line in text.splitlines():
454
+ stripped = line.strip()
455
+
456
+ if stripped == "## Connections":
457
+ in_connections = True
458
+ new_lines.append(line)
459
+ continue
460
+
461
+ if in_connections and stripped.startswith("## "):
462
+ in_connections = False
463
+
464
+ if in_connections and (stripped.startswith("->") or stripped.startswith("→")):
465
+ # Parse the link target
466
+ link_match = LINK_RE.search(stripped)
467
+ if link_match:
468
+ link_text = link_match.group(1)
469
+ link_target = link_match.group(2)
470
+ target_stem = Path(link_target).stem
471
+
472
+ if target_stem not in existing_stems:
473
+ # Target doesn't exist — prune silently
474
+ # (no comment: parent thread names may be permission-sensitive)
475
+ repairs.append(f"PRUNED {rel_path}: 1 connection removed")
476
+ continue
477
+ else:
478
+ # Target exists — fix relative path if needed
479
+ # All seed threads are in active-threads/ now
480
+ if target_stem in existing_files:
481
+ target_file = existing_files[target_stem]
482
+ # Compute correct relative path
483
+ target_abs = None
484
+ for subdir in ["active-threads", "artifacts", "threads"]:
485
+ candidate = target_dir / "gator-command" / subdir / target_file
486
+ if candidate.exists():
487
+ target_abs = candidate
488
+ break
489
+ if target_abs:
490
+ import os
491
+ new_rel = os.path.relpath(target_abs, file_dir)
492
+ new_rel = Path(new_rel).as_posix()
493
+ # Get the annotation (everything after " — " or " - ")
494
+ annotation_match = re.search(
495
+ r" [—\-] (.+)$", stripped
496
+ )
497
+ annotation = annotation_match.group(1) if annotation_match else ""
498
+ arrow = "→" if "→" in stripped else "->"
499
+ new_line = f"{arrow} [{link_text}]({new_rel})"
500
+ if annotation:
501
+ new_line += f" — {annotation}"
502
+ new_lines.append(new_line)
503
+ if new_line != stripped:
504
+ repairs.append(
505
+ f"FIXED {rel_path}: path to {link_text}"
506
+ )
507
+ continue
508
+
509
+ # Also fix inline links outside connections
510
+ if not in_connections:
511
+ def fix_link(m):
512
+ lt, lp = m.group(1), m.group(2)
513
+ ts = Path(lp).stem
514
+ if ("threads/" in lp or "artifacts/" in lp) and ts not in existing_stems:
515
+ repairs.append(f"INLINE {rel_path}: removed link to {lt}")
516
+ return lt # Plain text, no link
517
+ return m.group(0)
518
+
519
+ line = LINK_RE.sub(fix_link, line)
520
+
521
+ new_lines.append(line)
522
+
523
+ new_text = "\n".join(new_lines)
524
+ # Preserve trailing newline if original had one
525
+ if original.endswith("\n") and not new_text.endswith("\n"):
526
+ new_text += "\n"
527
+
528
+ if new_text != original:
529
+ file_path.write_text(new_text, encoding="utf-8")
530
+
531
+ # Fix artifact source-thread paths
532
+ for rel_path in copied_files:
533
+ if "artifacts/" not in rel_path or not rel_path.endswith(".md"):
534
+ continue
535
+ file_path = target_dir / rel_path
536
+ text = file_path.read_text(encoding="utf-8")
537
+ if "source-thread:" in text:
538
+ # Update source-thread to point to active-threads/
539
+ new_text = re.sub(
540
+ r"(source-thread:\s*)[\./]*(?:threads|active-threads)/",
541
+ r"\1../active-threads/",
542
+ text,
543
+ )
544
+ if new_text != text:
545
+ file_path.write_text(new_text, encoding="utf-8")
546
+ repairs.append(f"FIXED {rel_path}: source-thread path")
547
+
548
+ return repairs
549
+
550
+
551
+ # ── Git initialization ───────────────────────────────────────────────────────
552
+
553
+ def git_init_and_commit(target_dir: Path, project_name: str) -> None:
554
+ """Initialize git repo and create initial commit."""
555
+ subprocess.run(["git", "init"], cwd=str(target_dir), check=True,
556
+ capture_output=True)
557
+ subprocess.run(["git", "checkout", "-b", "dev"], cwd=str(target_dir),
558
+ check=True, capture_output=True)
559
+ subprocess.run(["git", "add", "-A"], cwd=str(target_dir), check=True,
560
+ capture_output=True)
561
+ subprocess.run(
562
+ ["git", "commit", "-m", f"Spawn {project_name} from parent Memex\n\n"
563
+ f"Memex skeleton + seed threads from parent graph.\n"
564
+ f"Knowledge-layer spawn — independent repo, no upstream."],
565
+ cwd=str(target_dir), check=True, capture_output=True,
566
+ )
567
+
568
+
569
+ # ── Main entry point (called from memex.py) ─────────────────────────────────
570
+
571
+ def run_spawn(name: str, thread_names: list[str], dry_run: bool = False,
572
+ fmt: str = "pretty") -> int:
573
+ """Execute the spawn operation."""
574
+ import json
575
+
576
+ memex_dir = REPO_ROOT / "gator-command"
577
+ target_dir = REPO_ROOT.parent / name
578
+
579
+ if target_dir.exists() and not dry_run:
580
+ print(f"Target directory already exists: {target_dir}")
581
+ return 1
582
+
583
+ # Resolve seed threads
584
+ seed_paths = resolve_seed_threads(memex_dir, thread_names)
585
+
586
+ # Collect dependencies
587
+ deps = collect_dependencies(memex_dir, seed_paths)
588
+
589
+ if dry_run:
590
+ result = {
591
+ "command": "spawn",
592
+ "project": name,
593
+ "target": str(target_dir),
594
+ "dry_run": True,
595
+ "threads": [p.stem for p in deps["threads"]],
596
+ "artifacts": [p.stem for p in deps["artifacts"]],
597
+ "vault_files": [str(p.name) for p in deps["vault_files"]],
598
+ }
599
+ if fmt == "json":
600
+ print(json.dumps(result, indent=2))
601
+ else:
602
+ print(f" Spawn: {name}")
603
+ print(f" Target: {target_dir}")
604
+ print(f" Threads: {', '.join(p.stem for p in deps['threads'])}")
605
+ print(f" Artifacts: {', '.join(p.stem for p in deps['artifacts'])}")
606
+ print(f" Vault files: {', '.join(p.name for p in deps['vault_files'])}")
607
+ print(f"\n Dry run — no files created.")
608
+ return 0
609
+
610
+ # Create skeleton
611
+ print(f" Creating skeleton: {target_dir}")
612
+ create_skeleton(target_dir, name, REPO_ROOT)
613
+
614
+ # Copy seed content
615
+ print(f" Copying seed content...")
616
+ copied = copy_seed_content(target_dir, deps, memex_dir)
617
+ for c in copied:
618
+ print(f" + {c}")
619
+
620
+ # Repair graph
621
+ print(f" Repairing graph...")
622
+ repairs = repair_graph(target_dir, copied)
623
+ for r in repairs:
624
+ print(f" {r}")
625
+
626
+ # Git init
627
+ print(f" Initializing git repo...")
628
+ git_init_and_commit(target_dir, name)
629
+
630
+ if fmt == "json":
631
+ result = {
632
+ "command": "spawn",
633
+ "project": name,
634
+ "target": str(target_dir),
635
+ "threads": [p.stem for p in deps["threads"]],
636
+ "artifacts": [p.stem for p in deps["artifacts"]],
637
+ "vault_files": [str(p.name) for p in deps["vault_files"]],
638
+ "repairs": repairs,
639
+ }
640
+ print(json.dumps(result, indent=2))
641
+ else:
642
+ print(f"\n ✓ Spawned {name} at {target_dir}")
643
+ print(f" {len(deps['threads'])} thread(s), "
644
+ f"{len(deps['artifacts'])} artifact(s), "
645
+ f"{len(deps['vault_files'])} vault file(s)")
646
+ print(f" {len(repairs)} graph repair(s)")
647
+ print(f" Branch: dev")
648
+ print(f"\n cd {target_dir} && claude")
649
+
650
+ return 0
@@ -0,0 +1,32 @@
1
+ # Blueprints
2
+
3
+ Per-feature flow maps — how features work end-to-end, referencing modules, functions, and charters.
4
+
5
+ Blueprints are optional. Create one when a feature or subsystem needs an architect-facing flow map.
6
+
7
+ ## Structure
8
+
9
+ Each blueprint describes one feature:
10
+
11
+ ```markdown
12
+ # Blueprint: <Feature Name>
13
+
14
+ ## What It Does
15
+ [1-2 sentences]
16
+
17
+ ## Flow
18
+ 1. **<Module>** → `function_name()` — what happens
19
+ → [charter-name](../charters/charter-name.md)
20
+
21
+ ## Key Charters
22
+ [Charters involved in this flow]
23
+
24
+ ## Invariants
25
+ [What must be true for this to work]
26
+ ```
27
+
28
+ ## Master Blueprint
29
+
30
+ Once you have several blueprints, create `system.md` — the architectural overview that links to individual feature blueprints.
31
+
32
+ See the [charters README](../charters/README.md) for the notation system used in function references.